diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index ae375b935b..0000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: 2 - -jobs: - build: - docker: - - image: "debian:bullseye" - steps: - - checkout - - run: - name: Installing build dependencies - command: 'apt-get update && apt-get install -y sudo gcc g++ build-essential git cmake libssl-dev zlib1g-dev' - - run: - name: Creating Build Files - command: 'cmake -H. -Bbuild' - - run: - name: Creating Binary Files - command: 'cmake --build build' - - run: - name: Testing installation - command: 'cmake --build build --target install' diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000..01b4f5f946 --- /dev/null +++ b/.clang-format @@ -0,0 +1,66 @@ +# Generated from CLion C/C++ Code Style settings +BasedOnStyle: LLVM +AccessModifierOffset: -8 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: None +AlignOperands: Align +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Always +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakTemplateDeclarations: No +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true +BreakBeforeBinaryOperators: None +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +ColumnLimit: 0 +CompactNamespaces: false +ContinuationIndentWidth: 8 +IndentCaseLabels: true +IndentPPDirectives: BeforeHash +IndentWidth: 8 +KeepEmptyLinesAtTheStartOfBlocks: true +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: None +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PointerAlignment: Right +ReflowComments: false +SpaceAfterCStyleCast: true +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 0 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +TabWidth: 8 +UseTab: ForContinuationAndIndentation diff --git a/.cspell.json b/.cspell.json index 14eab9cec9..0d8e2cd969 100644 --- a/.cspell.json +++ b/.cspell.json @@ -142,7 +142,14 @@ "khanda", "oclock", "moai", - "xbps" + "xbps", + "chacha20", + "chacha", + "nullopt", + "chrono", + "ciphersuite", + "rmap", + "WSAPOLLFD" ], "flagWords": [ "hte" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95cf307c37..eb9c2a65ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,10 +31,10 @@ jobs: permissions: contents: write concurrency: - group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.cfg.arch }}-(${{ matrix.cfg.cpp-version }}) + group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.cfg.arch }}-(${{ matrix.cfg.cpp }}-${{ matrix.cfg.version }}) cancel-in-progress: true - name: Linux ${{matrix.cfg.arch}} (${{matrix.cfg.cpp-version}}) - runs-on: ${{matrix.cfg.os}} + name: Linux ${{ matrix.cfg.arch }} (${{ matrix.cfg.cpp }}-${{ matrix.cfg.version }}${{ matrix.cfg.name-extra }}) + runs-on: ${{ matrix.cfg.os }} strategy: fail-fast: false # Don't fail everything if one fails. We want to test each OS/Compiler individually matrix: @@ -42,46 +42,54 @@ jobs: # arm7hf is a self-hosted docker-based runner at Brainbox.cc. Raspberry Pi 4, 8gb 4-core with NEON cfg: # clang++ - - { arch: 'amd64', concurrency: 4, os: ubuntu-20.04, package: clang-10, cpp-version: clang++-10, cmake-flags: '', cpack: 'no', ctest: 'no', mold: 'yes' } - - { arch: 'amd64', concurrency: 4, os: ubuntu-22.04, package: clang-11, cpp-version: clang++-11, cmake-flags: '', cpack: 'no', ctest: 'no', mold: 'yes' } - - { arch: 'amd64', concurrency: 4, os: ubuntu-22.04, package: clang-12, cpp-version: clang++-12, cmake-flags: '', cpack: 'no', ctest: 'no', mold: 'yes' } - - { arch: 'amd64', concurrency: 4, os: ubuntu-22.04, package: clang-13, cpp-version: clang++-13, cmake-flags: '', cpack: 'no', ctest: 'no', mold: 'yes' } - - { arch: 'amd64', concurrency: 4, os: ubuntu-22.04, package: clang-14, cpp-version: clang++-14, cmake-flags: '-DDPP_CORO=ON', cpack: 'no', ctest: 'no', mold: 'yes' } - - { arch: 'amd64', concurrency: 4, os: ubuntu-22.04, package: clang-15, cpp-version: clang++-15, cmake-flags: '-DDPP_CORO=ON', cpack: 'no', ctest: 'no', mold: 'yes' } - - { arch: 'amd64', concurrency: 4, os: ubuntu-24.04, package: clang-16, cpp-version: clang++-16, cmake-flags: '-DDPP_CORO=ON', cpack: 'no', ctest: 'no', mold: 'yes' } - - { arch: 'amd64', concurrency: 4, os: ubuntu-24.04, package: clang-17, cpp-version: clang++-17, cmake-flags: '-DDPP_CORO=ON', cpack: 'no', ctest: 'no', mold: 'yes' } - - { arch: 'amd64', concurrency: 4, os: ubuntu-24.04, package: clang-18, cpp-version: clang++-18, cmake-flags: '-DDPP_CORO=ON', cpack: 'no', ctest: 'no', mold: 'yes' } + - { arch: 'amd64', concurrency: 4, os: ubuntu-20.04, package: clang-10, cpp: clang++, version: 10, cmake-flags: '', cpack: 'no', ctest: 'no', mold: 'yes' } + - { arch: 'amd64', concurrency: 4, os: ubuntu-22.04, package: clang-11, cpp: clang++, version: 11, cmake-flags: '', cpack: 'no', ctest: 'no', mold: 'yes' } + - { arch: 'amd64', concurrency: 4, os: ubuntu-22.04, package: clang-12, cpp: clang++, version: 12, cmake-flags: '', cpack: 'no', ctest: 'no', mold: 'yes' } + - { arch: 'amd64', concurrency: 4, os: ubuntu-22.04, package: clang-13, cpp: clang++, version: 13, cmake-flags: '', cpack: 'no', ctest: 'no', mold: 'yes' } + - { arch: 'amd64', concurrency: 4, os: ubuntu-22.04, package: clang-14, cpp: clang++, version: 14, cmake-flags: '-DDPP_CORO=ON', cpack: 'no', ctest: 'no', mold: 'yes' } + - { arch: 'amd64', concurrency: 4, os: ubuntu-22.04, package: clang-15, cpp: clang++, version: 15, cmake-flags: '-DDPP_CORO=ON', cpack: 'no', ctest: 'no', mold: 'yes' } + - { arch: 'amd64', concurrency: 4, os: ubuntu-24.04, package: clang-16, cpp: clang++, version: 16, cmake-flags: '-DDPP_CORO=ON', cpack: 'no', ctest: 'no', mold: 'yes' } + - { arch: 'amd64', concurrency: 4, os: ubuntu-24.04, package: clang-17, cpp: clang++, version: 17, cmake-flags: '-DDPP_CORO=ON', cpack: 'no', ctest: 'no', mold: 'yes' } + - { arch: 'amd64', concurrency: 4, os: ubuntu-24.04, package: clang-18, cpp: clang++, version: 18, cmake-flags: '-DDPP_CORO=ON', cpack: 'no', ctest: 'no', mold: 'yes' } + - { arch: 'amd64', concurrency: 4, os: ubuntu-24.04, package: 'clang-19 libc++-19-dev libc++abi-19-dev', cpp: clang++, version: 19, cmake-flags: '-DDPP_CORO=ON -DCMAKE_CXX_FLAGS="-stdlib=libc++" -DCMAKE_EXE_LINKER_FLAGS="-stdlib=libc++"', cpack: 'no', ctest: 'no', mold: 'yes', name-extra: ' libc++', llvm-apt: 'yes' } # g++ - - { arch: 'amd64', concurrency: 4, os: ubuntu-24.04, package: g++-13, cpp-version: g++-13, cmake-flags: '-DDPP_CORO=ON', cpack: 'no', ctest: 'no', mold: 'yes' } - - { arch: 'amd64', concurrency: 4, os: ubuntu-24.04, package: g++-14, cpp-version: g++-14, cmake-flags: '-DDPP_CORO=ON', cpack: 'no', ctest: 'no', mold: 'yes' } - - { arch: 'amd64', concurrency: 4, os: ubuntu-22.04, package: g++-12, cpp-version: g++-12, cmake-flags: '-DDPP_CORO=ON', cpack: 'no', ctest: 'no', mold: 'yes' } - - { arch: 'amd64', concurrency: 4, os: ubuntu-22.04, package: g++-11, cpp-version: g++-11, cmake-flags: '-DDPP_CORO=ON', cpack: 'no', ctest: 'no', mold: 'yes' } - - { arch: 'amd64', concurrency: 4, os: ubuntu-22.04, package: g++-10, cpp-version: g++-10, cmake-flags: '', cpack: 'yes', ctest: 'no', mold: 'yes' } - - { arch: 'amd64', concurrency: 4, os: ubuntu-20.04, package: g++-9, cpp-version: g++-9, cmake-flags: '', cpack: 'no', ctest: 'no', mold: 'yes' } - - { arch: 'amd64', concurrency: 4, os: ubuntu-20.04, package: g++-8, cpp-version: g++-8, cmake-flags: '', cpack: 'no', ctest: 'yes', mold: 'yes' } + - { arch: 'amd64', concurrency: 4, os: ubuntu-24.04, package: g++-13, cpp: g++, version: 13, cmake-flags: '-DDPP_CORO=ON', cpack: 'no', ctest: 'no', mold: 'yes' } + - { arch: 'amd64', concurrency: 4, os: ubuntu-24.04, package: g++-14, cpp: g++, version: 14, cmake-flags: '-DDPP_CORO=ON', cpack: 'no', ctest: 'no', mold: 'yes' } + - { arch: 'amd64', concurrency: 4, os: ubuntu-22.04, package: g++-12, cpp: g++, version: 12, cmake-flags: '-DDPP_CORO=ON', cpack: 'no', ctest: 'no', mold: 'yes' } + - { arch: 'amd64', concurrency: 4, os: ubuntu-22.04, package: g++-11, cpp: g++, version: 11, cmake-flags: '-DDPP_CORO=ON', cpack: 'no', ctest: 'no', mold: 'yes' } + - { arch: 'amd64', concurrency: 4, os: ubuntu-22.04, package: g++-10, cpp: g++, version: 10, cmake-flags: '', cpack: 'yes', ctest: 'no', mold: 'yes' } + - { arch: 'amd64', concurrency: 4, os: ubuntu-20.04, package: g++-9, cpp: g++, version: 9, cmake-flags: '', cpack: 'no', ctest: 'no', mold: 'yes' } + - { arch: 'amd64', concurrency: 4, os: ubuntu-20.04, package: g++-8, cpp: g++, version: 8, cmake-flags: '', cpack: 'no', ctest: 'yes', mold: 'yes' } # Self hosted - - { arch: 'arm7hf', concurrency: 4, os: [self-hosted, linux, ARM], package: g++-12, cpp-version: g++-12, cmake-flags: '', cpack: 'yes', ctest: 'no', mold: 'no' } - - { arch: 'arm64', concurrency: 4, os: [self-hosted, linux, ARM64], package: g++-12, cpp-version: g++-12, cmake-flags: '', cpack: 'yes', ctest: 'no', mold: 'yes' } + - { arch: 'arm7hf', concurrency: 4, os: [self-hosted, linux, ARM], package: g++-12, cpp: g++, version: 12, cmake-flags: '', cpack: 'yes', ctest: 'no', mold: 'no' } + - { arch: 'arm64', concurrency: 4, os: [self-hosted, linux, ARM64], package: g++-12, cpp: g++, version: 12, cmake-flags: '', cpack: 'yes', ctest: 'no', mold: 'yes' } steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit - name: Checkout D++ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - - name: Install apt packages - run: sudo sed -i 's/azure\.//' /etc/apt/sources.list && sudo apt update && sudo apt-get install -y ${{ matrix.cfg.package }} pkg-config libsodium-dev libopus-dev zlib1g-dev rpm + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Setup mold if: ${{ matrix.cfg.mold == 'yes' }} - uses: rui314/setup-mold@2e332a0b602c2fc65d2d3995941b1b29a5f554a0 # v1 + uses: rui314/setup-mold@b015f7e3f2938ad3a5ed6e5111a8c6c7c1d6db6e # v1 + + - name: Add LLVM apt repository + if: ${{ matrix.cfg.llvm-apt }} + run: | + osname=`cat /etc/os-release | grep -e "^VERSION_CODENAME" | cut -d= -f2` + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - + sudo apt-add-repository -y "deb http://apt.llvm.org/$osname/ llvm-toolchain-$osname-${{ matrix.cfg.version }} main" + + - name: Install apt packages + run: sudo sed -i 's/azure\.//' /etc/apt/sources.list && sudo apt update && sudo apt-get install -y ${{ matrix.cfg.package }} pkg-config libopus-dev zlib1g-dev rpm - name: Generate CMake run: cmake -B build -DDPP_NO_VCPKG=ON -DAVX_TYPE=AVX0 -DCMAKE_BUILD_TYPE=Release ${{matrix.cfg.cmake-flags}} env: - CXX: ${{matrix.cfg.cpp-version}} + CXX: ${{ matrix.cfg.cpp }}-${{ matrix.cfg.version }} - name: Build Project run: cd build && make -j${{ matrix.cfg.concurrency }} @@ -103,14 +111,14 @@ jobs: - name: Upload Binary (DEB) if: ${{ matrix.cfg.cpack == 'yes' }} - uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: "libdpp - Debian Package ${{matrix.cfg.arch}}" path: '${{github.workspace}}/build/*.deb' - name: Upload Binary (RPM) if: ${{ matrix.cfg.cpack == 'yes' }} - uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: "libdpp - RPM Package ${{matrix.cfg.arch}}" path: '${{github.workspace}}/build/*.rpm' @@ -118,23 +126,23 @@ jobs: macos: permissions: contents: write - name: macOS ${{matrix.cfg.arch}} (${{matrix.cfg.cpp-version}}) - runs-on: ${{matrix.cfg.os}} + name: macOS ${{ matrix.cfg.arch }} (${{ matrix.cfg.cpp }}-${{ matrix.cfg.version }}) + runs-on: ${{ matrix.cfg.os }} strategy: fail-fast: false # Don't fail everything if one fails. We want to test each OS/Compiler individually matrix: cfg: - - { arch: 'arm64', concurrency: 3, os: macos-latest, cpp-version: clang++-16, cmake-flags: '', xcode-version: '16.0-beta' } - - { arch: 'arm64', concurrency: 3, os: macos-latest, cpp-version: clang++-15, cmake-flags: '', xcode-version: '15.3' } - - { arch: 'arm64', concurrency: 3, os: macos-latest, cpp-version: clang++-14, cmake-flags: '', xcode-version: '14.3.1' } + - { arch: 'arm64', concurrency: 3, os: macos-latest, cpp: clang++, version: 16, cmake-flags: '', xcode-version: '16.0.0' } + - { arch: 'arm64', concurrency: 3, os: macos-latest, cpp: clang++, version: 15, cmake-flags: '', xcode-version: '15.3' } + - { arch: 'arm64', concurrency: 3, os: macos-latest, cpp: clang++, version: 14, cmake-flags: '', xcode-version: '14.3.1' } steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit - name: Checkout D++ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Update Xcode uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0 @@ -142,7 +150,7 @@ jobs: xcode-version: ${{ matrix.cfg.xcode-version }} - name: Install homebrew packages - run: brew install cmake make libsodium opus openssl pkg-config + run: brew install cmake make opus openssl pkg-config - name: Generate CMake run: cmake -B build -DDPP_NO_VCPKG=ON -DCMAKE_BUILD_TYPE=Release -DDPP_CORO=ON -DAVX_TYPE=AVX0 @@ -183,12 +191,12 @@ jobs: runs-on: ${{matrix.cfg.os}} steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit - name: Checkout D++ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: path: main @@ -226,7 +234,7 @@ jobs: - name: Upload Binary if: ${{ matrix.cfg.upload }} - uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: "libdpp - Windows ${{matrix.cfg.name}}-${{matrix.cfg.config}}-vs${{matrix.cfg.vs}}" path: '${{github.workspace}}/main/build/*.zip' @@ -248,12 +256,12 @@ jobs: runs-on: ${{matrix.cfg.os}} steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit - name: Checkout D++ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Install Packages run: sudo sed -i 's/azure\.//' /etc/apt/sources.list && sudo apt update && sudo apt-get install -y cmake rpm @@ -268,13 +276,13 @@ jobs: run: cd build && sudo cpack --verbose || cat /home/runner/work/DPP/DPP/build/_CPack_Packages/Linux/DEB/PreinstallOutput.log - name: Upload Binaries (DEB) - uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: "libdpp - Debian Package ${{matrix.cfg.name}}" path: "${{github.workspace}}/build/*.deb" - name: Upload Binaries (RPM) - uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: "libdpp - RPM Package ${{matrix.cfg.name}}" path: "${{github.workspace}}/build/*.rpm" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ce56c8c98a..56607b609b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -41,16 +41,16 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3.25.11 + uses: github/codeql-action/init@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # v3.26.10 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -58,12 +58,12 @@ jobs: # Prefix the list here with "+" to use these queries and those in the config file. - name: Setup mold - uses: rui314/setup-mold@2e332a0b602c2fc65d2d3995941b1b29a5f554a0 + uses: rui314/setup-mold@b015f7e3f2938ad3a5ed6e5111a8c6c7c1d6db6e - name: Build run: cmake -B build -DDPP_NO_VCPKG=ON -DAVX_TYPE=AVX0 -DCMAKE_BUILD_TYPE=Release && cmake --build build -j4 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3.25.11 + uses: github/codeql-action/analyze@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # v3.26.10 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/construct-vcpkg-info.yml b/.github/workflows/construct-vcpkg-info.yml index 17e23fc404..be0ddf44b3 100644 --- a/.github/workflows/construct-vcpkg-info.yml +++ b/.github/workflows/construct-vcpkg-info.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit @@ -25,7 +25,7 @@ jobs: php-version: '8.1' - name: Checkout D++ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: submodules: recursive diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 56d5770ba5..7b0990bcf5 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -17,11 +17,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit - name: 'Checkout Repository' - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: 'Dependency Review' uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 40fe4e9d06..4f69516150 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -22,32 +22,32 @@ jobs: cancel-in-progress: false steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit - name: Set up QEMU - uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 + uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 + uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 - name: Login to DockerHub - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@15560696de535e4014efeff63c48f16952e52dd1 # v6.2.0 + uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 with: push: true tags: brainboxdotcc/dpp diff --git a/.github/workflows/documentation-check.yml b/.github/workflows/documentation-check.yml index f4fb32f6b1..6976497ad0 100644 --- a/.github/workflows/documentation-check.yml +++ b/.github/workflows/documentation-check.yml @@ -22,12 +22,12 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit - name: Checkout D++ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Check docs spelling run: npx -y cspell lint --language-id=cpp --no-progress --no-summary --show-context --show-suggestions --relative --color docpages/*.md include/dpp/*.h diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 30a9cf5446..ca694b0bd1 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit @@ -35,7 +35,7 @@ jobs: php-version: '8.0' - name: Checkout D++ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: submodules: recursive diff --git a/.github/workflows/gitguardian.yml b/.github/workflows/gitguardian.yml index b03592b7af..7542e71005 100644 --- a/.github/workflows/gitguardian.yml +++ b/.github/workflows/gitguardian.yml @@ -14,16 +14,16 @@ jobs: cancel-in-progress: true steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: fetch-depth: 0 # fetch all history so multiple commits can be scanned - name: GitGuardian scan - uses: GitGuardian/ggshield-action@3af6bd67c964cffe01a0f8f5c0dd04b8cda99e6b # master + uses: GitGuardian/ggshield-action@0cb2e309a0ce2cd43141dc998c28d9b225a7a1fe # master env: GITHUB_PUSH_BEFORE_SHA: ${{ github.event.before }} GITHUB_PUSH_BASE_SHA: ${{ github.event.base }} diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index d4802a85c7..c4cf52f566 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index a42bd6de20..80afe1ff49 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -32,17 +32,17 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit - name: "Checkout code" - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3 + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 with: results_file: results.sarif results_format: sarif @@ -64,7 +64,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: SARIF file path: results.sarif @@ -72,6 +72,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3.25.11 + uses: github/codeql-action/upload-sarif@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # v3.26.10 with: sarif_file: results.sarif diff --git a/.github/workflows/sitemap.yml b/.github/workflows/sitemap.yml index 6f49980d2b..cfe156e5e1 100644 --- a/.github/workflows/sitemap.yml +++ b/.github/workflows/sitemap.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 2e6162ab76..fc5c9fb328 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit diff --git a/.github/workflows/target-master.yml b/.github/workflows/target-master.yml index 6eed961974..7c6c7c2d82 100644 --- a/.github/workflows/target-master.yml +++ b/.github/workflows/target-master.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit diff --git a/.github/workflows/test-docs-examples.yml b/.github/workflows/test-docs-examples.yml index 6f3356281d..1be005e42c 100644 --- a/.github/workflows/test-docs-examples.yml +++ b/.github/workflows/test-docs-examples.yml @@ -25,17 +25,17 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit - name: Checkout D++ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: submodules: recursive - name: Install apt packages - run: sudo sed -i 's/azure\.//' /etc/apt/sources.list && sudo apt-get update && sudo apt-get install -y g++-12 libsodium-dev libopus-dev zlib1g-dev libmpg123-dev liboggz-dev cmake libfmt-dev libopusfile-dev + run: sudo sed -i 's/azure\.//' /etc/apt/sources.list && sudo apt-get update && sudo apt-get install -y g++-12 libopus-dev zlib1g-dev libmpg123-dev liboggz-dev cmake libfmt-dev libopusfile-dev - name: Generate CMake run: mkdir build && cd build && cmake -DDPP_NO_VCPKG=ON -DAVX_TYPE=T_fallback -DDPP_CORO=ON -DCMAKE_BUILD_TYPE=Debug .. diff --git a/CMakeLists.txt b/CMakeLists.txt index f2e7a58f2d..31b14d70ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,15 +24,19 @@ option(RUN_LDCONFIG "Run ldconfig after installation" ON) option(DPP_INSTALL "Generate the install target" ON) option(DPP_BUILD_TEST "Build the test program" ON) option(DPP_NO_VCPKG "No VCPKG" OFF) -option(DPP_CORO "Experimental support for C++20 coroutines" OFF) +option(DPP_CORO "Support for C++20 coroutines" OFF) option(DPP_FORMATTERS "Support for C++20 formatters" OFF) option(DPP_USE_EXTERNAL_JSON "Use an external installation of nlohmann::json" OFF) option(DPP_USE_PCH "Use precompiled headers to speed up compilation" OFF) option(AVX_TYPE "Force AVX type for speeding up audio mixing" OFF) +option(DPP_TEST_VCPKG "Force VCPKG build without VCPKG installed (for development use only!)" OFF) include(CheckCXXSymbolExists) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) add_compile_definitions(DPP_BUILD) +add_compile_definitions(NOMINMAX) + +include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/colour.cmake") set(DPP_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}) @@ -50,10 +54,14 @@ string(CONCAT DPP_VERSION "${DPP_VERSION_MAJOR}.${DPP_VERSION_MINOR}.${DPP_VERSI set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${DPP_ROOT_PATH}/cmake/") -if (DPP_NO_VCPKG) - message("-- INFO: Explicitly disabling VCPKG as running inside the CI action.") +if (DPP_TEST_VCPKG) + message("-- ${Red}DEVELOPER WARNING${ColourReset}: Running in ${Red}VCPKG test mode${ColourReset}: EMULATING A VCPKG BUILD WITHOUT VCPKG") else() - message("-- INFO: Using VCPKG if detected") + if (DPP_NO_VCPKG) + message("-- INFO: Explicitly disabling VCPKG as running inside the CI action.") + else() + message("-- INFO: Using VCPKG if detected") + endif() endif() if (WIN32 AND NOT MINGW AND BUILD_SHARED_LIBS) @@ -61,14 +69,14 @@ if (WIN32 AND NOT MINGW AND BUILD_SHARED_LIBS) configure_file("${DPP_ROOT_PATH}/src/dpp/dpp.rc.in" "${DPP_ROOT_PATH}/src/dpp/dpp.rc" NEWLINE_STYLE WIN32) endif() -if (NOT DPP_NO_VCPKG AND EXISTS "${_VCPKG_ROOT_DIR}") +if (DPP_TEST_VCPKG) set(PROJECT_NAME "dpp") project( - "${PROJECT_NAME}" - VERSION "${DPP_VERSION}" - LANGUAGES CXX - HOMEPAGE_URL "https://dpp.dev/" - DESCRIPTION "An incredibly lightweight C++ Discord library." + "${PROJECT_NAME}" + VERSION "${DPP_VERSION}" + LANGUAGES CXX + HOMEPAGE_URL "https://dpp.dev/" + DESCRIPTION "An incredibly lightweight C++ Discord library." ) if (MSVC AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") @@ -82,27 +90,51 @@ if (NOT DPP_NO_VCPKG AND EXISTS "${_VCPKG_ROOT_DIR}") add_subdirectory(library-vcpkg) else() - set(PROJECT_NAME "libdpp") - project( - "${PROJECT_NAME}" - VERSION "${DPP_VERSION}" - LANGUAGES CXX - HOMEPAGE_URL "https://dpp.dev/" - DESCRIPTION "An incredibly lightweight C++ Discord library." - ) - - if (MSVC AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set(DPP_CLANG_CL true) - endif() - - # Required before we add any subdirectories. - if (DPP_BUILD_TEST) - enable_testing(${CMAKE_CURRENT_SOURCE_DIR}) + if (NOT DPP_NO_VCPKG AND EXISTS "${_VCPKG_ROOT_DIR}") + set(PROJECT_NAME "dpp") + project( + "${PROJECT_NAME}" + VERSION "${DPP_VERSION}" + LANGUAGES CXX + HOMEPAGE_URL "https://dpp.dev/" + DESCRIPTION "An incredibly lightweight C++ Discord library." + ) + + if (MSVC AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set(DPP_CLANG_CL true) + endif() + + # Required before we add any subdirectories. + if (DPP_BUILD_TEST) + enable_testing(${CMAKE_CURRENT_SOURCE_DIR}) + endif() + + add_subdirectory(library-vcpkg) + else() + set(PROJECT_NAME "libdpp") + project( + "${PROJECT_NAME}" + VERSION "${DPP_VERSION}" + LANGUAGES CXX + HOMEPAGE_URL "https://dpp.dev/" + DESCRIPTION "An incredibly lightweight C++ Discord library." + ) + + if (MSVC AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set(DPP_CLANG_CL true) + endif() + + # Required before we add any subdirectories. + if (DPP_BUILD_TEST) + enable_testing(${CMAKE_CURRENT_SOURCE_DIR}) + endif() + + add_subdirectory(library) endif() - - add_subdirectory(library) endif() +find_package(Filesystem) + if(DPP_USE_EXTERNAL_JSON) # We do nothing here, we just assume it is on the include path. # nlohmann::json's cmake stuff does all kinds of weird, and is more hassle than it's worth. @@ -116,3 +148,7 @@ else() # that made no sense, it seems they may have changed their parsing rules somehow. message("-- Using bundled nlohmann::json") endif() + +if (NOT WIN32) + target_link_libraries(dpp PRIVATE std::filesystem) +endif() diff --git a/Dockerfile b/Dockerfile index 103c9f7115..b9ded8ca5f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ -FROM ubuntu:noble@sha256:2e863c44b718727c860746568e1d54afd13b2fa71b160f5cd9058fc436217b30 +FROM ubuntu:noble@sha256:99c35190e22d294cdace2783ac55effc69d32896daaa265f0bbedbcde4fbe3e5 -RUN apt-get update && apt-get install --no-install-recommends -y libssl-dev zlib1g-dev libsodium-dev libopus-dev cmake pkg-config g++ gcc git make && apt-get clean && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install --no-install-recommends -y libssl-dev zlib1g-dev libopus-dev cmake pkg-config g++ gcc git make && apt-get clean && rm -rf /var/lib/apt/lists/* WORKDIR /usr/src/DPP diff --git a/README.md b/README.md index cdc67777d3..953da47cb6 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ D++ is a lightweight and efficient library for **Discord** written in **modern C * Sharding and clustering (Many shards, one process: specify the number of shards, or let the library decide) * Highly optimised ETF (Erlang Term Format) support for very fast websocket throughput * [Slash Commands/Interactions support](https://dpp.dev/slashcommands.html) -* [Voice support](https://dpp.dev/soundboard.html) (sending **and** receiving audio) +* [Voice support](https://dpp.dev/soundboard.html) (sending **and** receiving audio) with [DAVE](https://daveprotocol.com) End-To-End Encryption * The entire Discord API is available for use in the library * Stable [Windows support](https://dpp.dev/buildwindows.html) * Ready-made compiled packages for Windows, Raspberry Pi (ARM64/ARM7/ARMv6), Debian x86/x64, and RPM based distributions @@ -77,6 +77,8 @@ You can find more examples in our [example page](https://dpp.dev/example-program ## 💻 Supported Systems +We support the following OS families, as long as they are still officially supported by their provider. **We will provide no support for operating systems past end-of-life**. + ### Linux The library runs ideally on **Linux**. @@ -150,15 +152,16 @@ Other compilers may work (either newer versions of those listed above, or differ ### External Dependencies (You must install these) -* [OpenSSL](https://openssl.org/) (whichever `-dev` package comes with your OS) -* [zlib](https://zlib.net) (whichever `-dev` package comes with your OS) +* [OpenSSL](https://openssl.org/) (For HTTPS, will use whichever `-dev` package comes with your OS) +* [zlib](https://zlib.net) (For websocket compression, will use whichever `-dev` package comes with your OS) #### Optional Dependencies -For voice support you require both of: -* [libopus](https://www.opus-codec.org) -* [libsodium](https://libsodium.org/) +For **voice support** you require: +* [libopus](https://www.opus-codec.org) (For audio encoding/decoding) +* Note that our **windows zips** come packaged with copies of this library - you do not need to install it yourself! ### Included Dependencies (Packaged with the library) -* [JSON for Modern C++](https://json.nlohmann.me/) +* [JSON for Modern C++](https://json.nlohmann.me/) (You can bring your own nlohmann::json into D++ by setting a CMAKE flag) +* [MLS++](https://github.com/cisco/mlspp) (This is statically compiled into the library if voice support is enabled) diff --git a/buildtools/classes/Generator/SyncGenerator.php b/buildtools/classes/Generator/SyncGenerator.php index e666feffdd..a5c40319a9 100644 --- a/buildtools/classes/Generator/SyncGenerator.php +++ b/buildtools/classes/Generator/SyncGenerator.php @@ -88,7 +88,7 @@ public function checkForChanges(): bool */ public function generateHeaderDef(string $returnType, string $currentFunction, string $parameters, string $noDefaults, string $parameterTypes, string $parameterNames): string { - return "$returnType {$currentFunction}_sync($parameters);\n\n"; + return "DPP_DEPRECATED(\"Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html\") $returnType {$currentFunction}_sync($parameters);\n\n"; } /** @@ -107,6 +107,7 @@ public function getCommentArray(): array return [ " * \memberof dpp::cluster", " * @throw dpp::rest_exception upon failure to execute REST function", + " * @deprecated This function is deprecated, please use coroutines instead.", " * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread.", " * Avoid direct use of this function inside an event handler.", ]; diff --git a/buildtools/classes/Packager/Vcpkg.php b/buildtools/classes/Packager/Vcpkg.php index 1790cbefc2..f677ba6675 100644 --- a/buildtools/classes/Packager/Vcpkg.php +++ b/buildtools/classes/Packager/Vcpkg.php @@ -170,7 +170,6 @@ function constructPortAndVersionFile(string $sha512 = "0"): string "license": "Apache-2.0", "supports": "((windows & !static & !uwp) | linux | osx)", "dependencies": [ - "libsodium", "nlohmann-json", "openssl", "opus", diff --git a/cmake/ARM64ToolChain.cmake b/cmake/ARM64ToolChain.cmake index 8087e3f375..d9caf02693 100644 --- a/cmake/ARM64ToolChain.cmake +++ b/cmake/ARM64ToolChain.cmake @@ -47,6 +47,5 @@ EXECUTE_PROCESS(COMMAND sudo mv TMPFILE /etc/apt/sources.list) EXECUTE_PROCESS(COMMAND sudo dpkg --add-architecture arm64) EXECUTE_PROCESS(COMMAND sudo apt-add-repository -y ppa:canonical-kernel-team/ppa) EXECUTE_PROCESS(COMMAND sudo apt update) -EXECUTE_PROCESS(COMMAND sudo apt install -y cmake gcc-8-aarch64-linux-gnu g++-8-aarch64-linux-gnu libc6-dev-arm64-cross zlib1g-dev:arm64 libssl-dev:arm64 libopus-dev:arm64 libsodium-dev:arm64) -EXECUTE_PROCESS(COMMAND sudo mv /usr/lib/aarch64-linux-gnu/pkgconfig/libsodium.pc /usr/lib/pkgconfig/) +EXECUTE_PROCESS(COMMAND sudo apt install -y cmake gcc-8-aarch64-linux-gnu g++-8-aarch64-linux-gnu libc6-dev-arm64-cross zlib1g-dev:arm64 libssl-dev:arm64 libopus-dev:arm64) diff --git a/cmake/ARMv7ToolChain.cmake b/cmake/ARMv7ToolChain.cmake index 7b9bacf3bc..215df7e0ce 100644 --- a/cmake/ARMv7ToolChain.cmake +++ b/cmake/ARMv7ToolChain.cmake @@ -46,6 +46,4 @@ EXECUTE_PROCESS(COMMAND printf "deb [arch=amd64] http://archive.ubuntu.com/ubunt EXECUTE_PROCESS(COMMAND sudo mv TMPFILE /etc/apt/sources.list) EXECUTE_PROCESS(COMMAND sudo dpkg --add-architecture armhf) EXECUTE_PROCESS(COMMAND sudo apt update) -EXECUTE_PROCESS(COMMAND sudo apt install -y cmake gcc-8-arm-linux-gnueabihf g++-8-arm-linux-gnueabihf zlib1g-dev:armhf libssl-dev:armhf libopus-dev:armhf libsodium-dev:armhf) -EXECUTE_PROCESS(COMMAND sudo mv /usr/lib/arm-linux-gnueabihf/pkgconfig/libsodium.pc /usr/lib/pkgconfig/) - +EXECUTE_PROCESS(COMMAND sudo apt install -y cmake gcc-8-arm-linux-gnueabihf g++-8-arm-linux-gnueabihf zlib1g-dev:armhf libssl-dev:armhf libopus-dev:armhf) diff --git a/cmake/CPackSetup.cmake b/cmake/CPackSetup.cmake index fcdcf156af..047b7600ff 100644 --- a/cmake/CPackSetup.cmake +++ b/cmake/CPackSetup.cmake @@ -50,8 +50,8 @@ set(CPACK_FREEBSD_PACKAGE_MAINTAINER "bsd@dpp.dev") set(CPACK_FREEBSD_PACKAGE_ORIGIN "misc/libdpp") set(CPACK_RPM_PACKAGE_LICENSE "Apache 2.0") set(CPACK_PACKAGE_CONTACT "https://discord.gg/dpp") # D++ Development Discord -set(CPACK_DEBIAN_PACKAGE_DEPENDS "libsodium23 (>= 1.0.17-1), libopus0 (>= 1.3-1)") -set(CPACK_RPM_PACKAGE_REQUIRES "libsodium >= 1.0.20-1, opus >= 1.3.1") +set(CPACK_DEBIAN_PACKAGE_DEPENDS "libopus0 (>= 1.3-1)") +set(CPACK_RPM_PACKAGE_REQUIRES "opus >= 1.3.1") set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "An incredibly lightweight C++ Discord library") set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") set(CPACK_DEBIAN_PACKAGE_SECTION "libs") diff --git a/cmake/DetectArchitecture.cmake b/cmake/DetectArchitecture.cmake index 4c3a2030b5..af4f84a5b7 100644 --- a/cmake/DetectArchitecture.cmake +++ b/cmake/DetectArchitecture.cmake @@ -3,8 +3,13 @@ include(CheckCXXSourceRuns) function(check_instruction_set INSTRUCTION_SET_NAME INSTRUCTION_SET_FLAG INSTRUCTION_SET_INTRINSIC) set(INSTRUCTION_SET_CODE " + #if defined(__arm__) || defined(__aarch64__) + #include + #else #include #include + #endif + int main() { ${INSTRUCTION_SET_INTRINSIC}; @@ -27,12 +32,14 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") "AVX1?/arch:AVX?__m128i value{}#auto result = _mm_extract_epi32(value, 0)" "AVX2?/arch:AVX2?__m256i value{}#auto result = _mm256_add_epi32(__m256i{}, __m256i{})" "AVX512?/arch:AVX512?int32_t result[16]#const _mm512i& value{}#_mm512_store_si512(result, value)" + "AVX1024??uint8x16_t mask{ 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F }#vandq_u8(mask, mask)" ) else() set(INSTRUCTION_SETS "AVX1?-mavx?__m128i value{}#auto result = _mm_extract_epi32(value, 0)" "AVX2?-mavx2?__m256i value{}#auto result = _mm256_add_epi32(__m256i{}, __m256i{})" "AVX512?-mavx512f?int32_t result[16]#const _mm512i& value{}#_mm512_store_si512(result, value)" + "AVX1024??uint8x16_t mask{ 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F }#vandq_u8(mask, mask)" ) endif() @@ -43,7 +50,7 @@ set(AVX_TYPE "AVX0" PARENT_SCOPE) set(AVX_FLAGS "" PARENT_SCOPE) # This is only supported on x86/x64, it is completely skipped and forced to T_fallback anywhere else -if ((${CMAKE_SYSTEM_PROCESSOR} MATCHES "x86_64") OR (${CMAKE_SYSTEM_PROCESSOR} MATCHES "i386") OR (${CMAKE_SYSTEM_PROCESSOR} MATCHES "AMD64")) +if ((${CMAKE_SYSTEM_PROCESSOR} MATCHES "x86_64") OR (${CMAKE_SYSTEM_PROCESSOR} MATCHES "i386") OR (${CMAKE_SYSTEM_PROCESSOR} MATCHES "AMD64") OR (${CMAKE_HOST_SYSTEM_PROCESSOR} MATCHES "arm64") OR (${CMAKE_HOST_SYSTEM_PROCESSOR} MATCHES "armv7l")) foreach(INSTRUCTION_SET IN LISTS INSTRUCTION_SETS) string(REPLACE "?" ";" CURRENT_LIST "${INSTRUCTION_SET}") diff --git a/cmake/FindFilesystem.cmake b/cmake/FindFilesystem.cmake new file mode 100644 index 0000000000..a152e52293 --- /dev/null +++ b/cmake/FindFilesystem.cmake @@ -0,0 +1,247 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: + +FindFilesystem +############## + +This module supports the C++17 standard library's filesystem utilities. Use the +:imp-target:`std::filesystem` imported target to + +Options +******* + +The ``COMPONENTS`` argument to this module supports the following values: + +.. find-component:: Experimental + :name: fs.Experimental + + Allows the module to find the "experimental" Filesystem TS version of the + Filesystem library. This is the library that should be used with the + ``std::experimental::filesystem`` namespace. + +.. find-component:: Final + :name: fs.Final + + Finds the final C++17 standard version of the filesystem library. + +If no components are provided, behaves as if the +:find-component:`fs.Final` component was specified. + +If both :find-component:`fs.Experimental` and :find-component:`fs.Final` are +provided, first looks for ``Final``, and falls back to ``Experimental`` in case +of failure. If ``Final`` is found, :imp-target:`std::filesystem` and all +:ref:`variables ` will refer to the ``Final`` version. + + +Imported Targets +**************** + +.. imp-target:: std::filesystem + + The ``std::filesystem`` imported target is defined when any requested + version of the C++ filesystem library has been found, whether it is + *Experimental* or *Final*. + + If no version of the filesystem library is available, this target will not + be defined. + + .. note:: + This target has ``cxx_std_17`` as an ``INTERFACE`` + :ref:`compile language standard feature `. Linking + to this target will automatically enable C++17 if no later standard + version is already required on the linking target. + + +.. _fs.variables: + +Variables +********* + +.. variable:: CXX_FILESYSTEM_IS_EXPERIMENTAL + + Set to ``TRUE`` when the :find-component:`fs.Experimental` version of C++ + filesystem library was found, otherwise ``FALSE``. + +.. variable:: CXX_FILESYSTEM_HAVE_FS + + Set to ``TRUE`` when a filesystem header was found. + +.. variable:: CXX_FILESYSTEM_HEADER + + Set to either ``filesystem`` or ``experimental/filesystem`` depending on + whether :find-component:`fs.Final` or :find-component:`fs.Experimental` was + found. + +.. variable:: CXX_FILESYSTEM_NAMESPACE + + Set to either ``std::filesystem`` or ``std::experimental::filesystem`` + depending on whether :find-component:`fs.Final` or + :find-component:`fs.Experimental` was found. + + +Examples +******** + +Using `find_package(Filesystem)` with no component arguments: + +.. code-block:: cmake + + find_package(Filesystem REQUIRED) + + add_executable(my-program main.cpp) + target_link_libraries(my-program PRIVATE std::filesystem) + + +#]=======================================================================] + + +if(TARGET std::filesystem) + # This module has already been processed. Don't do it again. + return() +endif() + +cmake_minimum_required(VERSION 3.10) + +include(CMakePushCheckState) +include(CheckIncludeFileCXX) + +# If we're not cross-compiling, try to run test executables. +# Otherwise, assume that compile + link is a sufficient check. +if(CMAKE_CROSSCOMPILING) + include(CheckCXXSourceCompiles) + macro(_cmcm_check_cxx_source code var) + check_cxx_source_compiles("${code}" ${var}) + endmacro() +else() + include(CheckCXXSourceRuns) + macro(_cmcm_check_cxx_source code var) + check_cxx_source_runs("${code}" ${var}) + endmacro() +endif() + +cmake_push_check_state() + +set(CMAKE_REQUIRED_QUIET ${Filesystem_FIND_QUIETLY}) + +# All of our tests required C++17 or later +set(CMAKE_CXX_STANDARD 17) + +# Normalize and check the component list we were given +set(want_components ${Filesystem_FIND_COMPONENTS}) +if(Filesystem_FIND_COMPONENTS STREQUAL "") + set(want_components Final) +endif() + +# Warn on any unrecognized components +set(extra_components ${want_components}) +list(REMOVE_ITEM extra_components Final Experimental) +foreach(component IN LISTS extra_components) + message(WARNING "Extraneous find_package component for Filesystem: ${component}") +endforeach() + +# Detect which of Experimental and Final we should look for +set(find_experimental TRUE) +set(find_final TRUE) +if(NOT "Final" IN_LIST want_components) + set(find_final FALSE) +endif() +if(NOT "Experimental" IN_LIST want_components) + set(find_experimental FALSE) +endif() + +if(find_final) + check_include_file_cxx("filesystem" _CXX_FILESYSTEM_HAVE_HEADER) + mark_as_advanced(_CXX_FILESYSTEM_HAVE_HEADER) + if(_CXX_FILESYSTEM_HAVE_HEADER) + # We found the non-experimental header. Don't bother looking for the + # experimental one. + set(find_experimental FALSE) + endif() +else() + set(_CXX_FILESYSTEM_HAVE_HEADER FALSE) +endif() + +if(find_experimental) + check_include_file_cxx("experimental/filesystem" _CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER) + mark_as_advanced(_CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER) +else() + set(_CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER FALSE) +endif() + +if(_CXX_FILESYSTEM_HAVE_HEADER) + set(_have_fs TRUE) + set(_fs_header filesystem) + set(_fs_namespace std::filesystem) + set(_is_experimental FALSE) +elseif(_CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER) + set(_have_fs TRUE) + set(_fs_header experimental/filesystem) + set(_fs_namespace std::experimental::filesystem) + set(_is_experimental TRUE) +else() + set(_have_fs FALSE) +endif() + +set(CXX_FILESYSTEM_HAVE_FS ${_have_fs} CACHE BOOL "TRUE if we have the C++ filesystem headers") +set(CXX_FILESYSTEM_HEADER ${_fs_header} CACHE STRING "The header that should be included to obtain the filesystem APIs") +set(CXX_FILESYSTEM_NAMESPACE ${_fs_namespace} CACHE STRING "The C++ namespace that contains the filesystem APIs") +set(CXX_FILESYSTEM_IS_EXPERIMENTAL ${_is_experimental} CACHE BOOL "TRUE if the C++ filesystem library is the experimental version") + +set(_found FALSE) + +if(CXX_FILESYSTEM_HAVE_FS) + # We have some filesystem library available. Do link checks + string(CONFIGURE [[ + #include + #include <@CXX_FILESYSTEM_HEADER@> + + int main() { + auto cwd = @CXX_FILESYSTEM_NAMESPACE@::current_path(); + printf("%s", cwd.c_str()); + return EXIT_SUCCESS; + } + ]] code @ONLY) + + # Check a simple filesystem program without any linker flags + _cmcm_check_cxx_source("${code}" CXX_FILESYSTEM_NO_LINK_NEEDED) + + set(can_link ${CXX_FILESYSTEM_NO_LINK_NEEDED}) + + if(NOT CXX_FILESYSTEM_NO_LINK_NEEDED) + set(prev_libraries ${CMAKE_REQUIRED_LIBRARIES}) + # Add the libstdc++ flag + set(CMAKE_REQUIRED_LIBRARIES ${prev_libraries} -lstdc++fs) + _cmcm_check_cxx_source("${code}" CXX_FILESYSTEM_STDCPPFS_NEEDED) + set(can_link ${CXX_FILESYSTEM_STDCPPFS_NEEDED}) + if(NOT CXX_FILESYSTEM_STDCPPFS_NEEDED) + # Try the libc++ flag + set(CMAKE_REQUIRED_LIBRARIES ${prev_libraries} -lc++fs) + _cmcm_check_cxx_source("${code}" CXX_FILESYSTEM_CPPFS_NEEDED) + set(can_link ${CXX_FILESYSTEM_CPPFS_NEEDED}) + endif() + endif() + + if(can_link) + add_library(std::filesystem INTERFACE IMPORTED) + set_property(TARGET std::filesystem APPEND PROPERTY INTERFACE_COMPILE_FEATURES cxx_std_17) + set(_found TRUE) + + if(CXX_FILESYSTEM_NO_LINK_NEEDED) + # Nothing to add... + elseif(CXX_FILESYSTEM_STDCPPFS_NEEDED) + set_property(TARGET std::filesystem APPEND PROPERTY INTERFACE_LINK_LIBRARIES -lstdc++fs) + elseif(CXX_FILESYSTEM_CPPFS_NEEDED) + set_property(TARGET std::filesystem APPEND PROPERTY INTERFACE_LINK_LIBRARIES -lc++fs) + endif() + endif() +endif() + +cmake_pop_check_state() + +set(Filesystem_FOUND ${_found} CACHE BOOL "TRUE if we can run a program using std::filesystem" FORCE) + +if(Filesystem_FIND_REQUIRED AND NOT Filesystem_FOUND) + message(FATAL_ERROR "Cannot run simple program using std::filesystem") +endif() diff --git a/cmake/FindOpus.cmake b/cmake/FindOpus.cmake index ba23166985..a34553f73a 100644 --- a/cmake/FindOpus.cmake +++ b/cmake/FindOpus.cmake @@ -18,7 +18,8 @@ endif() if(OPUS_LIBRARIES) if(OPUS_USE_STATIC_LIBS) - find_library(LIBM NAMES "libm.a" "libm.tbd") + # on linux with glibc you CANT statically link libm without statically linking all of glibc. DONT DO IT. + #find_library(LIBM NAMES "libm.a" "libm.tbd") else() find_library(LIBM NAMES m) endif() diff --git a/cmake/FindSodium.cmake b/cmake/FindSodium.cmake deleted file mode 100644 index fbcc45ad11..0000000000 --- a/cmake/FindSodium.cmake +++ /dev/null @@ -1,293 +0,0 @@ -# Written in 2016 by Henrik Steffen Gaßmann -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see -# -# http://creativecommons.org/publicdomain/zero/1.0/ -# -# ############################################################################## -# Tries to find the local libsodium installation. -# -# On Windows the sodium_DIR environment variable is used as a default hint which -# can be overridden by setting the corresponding cmake variable. -# -# Once done the following variables will be defined: -# -# sodium_FOUND sodium_INCLUDE_DIR sodium_LIBRARY_DEBUG sodium_LIBRARY_RELEASE -# sodium_VERSION_STRING -# -# Furthermore an imported "sodium" target is created. -# - -if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang") - set(_GCC_COMPATIBLE 1) -endif() - -# static library option -if(NOT DEFINED sodium_USE_STATIC_LIBS) - option(sodium_USE_STATIC_LIBS "enable to statically link against sodium" OFF) -endif() -if(NOT (sodium_USE_STATIC_LIBS EQUAL sodium_USE_STATIC_LIBS_LAST)) - unset(sodium_LIBRARY CACHE) - unset(sodium_LIBRARY_DEBUG CACHE) - unset(sodium_LIBRARY_RELEASE CACHE) - unset(sodium_DLL_DEBUG CACHE) - unset(sodium_DLL_RELEASE CACHE) - set(sodium_USE_STATIC_LIBS_LAST - ${sodium_USE_STATIC_LIBS} - CACHE INTERNAL "internal change tracking variable") -endif() - -# ############################################################################## -# UNIX -if(UNIX) - # import pkg-config - find_package(PkgConfig) - if(PKG_CONFIG_FOUND) - pkg_check_modules(sodium_PKG QUIET libsodium) - endif() - - if(sodium_USE_STATIC_LIBS) - if(sodium_PKG_STATIC_LIBRARIES) - foreach(_libname ${sodium_PKG_STATIC_LIBRARIES}) - if(NOT _libname MATCHES "^lib.*\\.a$") # ignore strings already ending - # with .a - list(INSERT sodium_PKG_STATIC_LIBRARIES 0 "lib${_libname}.a") - endif() - endforeach() - list(REMOVE_DUPLICATES sodium_PKG_STATIC_LIBRARIES) - else() - # if pkgconfig for libsodium doesn't provide static lib info, then - # override PKG_STATIC here.. - set(sodium_PKG_STATIC_LIBRARIES libsodium.a) - endif() - - set(XPREFIX sodium_PKG_STATIC) - else() - if(sodium_PKG_LIBRARIES STREQUAL "") - set(sodium_PKG_LIBRARIES sodium) - endif() - - set(XPREFIX sodium_PKG) - endif() - - find_path(sodium_INCLUDE_DIR sodium.h HINTS ${${XPREFIX}_INCLUDE_DIRS}) - find_library(sodium_LIBRARY_DEBUG - NAMES ${${XPREFIX}_LIBRARIES} - HINTS ${${XPREFIX}_LIBRARY_DIRS}) - find_library(sodium_LIBRARY_RELEASE - NAMES ${${XPREFIX}_LIBRARIES} - HINTS ${${XPREFIX}_LIBRARY_DIRS}) - - # ############################################################################ - # Windows -elseif(WIN32) - set(sodium_DIR "$ENV{sodium_DIR}" CACHE FILEPATH "sodium install directory") - mark_as_advanced(sodium_DIR) - - find_path(sodium_INCLUDE_DIR sodium.h - HINTS ${sodium_DIR} - PATH_SUFFIXES include) - - if(MSVC) - # detect target architecture - file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/arch.cpp" [=[ - #if defined _M_IX86 - #error ARCH_VALUE x86_32 - #elif defined _M_X64 - #error ARCH_VALUE x86_64 - #endif - #error ARCH_VALUE unknown - ]=]) - try_compile(_UNUSED_VAR "${CMAKE_CURRENT_BINARY_DIR}" - "${CMAKE_CURRENT_BINARY_DIR}/arch.cpp" - OUTPUT_VARIABLE _COMPILATION_LOG) - string(REGEX - REPLACE ".*ARCH_VALUE ([a-zA-Z0-9_]+).*" - "\\1" - _TARGET_ARCH - "${_COMPILATION_LOG}") - - # construct library path - if(_TARGET_ARCH STREQUAL "x86_32") - string(APPEND _PLATFORM_PATH "Win32") - elseif(_TARGET_ARCH STREQUAL "x86_64") - string(APPEND _PLATFORM_PATH "x64") - else() - message( - FATAL_ERROR - "the ${_TARGET_ARCH} architecture is not supported by Findsodium.cmake." - ) - endif() - string(APPEND _PLATFORM_PATH "/$$CONFIG$$") - - if(MSVC_VERSION LESS 1900) - math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 60") - else() - math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 50") - endif() - string(APPEND _PLATFORM_PATH "/v${_VS_VERSION}") - - if(sodium_USE_STATIC_LIBS) - string(APPEND _PLATFORM_PATH "/static") - else() - string(APPEND _PLATFORM_PATH "/dynamic") - endif() - - string(REPLACE "$$CONFIG$$" - "Debug" - _DEBUG_PATH_SUFFIX - "${_PLATFORM_PATH}") - string(REPLACE "$$CONFIG$$" - "Release" - _RELEASE_PATH_SUFFIX - "${_PLATFORM_PATH}") - - find_library(sodium_LIBRARY_DEBUG libsodium.lib - HINTS ${sodium_DIR} - PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX}) - find_library(sodium_LIBRARY_RELEASE libsodium.lib - HINTS ${sodium_DIR} - PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX}) - if(NOT sodium_USE_STATIC_LIBS) - set(CMAKE_FIND_LIBRARY_SUFFIXES_BCK ${CMAKE_FIND_LIBRARY_SUFFIXES}) - set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll") - find_library(sodium_DLL_DEBUG libsodium - HINTS ${sodium_DIR} - PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX}) - find_library(sodium_DLL_RELEASE libsodium - HINTS ${sodium_DIR} - PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX}) - set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES_BCK}) - endif() - - elseif(_GCC_COMPATIBLE) - if(sodium_USE_STATIC_LIBS) - find_library(sodium_LIBRARY_DEBUG libsodium.a - HINTS ${sodium_DIR} - PATH_SUFFIXES lib) - find_library(sodium_LIBRARY_RELEASE libsodium.a - HINTS ${sodium_DIR} - PATH_SUFFIXES lib) - else() - find_library(sodium_LIBRARY_DEBUG libsodium.dll.a - HINTS ${sodium_DIR} - PATH_SUFFIXES lib) - find_library(sodium_LIBRARY_RELEASE libsodium.dll.a - HINTS ${sodium_DIR} - PATH_SUFFIXES lib) - - file(GLOB _DLL - LIST_DIRECTORIES false - RELATIVE "${sodium_DIR}/bin" - "${sodium_DIR}/bin/libsodium*.dll") - find_library(sodium_DLL_DEBUG ${_DLL} libsodium - HINTS ${sodium_DIR} - PATH_SUFFIXES bin) - find_library(sodium_DLL_RELEASE ${_DLL} libsodium - HINTS ${sodium_DIR} - PATH_SUFFIXES bin) - endif() - else() - message(FATAL_ERROR "this platform is not supported by FindSodium.cmake") - endif() - - # ############################################################################ - # unsupported -else() - message(FATAL_ERROR "this platform is not supported by FindSodium.cmake") -endif() - -# ############################################################################## -# common stuff - -# extract sodium version -if(sodium_INCLUDE_DIR) - set(_VERSION_HEADER "${sodium_INCLUDE_DIR}/sodium/version.h") - if(EXISTS "${_VERSION_HEADER}") - file(READ "${_VERSION_HEADER}" _VERSION_HEADER_CONTENT) - string( - REGEX - REPLACE - ".*#define[ \t]*SODIUM_VERSION_STRING[ \t]*\"([^\n]*)\".*" - "\\1" - sodium_VERSION_STRING - "${_VERSION_HEADER_CONTENT}") - set(sodium_VERSION_STRING "${sodium_VERSION_STRING}") - endif() -endif() - -# communicate results -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(sodium - REQUIRED_VARS - sodium_LIBRARY_RELEASE - sodium_LIBRARY_DEBUG - sodium_INCLUDE_DIR - VERSION_VAR - sodium_VERSION_STRING) - -# mark file paths as advanced -mark_as_advanced(sodium_INCLUDE_DIR) -mark_as_advanced(sodium_LIBRARY_DEBUG) -mark_as_advanced(sodium_LIBRARY_RELEASE) -if(WIN32) - mark_as_advanced(sodium_DLL_DEBUG) - mark_as_advanced(sodium_DLL_RELEASE) -endif() - -# create imported target -if(sodium_USE_STATIC_LIBS) - set(_LIB_TYPE STATIC) -else() - set(_LIB_TYPE SHARED) -endif() -add_library(sodium ${_LIB_TYPE} IMPORTED) - -set_target_properties(sodium - PROPERTIES INTERFACE_INCLUDE_DIRECTORIES - "${sodium_INCLUDE_DIR}" - IMPORTED_LINK_INTERFACE_LANGUAGES - "C") - -if(sodium_USE_STATIC_LIBS) - set_target_properties(sodium - PROPERTIES INTERFACE_COMPILE_DEFINITIONS - "SODIUM_STATIC" - IMPORTED_LOCATION - "${sodium_LIBRARY_RELEASE}" - IMPORTED_LOCATION_DEBUG - "${sodium_LIBRARY_DEBUG}") -else() - if(UNIX) - set_target_properties(sodium - PROPERTIES IMPORTED_LOCATION - "${sodium_LIBRARY_RELEASE}" - IMPORTED_LOCATION_DEBUG - "${sodium_LIBRARY_DEBUG}") - elseif(WIN32) - set_target_properties(sodium - PROPERTIES IMPORTED_IMPLIB - "${sodium_LIBRARY_RELEASE}" - IMPORTED_IMPLIB_DEBUG - "${sodium_LIBRARY_DEBUG}") - if(NOT (sodium_DLL_DEBUG MATCHES ".*-NOTFOUND")) - set_target_properties(sodium - PROPERTIES IMPORTED_LOCATION_DEBUG - "${sodium_DLL_DEBUG}") - endif() - if(NOT (sodium_DLL_RELEASE MATCHES ".*-NOTFOUND")) - set_target_properties(sodium - PROPERTIES IMPORTED_LOCATION_RELWITHDEBINFO - "${sodium_DLL_RELEASE}" - IMPORTED_LOCATION_MINSIZEREL - "${sodium_DLL_RELEASE}" - IMPORTED_LOCATION_RELEASE - "${sodium_DLL_RELEASE}") - endif() - endif() -endif() diff --git a/cmake/LINUXx86ToolChain.cmake b/cmake/LINUXx86ToolChain.cmake index 240eb269aa..e943b2aafd 100644 --- a/cmake/LINUXx86ToolChain.cmake +++ b/cmake/LINUXx86ToolChain.cmake @@ -26,7 +26,7 @@ set(T_AVX_EXITCODE "0" CACHE STRING INTERNAL FORCE) EXECUTE_PROCESS(COMMAND sudo dpkg --add-architecture i386) EXECUTE_PROCESS(COMMAND sudo apt-get update) -EXECUTE_PROCESS(COMMAND sudo apt-get install -qq -y g++-10 gcc-10-multilib glibc-*:i386 libc6-dev-i386 g++-10-multilib zlib1g-dev:i386 libssl-dev:i386 libopus-dev:i386 libsodium-dev:i386) +EXECUTE_PROCESS(COMMAND sudo apt-get install -qq -y g++-10 gcc-10-multilib glibc-*:i386 libc6-dev-i386 g++-10-multilib zlib1g-dev:i386 libssl-dev:i386 libopus-dev:i386) EXECUTE_PROCESS(COMMAND export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig/) set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY") diff --git a/cmake/Win32Toolchain.cmake b/cmake/Win32Toolchain.cmake index 7b1ab655ad..ec20c1ab8a 100644 --- a/cmake/Win32Toolchain.cmake +++ b/cmake/Win32Toolchain.cmake @@ -10,17 +10,12 @@ ADD_DEFINITIONS(/bigobj) link_libraries("${PROJECT_SOURCE_DIR}/win32/32/lib/libssl.lib") link_libraries("${PROJECT_SOURCE_DIR}/win32/32/lib/libcrypto.lib") link_libraries("${PROJECT_SOURCE_DIR}/win32/32/lib/zlib.lib") -link_libraries("${PROJECT_SOURCE_DIR}/win32/32/lib/libsodium.lib") link_libraries("${PROJECT_SOURCE_DIR}/win32/32/lib/opus.lib") set(OPUS_INCLUDE_DIRS "${PROJECT_SOURCE_DIR}/win32/include") set(OPUS_LIBRARIES "${PROJECT_SOURCE_DIR}/win32/32/lib/opus.lib") -set(sodium_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/win32/include") -set(sodium_LIBRARY_DEBUG "${PROJECT_SOURCE_DIR}/win32/32/lib/libsodium.lib") -set(sodium_LIBRARY_RELEASE "${PROJECT_SOURCE_DIR}/win32/32/lib/libsodium.lib") set(HAVE_OPUS_OPUS_H "${PROJECT_SOURCE_DIR}/win32/include/opus/opus.h") set(OPUS_FOUND 1) -SET(sodium_VERSION_STRING "win32 bundled") include_directories("${PROJECT_SOURCE_DIR}/win32/include") diff --git a/docpages/01_frequently_asked_questions.md b/docpages/01_frequently_asked_questions.md index 8c82b46f6d..62336d6ff9 100644 --- a/docpages/01_frequently_asked_questions.md +++ b/docpages/01_frequently_asked_questions.md @@ -68,7 +68,7 @@ All functions within D++ are multi-threaded. You should still avoid doing long o ## Does this library support voice? -Yes! This library supports voice and will automatically enable voice if your system has the libopus and libsodium libraries. When running `cmake` the script will identify if these libraries are found. See the example programs for information on how to send audio. +Yes! This library supports voice and will automatically enable voice if your system has the libopus library. When running `cmake` the script will identify if this library is found. See the example programs for information on how to send audio. ## Does this library support sharding? @@ -94,10 +94,6 @@ No, the library only requires C++17. We have some optional features such as \ref To fix this issue, run `ldconfig`: `sudo ldconfig` as root. Log out if your SSH session and log back in, and the bot should be able to find the library. -## When compiling with voice support, I get an error: "No rule to make target 'sodium_LIBRARY_DEBUG-NOTFOUND', needed by 'libdpp.so'. Stop." - -The libsodium package requires pkg-config, but does not check for it when installed. Install it as root, e.g. `sudo apt install pkg-config`. Rerun cmake, and rebuild the library. - ## When I try to instantiate a dpp::cluster in windows, I get "D++ Debug/Release mismatch" If this happens, ensure you are using the correct precompiled build of the library. Our precompiled binaries are built in two forms, **release mode** and **debug mode** for Visual Studio 2019/2022. These two versions of the library are not cross-compatible due to differences in the debug and release STL (Microsoft standard library implementation). You should not need to build your own copy, but please see the section about \ref buildwindows for more information on how to build your own copy, if needed. diff --git a/docpages/advanced_reference/roadmap.md b/docpages/advanced_reference/roadmap.md index 0914ac759e..0a50a5092a 100644 --- a/docpages/advanced_reference/roadmap.md +++ b/docpages/advanced_reference/roadmap.md @@ -2,6 +2,11 @@ At present our roadmap is: -*Short term (6 months):*: Stabilise coroutine support and release it as stable a feature +## Short term (1 month) +* Implement user apps support -*Long term*: Continue development of the library to implement Discord new features as they add them. Discord does not share their internal roadmap with library developers, so we are informed of these new features shortly before they become public given enough time to implement them. This is our permanent ongoing goal. +## Medium term (6 months) +* Stabilise DAVE E2EE support and release it as stable a feature + +## Long term +* Continue development of the library to implement Discord new features as they add them. Discord does not share their internal roadmap with library developers, so we are informed of these new features shortly before they become public given enough time to implement them. This is our permanent ongoing goal. diff --git a/docpages/advanced_reference/unit_tests.md b/docpages/advanced_reference/unit_tests.md index a4a50c9ee9..03f2e78b8b 100644 --- a/docpages/advanced_reference/unit_tests.md +++ b/docpages/advanced_reference/unit_tests.md @@ -24,6 +24,13 @@ export TEST_USER_ID="826535422381391913" export TEST_EVENT_ID="909928577951203360" ``` +You may also optionally set: +```bash +export TEST_DATA_DIR="/path/to/test/data" +``` +If you wish to have test data (Robot.pcm etc) in a different location than two directories above the unit test program. If you do not specify +this environment variable the default will be used. + Then, after cloning and building DPP, run `cd build && ctest -VV` for unit test cases. If you do not specify the `DPP_UNIT_TEST_TOKEN` environment variable, a subset of the tests will run which do not require discord connectivity. diff --git a/docpages/advanced_reference/voice_model.md b/docpages/advanced_reference/voice_model.md index 10bf1d4419..15864fa0ed 100644 --- a/docpages/advanced_reference/voice_model.md +++ b/docpages/advanced_reference/voice_model.md @@ -1,5 +1,42 @@ \page voice-model Voice Model +# High Level Summary + +Discord's audio system consists of several layers and inter-related systems as shown in the flow chart below. + +At the top level, connecting to a voice server requires the library to request the details of a voice server from Discord via the websocket for the shard where the +server is located. Performing this request will make Discord reply with a websocket URI and an ephemeral token (not the bot token) which are used to establish an +initial connection to this secondary websocket. Every connection to a voice channel creates a separate secondary websocket. + +Once connected to this websocket, the library negotiates which protocols it supports and what encryption schemes to use. If you enabled DAVE (Discord's end-to-end +encryption scheme) this is negotiated first. An MLS (message layer security) group is joined or created. If you did not enable DAVE, this step is bypassed. + +The secondary websocket then gives the library a shared encryption secret and the hostname of an RTP server, which is used to encrypt RTP packets using libssl. +This is stored for later. + +The next step is to send an initial packet to the RTP server so that the library can detect the public IP where the bot is running. Once the RTP server replies, +the bot may tell the websocket what encryption protocols it is going to use to encrypt the RTP packet contents (leaving the RTP header somwhat intact). + +The library is now in an initialised state and will accept method calls for `send_audio_raw()` and `send_audio_opus()`. If you send raw audio, it will first be +encoded as OPUS using libopus, and potentially repacketized to fit into UDP packets, with larger streams being split into multiple smaller packets that are scheduled +to be sent in the future. + +If at this point DAVE is enabled, the contents of the OPUS encoded audio are encrypted using the AES 128 bit AEAD cipher and using the bot's MLS ratchet, derived +during the MLS negotiation which was carried out earlier. All valid participants in the voice channel may use their private key, and the public key derived from +their ratchets, to decrypt the OPUS audio. + +Regardless of if DAVE is enabled or not, the OPUS stream (encrypted by DAVE, or "plaintext") is placed into an RTP packet, and then encrypted using the shared secret +given by the websocket, known only to you and Discord, using the xchacha20 poly1305 cipher. + +The completed packet, potentially with two separate layers of encryption (one with a key only you and Discord know, and one with a key only you and participants in the +voice chat know!), plus opus encoded audio is sent on its way via UDP to the RTP server, where Discord promptly distribute it to all participants in the chat. + +\image html audioframe.svg + +After reading all this, go get a coffee or something, you deserve it! ☕ + +# Flow Diagram + \dot digraph "Example Directory" { graph [ranksep=1]; @@ -109,6 +146,26 @@ digraph "Example Directory" { "HTTP/1.1 101 Switching Protocols" -> "discord_voice_client::handle_frame"; label = "Do the voice stuff."; + + "discord_voice_client::handle_frame"-> "DAVE enabled"; + "discord_voice_client::handle_frame"-> "DAVE disabled"; + "DAVE disabled"->"discord_voice_client::send_audio_*()"; + "discord_voice_client::send_audio_*()" -> "Dave encryption on"; + "discord_voice_client::send_audio_*()" -> "Dave encryption off"; + "Dave encryption on" -> "AES AEAD encryption\nof OPUS stream\nusing ratchet"; + "AES AEAD encryption\nof OPUS stream\nusing ratchet" -> "XChaCha20-Poly1305 encryption"; + "Dave encryption off" -> "XChaCha20-Poly1305 encryption"; + "XChaCha20-Poly1305 encryption" -> "UDP sendto"; + "UDP sendto" -> "Discord RTP server"; + "DAVE enabled" -> "MLS send key package"; + "MLS send key package" -> "MLS receive external sender"; + "MLS receive external sender" -> "MLS proposals"; + "MLS proposals" -> "MLS Welcome"; + "MLS proposals" -> "MLS Commit"; + "MLS Commit" -> "DAVE begin transition"; + "MLS Welcome" -> "DAVE begin transition"; + "DAVE begin transition" -> "Dave execute transition"; + "Dave execute transition" -> "discord_voice_client::send_audio_*()"; } "Your bot" -> "guild::connect_member_voice"; diff --git a/docpages/building/freebsd.md b/docpages/building/freebsd.md index f8ca1a87e1..b6ec50ed99 100644 --- a/docpages/building/freebsd.md +++ b/docpages/building/freebsd.md @@ -12,21 +12,13 @@ pkg install cmake ## 2. Install Voice Dependencies (Optional) -If you wish to use voice support, you'll need to install opus and libsodium: +If you wish to use voice support, you'll need to install opus: -First, you need to install opus. ```bash cd /usr/ports/audio/opus make && make install ``` -Then, you need to install libsodium. - -```bash -cd /usr/ports/security/libsodium -make && make install -``` - ## 3. Build Source Code ```bash diff --git a/docpages/building/openbsd.md b/docpages/building/openbsd.md index a289d75f13..34953cccc9 100644 --- a/docpages/building/openbsd.md +++ b/docpages/building/openbsd.md @@ -15,7 +15,7 @@ pkg_add cmake If you wish to use voice support, you'll need to do the following: ```bash -pkg_add libsodium opus pkgconf +pkg_add opus ``` ## 3. Build Source Code diff --git a/docpages/building/osx.md b/docpages/building/osx.md index b43a99e9e1..059c77fca0 100644 --- a/docpages/building/osx.md +++ b/docpages/building/osx.md @@ -15,10 +15,10 @@ brew install openssl pkgconfig \note Usually, you do not need pkgconfig. However, it seems that it throws errors about openssl without. -For voice support, additional dependencies are required: +For voice support, additional dependency is required: ```bash -brew install libsodium opus +brew install opus ``` ## 3. Build Source Code diff --git a/docpages/example_code/CMakeLists.txt b/docpages/example_code/CMakeLists.txt index 87c7713f2b..57f4fee152 100644 --- a/docpages/example_code/CMakeLists.txt +++ b/docpages/example_code/CMakeLists.txt @@ -33,7 +33,7 @@ project(documentation_tests) string(ASCII 27 Esc) -set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDPP_CORO -std=c++20 -pthread -O0 -fPIC -rdynamic -DFMT_HEADER_ONLY -Wall -Wextra -Wpedantic -Werror -Wno-unused-parameter") +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDPP_CORO -std=c++20 -pthread -O0 -fPIC -rdynamic -DFMT_HEADER_ONLY -Wall -Wextra -Wpedantic -Werror -Wno-unused-parameter -Wno-deprecated-declarations") set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0") file(GLOB example_list ./*.cpp) diff --git a/docpages/example_code/commandhandler.cpp b/docpages/example_code/commandhandler.cpp index ea23e87529..898a1cf63a 100644 --- a/docpages/example_code/commandhandler.cpp +++ b/docpages/example_code/commandhandler.cpp @@ -35,7 +35,7 @@ int main() { /* Command description */ "A test ping command", - /* Guild id (omit for a guild command) */ + /* Guild id (omit for a global command) */ 819556414099554344 ); diff --git a/docpages/example_code/embeds.cpp b/docpages/example_code/embeds.cpp index 594b2a1861..f5e8e51fbd 100644 --- a/docpages/example_code/embeds.cpp +++ b/docpages/example_code/embeds.cpp @@ -2,7 +2,9 @@ int main() { /* Setup the bot */ - dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content); + dpp::cluster bot("token"); + + bot.on_log(dpp::utility::cout_logger()); /* The event is fired when someone issues your commands */ bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) { diff --git a/docpages/example_programs/using_coroutines/coro_introduction.md b/docpages/example_programs/using_coroutines/coro_introduction.md index e91a4a271e..0e51c1b3cb 100644 --- a/docpages/example_programs/using_coroutines/coro_introduction.md +++ b/docpages/example_programs/using_coroutines/coro_introduction.md @@ -18,3 +18,5 @@ When using a `co_*` function such as `co_message_create`, the request is sent im You may hear that coroutines are "writing async code as if it was sync", while this is sort of correct, it may limit your understanding and especially the dangers of coroutines. I find **they are best thought of as a shortcut for a state machine**, if you've ever written one, you know what this means. Think of the lambda as *its constructor*, in which captures are variable parameters. Think of the parameters passed to your lambda as data members in your state machine. When you `co_await` something, the state machine's function exits, the program goes back to the caller, at this point the calling function may return. References are kept as references in the state machine, which means by the time the state machine is resumed, the reference may be dangling: \ref lambdas-and-locals "this is not good"! Another way to think of them is just like callbacks but keeping the current scope intact. In fact this is exactly what it is, the co_* functions call the normal API calls, with a callback that resumes the coroutine, *in the callback thread*. This means you cannot rely on thread_local variables and need to keep in mind concurrency issues with global states, as your coroutine will be resumed in another thread than the one it started on. + +It is also worth noting that coroutines can lead to cases where errors may fall through and remain uncaptured, requiring the user to manually handle the error. These exceptions occur because callbacks cannot be used in their typical manner, which can be observed [in the following issue](https://github.com/brainboxdotcc/DPP/issues/1222) and the \ref coro-simple-commands "sample coroutine code". diff --git a/docpages/images/audioframe.svg b/docpages/images/audioframe.svg new file mode 100755 index 0000000000..b17e104c49 --- /dev/null +++ b/docpages/images/audioframe.svg @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + UDP PACKET + RTP FRAME + RTP HEADER + + OPUS AUDIO(44khz 16 bit signed stereo) + + + + + DAVE OPUS AUDIO (AES 128 GCM AEAD) + RTP CONTENT (CHACHA20 POLY1305 AEAD) + 8...65535 BYTES + 1..380 FRAMES PER UDP PACKET + 76 BYTES + DISCORD AUDIO FRAME LAYOUT + + diff --git a/docpages/include/coro_warn.dox b/docpages/include/coro_warn.dox index 2bf9097b71..5710006205 100644 --- a/docpages/include/coro_warn.dox +++ b/docpages/include/coro_warn.dox @@ -1 +1 @@ -\warning D++ Coroutines are a very new feature and are currently only supported by D++ on g++ 13, clang/LLVM 14, and MSVC 19.37 or above. Additionally, D++ must be built with the CMake option DPP_CORO, and your program must both define the macro DPP_CORO and use C++20 or above. The feature is experimental and may have bugs or even crashes, please report any to GitHub Issues or to our Discord Server. +\warning D++ Coroutines are a very new feature and are currently only supported by D++ on g++ 13, clang/LLVM 14, and MSVC 19.37 or above. Additionally, D++ must be built with the CMake option DPP_CORO, and your program must both define the macro DPP_CORO and use C++20 or above. diff --git a/docpages/install/install-windows-vs-zip.md b/docpages/install/install-windows-vs-zip.md index 19d7fc3917..068a9c58ff 100644 --- a/docpages/install/install-windows-vs-zip.md +++ b/docpages/install/install-windows-vs-zip.md @@ -52,7 +52,7 @@ To add D++ to a Visual Studio project, using **Visual Studio 2019** or **Visual \note A much easier way of getting a bot running is available \ref build-a-discord-bot-windows-visual-studio "here"! We recommend using that one if this process seems too complex or you run into too many issues. -- If you get an error that a DLL is missing (e.g. `dpp.dll` or `opus.dll`) when starting your bot, then simply copy all DLLs from the **bin** directory of where you extracted the D++ zip file to, into the same directory where your bot's executable is. You only need to do this once. There should be several of these DLL files: `dpp.dll`, `zlib.dll`, `openssl.dll` and `libcrypto.dll` (or similarly named SSL related files), `libsodium.dll` and `opus.dll`. +- If you get an error that a DLL is missing (e.g. `dpp.dll` or `opus.dll`) when starting your bot, then simply copy all DLLs from the **bin** directory of where you extracted the D++ zip file to, into the same directory where your bot's executable is. You only need to do this once. There should be several of these DLL files: `dpp.dll`, `zlib.dll`, `openssl.dll` and `libcrypto.dll` (or similarly named SSL related files), and `opus.dll`. - Please note that if you change the architecture (step 13) you must reconfigure all of steps 7 through 12 again as these configurations are specific to each architecture. This is to allow for different sets of precompiled libs, e.g. for `x86`, `x64`, etc. - If you get an error that says "Debug/Release mismatch", **you are using the wrong configuration of the D++ dll**. Your bot's executable and the dpp.dll file should both be built in either Release or Debug, you get this error if they are different. We recommend using \ref build-a-discord-bot-windows-visual-studio "the bot template", it has all those things already set up. - You should run your bot from a command prompt. If you do not, and it exits, you will not be able to see any output as the window will immediately close. diff --git a/docpages/make_a_bot/cmake.md b/docpages/make_a_bot/cmake.md index ca215d66a2..c96d8275d0 100644 --- a/docpages/make_a_bot/cmake.md +++ b/docpages/make_a_bot/cmake.md @@ -120,7 +120,7 @@ find_package_handle_standard_args(DPP DEFAULT_MSG DPP_LIBRARIES DPP_INCLUDE_DIR) ## 4. Build the bot. -Now that we have our all our cmake stuff setup and we've got our code in place, we can initalise CMake. You'll want to go inside the `build/` directory and do `cmake ..`. +Now that we have our all our cmake stuff setup and we've got our code in place, we can initalise CMake. You'll want to go inside the `build/` directory and do `cmake ..`. Once that's completed, you'll want to head back to your up-most folder (where all the folders are for your bot) and run `cmake --build build/ -j4` (replace -j4 with however many threads you want to use). This will start compiling your bot and creating the executable. diff --git a/docpages/make_a_bot/windows_vs.md b/docpages/make_a_bot/windows_vs.md index 9755efde9b..3376945fe1 100644 --- a/docpages/make_a_bot/windows_vs.md +++ b/docpages/make_a_bot/windows_vs.md @@ -35,6 +35,6 @@ referenced in function "public: class dpp::task __cdecl `int __cdecl main( 1>...\windows-bot-template-main\x64\Debug\MyBot.exe : fatal error LNK1120: 1 unresolved externals 1>Done building project "MyBot.vcxproj" -- FAILED. \endcode Make sure your don't have another version of the library installed through vcpkg. The template uses a slightly different version of D++ that has coroutines, while the vcpkg version does not, and the latter overwrites it! Uninstalling the library through vcpkg should fix this issue. -- If you get an error that a DLL is missing (e.g. `dpp.dll` or `opus.dll`) when starting your bot, then simply copy all DLLs from the **bin** directory of where you cloned the D++ repository to, into the same directory where your bot's executable is. You only need to do this once. There should be several of these DLL files: `dpp.dll`, `zlib.dll`, `openssl.dll` and `libcrypto.dll` (or similarly named SSL related files), `libsodium.dll` and `opus.dll`. Note the template project does this for you, so you should never encounter this issue. +- If you get an error that a DLL is missing (e.g. `dpp.dll` or `opus.dll`) when starting your bot, then simply copy all DLLs from the **bin** directory of where you cloned the D++ repository to, into the same directory where your bot's executable is. You only need to do this once. There should be several of these DLL files: `dpp.dll`, `zlib.dll`, `openssl.dll` and `libcrypto.dll` (or similarly named SSL related files), and `opus.dll`. Note the template project does this for you, so you should never encounter this issue. - If you get an error that says "Debug/Release mismatch", **you are using the wrong configuration of the D++ dll**. Your bot's executable and the dpp.dll file should both be built in the same configuration (Release or Debug), you get this error if they are different. **This also means you altered the template in a significant way,** we recommend you undo your modifications or reinstall the template. - Stuck? You can find us on the [official Discord server](https://discord.gg/dpp) - ask away! We don't bite! diff --git a/docpages/make_a_bot/windows_wsl.md b/docpages/make_a_bot/windows_wsl.md index 455073bb20..e09ea7400e 100644 --- a/docpages/make_a_bot/windows_wsl.md +++ b/docpages/make_a_bot/windows_wsl.md @@ -8,7 +8,7 @@ This tutorial teaches you how to create a lightweight environment for D++ develo 2. Now open PowerShell as Administrator and type `wsl` to start up your subsystem. You may also type `ubuntu` into your search bar and open it that way. 3. Head on over to your home directory using `cd ~`. 4. Download the latest build for your distro using `wget [url here]`. In this guide we will use the latest build for 64 bit Ubuntu: `wget -O libdpp.deb https://dl.dpp.dev/latest`. -5. Finally install all required dependencies and the library itself using `sudo apt-get install libopus0 libopus-dev libsodium-dev && sudo dpkg -i libdpp.deb && rm libdpp.deb`. +5. Finally install all required dependencies and the library itself using `sudo apt-get install libopus0 libopus-dev && sudo dpkg -i libdpp.deb && rm libdpp.deb`. 6. Congratulations, you've successfully installed all dependencies! Now comes the real fun: Setting up the environment! For this tutorial we'll use a as small as possible setup, so you might create a more advanced one for production bots. 7. Create a new directory, inside your home directory, using `mkdir MyBot`. Then, you want to open that directory using `cd MyBot`. 8. Now that you've a directory to work in, type `touch mybot.cxx` to create a file you can work in! diff --git a/doxygen-awesome-css b/doxygen-awesome-css index 28ed396de1..af1d9030b3 160000 --- a/doxygen-awesome-css +++ b/doxygen-awesome-css @@ -1 +1 @@ -Subproject commit 28ed396de19cd3d803bcb483dceefdb6d03b1b2b +Subproject commit af1d9030b3ffa7b483fa9997a7272fb12af6af4c diff --git a/include/dpp/appcommand.h b/include/dpp/appcommand.h index 184978954e..53e52aed1c 100644 --- a/include/dpp/appcommand.h +++ b/include/dpp/appcommand.h @@ -1640,4 +1640,4 @@ typedef std::unordered_map slashcommand_map; */ typedef std::unordered_map guild_command_permissions_map; -} // namespace dpp +} diff --git a/include/dpp/application.h b/include/dpp/application.h index a68c6c5c9b..d86d4f08cd 100644 --- a/include/dpp/application.h +++ b/include/dpp/application.h @@ -335,6 +335,11 @@ class DPP_EXPORT application : public managed, public json_interface application_map; -} // namespace dpp +} diff --git a/include/dpp/auditlog.h b/include/dpp/auditlog.h index 90339ec331..5af57abdb7 100644 --- a/include/dpp/auditlog.h +++ b/include/dpp/auditlog.h @@ -478,4 +478,4 @@ class DPP_EXPORT auditlog : public json_interface { virtual ~auditlog() = default; }; -} // namespace dpp +} diff --git a/include/dpp/automod.h b/include/dpp/automod.h index f01c81d821..05c194f47f 100644 --- a/include/dpp/automod.h +++ b/include/dpp/automod.h @@ -400,4 +400,4 @@ class DPP_EXPORT automod_rule : public managed, public json_interface automod_rule_map; -} // namespace dpp +} diff --git a/include/dpp/ban.h b/include/dpp/ban.h index 6a02352ed1..039aa90e31 100644 --- a/include/dpp/ban.h +++ b/include/dpp/ban.h @@ -66,4 +66,4 @@ class DPP_EXPORT ban : public json_interface { */ typedef std::unordered_map ban_map; -} // namespace dpp +} diff --git a/include/dpp/bignum.h b/include/dpp/bignum.h index 2966d7b8b0..6d13f8f472 100644 --- a/include/dpp/bignum.h +++ b/include/dpp/bignum.h @@ -98,4 +98,4 @@ class DPP_EXPORT bignumber { [[nodiscard]] std::vector get_binary() const; }; -} // namespace dpp +} diff --git a/include/dpp/cache.h b/include/dpp/cache.h index cdfa3788f8..f4bb884e98 100644 --- a/include/dpp/cache.h +++ b/include/dpp/cache.h @@ -270,5 +270,5 @@ cache_decl(role, find_role, get_role_cache, get_role_count); cache_decl(channel, find_channel, get_channel_cache, get_channel_count); cache_decl(emoji, find_emoji, get_emoji_cache, get_emoji_count); -} // namespace dpp +} diff --git a/include/dpp/channel.h b/include/dpp/channel.h index 6b97b9e23a..4550bfc247 100644 --- a/include/dpp/channel.h +++ b/include/dpp/channel.h @@ -878,5 +878,5 @@ void to_json(nlohmann::json& j, const permission_overwrite& po); */ typedef std::unordered_map channel_map; -} // namespace dpp +} diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index 7a5ebfd63b..9c283e4a72 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -133,6 +133,38 @@ class DPP_EXPORT cluster { */ timer_next_t next_timer; + /** + * @brief Mutex to work with named_commands and synchronize read write access + */ + std::shared_mutex named_commands_mutex; + + /** + * @brief Typedef for slashcommand handler type + */ + using slashcommand_handler_t = std::function; + +#ifdef DPP_CORO + /** + * @brief Typedef for coroutines based slashcommand handler type + */ + using co_slashcommand_handler_t = std::function(const slashcommand_t&)>; + + /** + * @brief Typedef for variant of coroutines based slashcommand handler type and regular version of it + */ + using slashcommand_handler_variant = std::variant; + + /** + * @brief Container to store relation between command name and it's handler + */ + std::map named_commands; +#else + /** + * @brief Container to store relation between command name and it's handler + */ + std::map named_commands; +#endif + /** * @brief Tick active timers */ @@ -436,6 +468,49 @@ class DPP_EXPORT cluster { /* Functions for attaching to event handlers */ + /** + * @brief Register a slash command handler. + * + * @param name The name of the slash command to register + * @param handler A handler function of type `slashcommand_handler_t` + * + * @return bool Returns `true` if the command was registered successfully, or `false` if + * the command with the same name already exists + */ + bool register_command(const std::string& name, const slashcommand_handler_t handler); + +#ifdef DPP_CORO + /** + * @brief Register a coroutine-based slash command handler. + * + * @param name The name of the slash command to register. + * @param handler A coroutine handler function of type `co_slashcommand_handler_t`. + * + * @return bool Returns `true` if the command was registered successfully, or `false` if + * the command with the same name already exists. + */ + template + std::enable_if_t, dpp::task>, bool> + register_command(const std::string& name, F&& handler){ + std::unique_lock lk(named_commands_mutex); + auto [_, inserted] = named_commands.try_emplace(name, std::forward(handler)); + return inserted; + }; +#endif + + /** + * @brief Unregister a slash command. + * + * This function unregisters (removes) a previously registered slash command by name. + * If the command is successfully removed, it returns `true`. + * + * @param name The name of the slash command to unregister. + * + * @return bool Returns `true` if the command was successfully unregistered, or `false` + * if the command was not found. + */ + bool unregister_command(const std::string& name); + /** * @brief on voice state update event * @@ -445,7 +520,16 @@ class DPP_EXPORT cluster { */ event_router_t on_voice_state_update; - + /** + * @brief on voice client platform event + * After a client connects, or on joining a vc, you will receive the platform type of each client. This is either desktop + * or mobile. + * + * @note Use operator() to attach a lambda to this event, and the detach method to detach the listener using the returned ID. + * The function signature for this event takes a single `const` reference of type voice_client_disconnect_t&, and returns void. + */ + event_router_t on_voice_client_platform; + /** * @brief on voice client disconnect event * @@ -1237,7 +1321,7 @@ class DPP_EXPORT cluster { /** * @brief Called when packets are sent from the voice buffer. * The voice buffer contains packets that are already encoded with Opus and encrypted - * with Sodium, and merged into packets by the repacketizer, which is done in the + * with XChaCha20-Poly1305, and merged into packets by the repacketizer, which is done in the * dpp::discord_voice_client::send_audio method. You should use the buffer size properties * of dpp::voice_buffer_send_t to determine if you should fill the buffer with more * content. @@ -1250,17 +1334,6 @@ class DPP_EXPORT cluster { event_router_t on_voice_buffer_send; - /** - * @brief Called when a user is talking on a voice channel. - * - * @warning If the cache policy has disabled guild caching, the pointer to the guild in this event may be nullptr. - * - * @note Use operator() to attach a lambda to this event, and the detach method to detach the listener using the returned ID. - * The function signature for this event takes a single `const` reference of type voice_user_talking_t&, and returns void. - */ - event_router_t on_voice_user_talking; - - /** * @brief Called when a voice channel is connected and ready to send audio. * Note that this is not directly attached to the READY event of the websocket, @@ -3578,7 +3651,7 @@ class DPP_EXPORT cluster { /** * @brief Get all guild stickers - * @see https://discord.com/developers/docs/resources/sticker#get-guild-stickers + * @see https://discord.com/developers/docs/resources/sticker#list-guild-stickers * @param guild_id Guild ID of the guild where the sticker is * @param callback Function to call when the API call completes. * On success the callback will contain a dpp::sticker_map object in confirmation_callback_t::value. On failure, the value is undefined and confirmation_callback_t::is_error() method will return true. You can obtain full error details with confirmation_callback_t::get_error(). @@ -3587,7 +3660,7 @@ class DPP_EXPORT cluster { /** * @brief Get a list of available sticker packs - * @see https://discord.com/developers/docs/resources/sticker#list-nitro-sticker-packs + * @see https://discord.com/developers/docs/resources/sticker#list-sticker-packs * @param callback Function to call when the API call completes. * On success the callback will contain a dpp::sticker_pack_map object in confirmation_callback_t::value. On failure, the value is undefined and confirmation_callback_t::is_error() method will return true. You can obtain full error details with confirmation_callback_t::get_error(). */ @@ -3737,6 +3810,16 @@ class DPP_EXPORT cluster { */ void current_user_set_voice_state(snowflake guild_id, snowflake channel_id, bool suppress = false, time_t request_to_speak_timestamp = 0, command_completion_event_t callback = utility::log_error()); + /** + * @brief Get the bot's voice state in a guild without a Gateway connection + * + * @see https://discord.com/developers/docs/resources/voice#get-current-user-voice-state + * @param guild_id Guild to get the voice state for + * @param callback Function to call when the API call completes. + * On success the callback will contain a dpp::voicestate object in confirmation_callback_t::value. On failure, the value is undefined and confirmation_callback_t::is_error() method will return true. You can obtain full error details with confirmation_callback_t::get_error(). + */ + void current_user_get_voice_state(snowflake guild_id, command_completion_event_t callback); + /** * @brief Set a user's voice state on a stage channel * @@ -3760,6 +3843,17 @@ class DPP_EXPORT cluster { */ void user_set_voice_state(snowflake user_id, snowflake guild_id, snowflake channel_id, bool suppress = false, command_completion_event_t callback = utility::log_error()); + /** + * @brief Get a user's voice state in a guild without a Gateway connection + * + * @see https://discord.com/developers/docs/resources/voice#get-user-voice-state + * @param guild_id Guild to get the voice state for + * @param user_id The user to get the voice state of + * @param callback Function to call when the API call completes. + * On success the callback will contain a dpp::voicestate object in confirmation_callback_t::value. On failure, the value is undefined and confirmation_callback_t::is_error() method will return true. You can obtain full error details with confirmation_callback_t::get_error(). + */ + void user_get_voice_state(snowflake guild_id, snowflake user_id, command_completion_event_t callback); + /** * @brief Get all auto moderation rules for a guild * @@ -3888,4 +3982,4 @@ class DPP_EXPORT cluster { }; -} // namespace dpp +} diff --git a/include/dpp/cluster_coro_calls.h b/include/dpp/cluster_coro_calls.h index 0ea93fc9b6..9077e9efa6 100644 --- a/include/dpp/cluster_coro_calls.h +++ b/include/dpp/cluster_coro_calls.h @@ -2009,7 +2009,7 @@ /** * @brief Get all guild stickers * @see dpp::cluster::guild_stickers_get - * @see https://discord.com/developers/docs/resources/sticker#get-guild-stickers + * @see https://discord.com/developers/docs/resources/sticker#list-guild-stickers * @param guild_id Guild ID of the guild where the sticker is * @return sticker_map returned object on completion * \memberof dpp::cluster @@ -2029,7 +2029,7 @@ /** * @brief Get a list of available sticker packs * @see dpp::cluster::sticker_packs_get - * @see https://discord.com/developers/docs/resources/sticker#list-nitro-sticker-packs + * @see https://discord.com/developers/docs/resources/sticker#list-sticker-packs * @return sticker_pack_map returned object on completion * \memberof dpp::cluster */ @@ -2367,6 +2367,17 @@ */ [[nodiscard]] async co_current_user_set_voice_state(snowflake guild_id, snowflake channel_id, bool suppress = false, time_t request_to_speak_timestamp = 0); +/** + * @brief Get the bot's voice state in a guild without a Gateway connection + * + * @see dpp::cluster::current_user_get_voice_state + * @see https://discord.com/developers/docs/resources/voice#get-current-user-voice-state + * @param guild_id Guild to get the voice state for + * @return voicestate returned object on completion + * \memberof dpp::cluster + */ +[[nodiscard]] async co_current_user_get_voice_state(snowflake guild_id); + /** * @brief Set a user's voice state on a stage channel * @@ -2391,6 +2402,18 @@ */ [[nodiscard]] async co_user_set_voice_state(snowflake user_id, snowflake guild_id, snowflake channel_id, bool suppress = false); +/** + * @brief Get a user's voice state in a guild without a Gateway connection + * + * @see dpp::cluster::user_get_voice_state + * @see https://discord.com/developers/docs/resources/voice#get-user-voice-state + * @param guild_id Guild to get the voice state for + * @param user_id The user to get the voice state of + * @return voicestate returned object on completion + * \memberof dpp::cluster + */ +[[nodiscard]] async co_user_get_voice_state(snowflake guild_id, snowflake user_id); + /** * @brief Get current user's connections (linked accounts, e.g. steam, xbox). * This call requires the oauth2 `connections` scope and cannot be executed diff --git a/include/dpp/cluster_sync_calls.h b/include/dpp/cluster_sync_calls.h index 302e57111a..86ccd5fc02 100644 --- a/include/dpp/cluster_sync_calls.h +++ b/include/dpp/cluster_sync_calls.h @@ -38,10 +38,11 @@ * @return slashcommand_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -slashcommand_map global_bulk_command_create_sync(const std::vector &commands); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand_map global_bulk_command_create_sync(const std::vector &commands); /** * @brief Delete all existing global slash commands. @@ -51,10 +52,11 @@ slashcommand_map global_bulk_command_create_sync(const std::vector * @return slashcommand_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -slashcommand_map global_bulk_command_delete_sync(); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand_map global_bulk_command_delete_sync(); /** * @brief Create a global slash command (a bot can have a maximum of 100 of these). @@ -65,10 +67,11 @@ slashcommand_map global_bulk_command_delete_sync(); * @return slashcommand returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -slashcommand global_command_create_sync(const slashcommand &s); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand global_command_create_sync(const slashcommand &s); /** * @brief Get a global slash command @@ -79,10 +82,11 @@ slashcommand global_command_create_sync(const slashcommand &s); * @return slashcommand returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -slashcommand global_command_get_sync(snowflake id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand global_command_get_sync(snowflake id); /** * @brief Delete a global slash command (a bot can have a maximum of 100 of these) @@ -93,10 +97,11 @@ slashcommand global_command_get_sync(snowflake id); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation global_command_delete_sync(snowflake id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation global_command_delete_sync(snowflake id); /** * @brief Edit a global slash command (a bot can have a maximum of 100 of these) @@ -107,10 +112,11 @@ confirmation global_command_delete_sync(snowflake id); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation global_command_edit_sync(const slashcommand &s); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation global_command_edit_sync(const slashcommand &s); /** * @brief Get the application's global slash commands @@ -120,10 +126,11 @@ confirmation global_command_edit_sync(const slashcommand &s); * @return slashcommand_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -slashcommand_map global_commands_get_sync(); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand_map global_commands_get_sync(); /** * @brief Create/overwrite guild slash commands. @@ -137,10 +144,11 @@ slashcommand_map global_commands_get_sync(); * @return slashcommand_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -slashcommand_map guild_bulk_command_create_sync(const std::vector &commands, snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand_map guild_bulk_command_create_sync(const std::vector &commands, snowflake guild_id); /** * @brief Delete all existing guild slash commands. @@ -151,10 +159,11 @@ slashcommand_map guild_bulk_command_create_sync(const std::vector * @return slashcommand_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -slashcommand_map guild_bulk_command_delete_sync(snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand_map guild_bulk_command_delete_sync(snowflake guild_id); /** * @brief Get all slash command permissions of a guild @@ -165,10 +174,11 @@ slashcommand_map guild_bulk_command_delete_sync(snowflake guild_id); * @return guild_command_permissions_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -guild_command_permissions_map guild_commands_get_permissions_sync(snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_command_permissions_map guild_commands_get_permissions_sync(snowflake guild_id); /** * @brief Edit/Overwrite the permissions of all existing slash commands in a guild @@ -184,10 +194,11 @@ guild_command_permissions_map guild_commands_get_permissions_sync(snowflake guil * @deprecated This has been disabled with updates to Permissions v2. You can use guild_command_edit_permissions instead * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -guild_command_permissions_map guild_bulk_command_edit_permissions_sync(const std::vector &commands, snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_command_permissions_map guild_bulk_command_edit_permissions_sync(const std::vector &commands, snowflake guild_id); /** * @brief Create a slash command local to a guild @@ -200,10 +211,11 @@ guild_command_permissions_map guild_bulk_command_edit_permissions_sync(const std * @return slashcommand returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -slashcommand guild_command_create_sync(const slashcommand &s, snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand guild_command_create_sync(const slashcommand &s, snowflake guild_id); /** * @brief Delete a slash command local to a guild @@ -215,10 +227,11 @@ slashcommand guild_command_create_sync(const slashcommand &s, snowflake guild_id * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation guild_command_delete_sync(snowflake id, snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_command_delete_sync(snowflake id, snowflake guild_id); /** * @brief Edit slash command permissions of a guild @@ -231,10 +244,11 @@ confirmation guild_command_delete_sync(snowflake id, snowflake guild_id); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation guild_command_edit_permissions_sync(const slashcommand &s, snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_command_edit_permissions_sync(const slashcommand &s, snowflake guild_id); /** * @brief Get a slash command of a guild @@ -247,10 +261,11 @@ confirmation guild_command_edit_permissions_sync(const slashcommand &s, snowflak * @return slashcommand returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -slashcommand guild_command_get_sync(snowflake id, snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand guild_command_get_sync(snowflake id, snowflake guild_id); /** * @brief Get the permissions for a slash command of a guild @@ -262,10 +277,11 @@ slashcommand guild_command_get_sync(snowflake id, snowflake guild_id); * @return guild_command_permissions returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -guild_command_permissions guild_command_get_permissions_sync(snowflake id, snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_command_permissions guild_command_get_permissions_sync(snowflake id, snowflake guild_id); /** * @brief Edit a slash command local to a guild @@ -277,10 +293,11 @@ guild_command_permissions guild_command_get_permissions_sync(snowflake id, snowf * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation guild_command_edit_sync(const slashcommand &s, snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_command_edit_sync(const slashcommand &s, snowflake guild_id); /** * @brief Get the application's slash commands for a guild @@ -292,10 +309,11 @@ confirmation guild_command_edit_sync(const slashcommand &s, snowflake guild_id); * @return slashcommand_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -slashcommand_map guild_commands_get_sync(snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") slashcommand_map guild_commands_get_sync(snowflake guild_id); /** * @brief Respond to a slash command @@ -308,10 +326,11 @@ slashcommand_map guild_commands_get_sync(snowflake guild_id); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation interaction_response_create_sync(snowflake interaction_id, const std::string &token, const interaction_response &r); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation interaction_response_create_sync(snowflake interaction_id, const std::string &token, const interaction_response &r); /** * @brief Edit response to a slash command @@ -323,10 +342,11 @@ confirmation interaction_response_create_sync(snowflake interaction_id, const st * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation interaction_response_edit_sync(const std::string &token, const message &m); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation interaction_response_edit_sync(const std::string &token, const message &m); /** * @brief Get the original response to a slash command @@ -337,10 +357,11 @@ confirmation interaction_response_edit_sync(const std::string &token, const mess * @return message returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -message interaction_response_get_original_sync(const std::string &token); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message interaction_response_get_original_sync(const std::string &token); /** * @brief Create a followup message to a slash command @@ -352,10 +373,11 @@ message interaction_response_get_original_sync(const std::string &token); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation interaction_followup_create_sync(const std::string &token, const message &m); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation interaction_followup_create_sync(const std::string &token, const message &m); /** * @brief Edit original followup message to a slash command @@ -368,10 +390,11 @@ confirmation interaction_followup_create_sync(const std::string &token, const me * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation interaction_followup_edit_original_sync(const std::string &token, const message &m); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation interaction_followup_edit_original_sync(const std::string &token, const message &m); /** * @brief Delete the initial interaction response @@ -382,10 +405,11 @@ confirmation interaction_followup_edit_original_sync(const std::string &token, c * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation interaction_followup_delete_sync(const std::string &token); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation interaction_followup_delete_sync(const std::string &token); /** * @brief Edit followup message to a slash command @@ -398,10 +422,11 @@ confirmation interaction_followup_delete_sync(const std::string &token); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation interaction_followup_edit_sync(const std::string &token, const message &m); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation interaction_followup_edit_sync(const std::string &token, const message &m); /** * @brief Get the followup message to a slash command @@ -413,10 +438,11 @@ confirmation interaction_followup_edit_sync(const std::string &token, const mess * @return message returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -message interaction_followup_get_sync(const std::string &token, snowflake message_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message interaction_followup_get_sync(const std::string &token, snowflake message_id); /** * @brief Get the original followup message to a slash command @@ -428,10 +454,11 @@ message interaction_followup_get_sync(const std::string &token, snowflake messag * @return message returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -message interaction_followup_get_original_sync(const std::string &token); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message interaction_followup_get_original_sync(const std::string &token); /** * @brief Get all auto moderation rules for a guild @@ -440,10 +467,11 @@ message interaction_followup_get_original_sync(const std::string &token); * @return automod_rule_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -automod_rule_map automod_rules_get_sync(snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") automod_rule_map automod_rules_get_sync(snowflake guild_id); /** * @brief Get a single auto moderation rule @@ -453,10 +481,11 @@ automod_rule_map automod_rules_get_sync(snowflake guild_id); * @return automod_rule returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -automod_rule automod_rule_get_sync(snowflake guild_id, snowflake rule_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") automod_rule automod_rule_get_sync(snowflake guild_id, snowflake rule_id); /** * @brief Create an auto moderation rule @@ -466,10 +495,11 @@ automod_rule automod_rule_get_sync(snowflake guild_id, snowflake rule_id); * @return automod_rule returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -automod_rule automod_rule_create_sync(snowflake guild_id, const automod_rule& r); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") automod_rule automod_rule_create_sync(snowflake guild_id, const automod_rule& r); /** * @brief Edit an auto moderation rule @@ -479,10 +509,11 @@ automod_rule automod_rule_create_sync(snowflake guild_id, const automod_rule& r) * @return automod_rule returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -automod_rule automod_rule_edit_sync(snowflake guild_id, const automod_rule& r); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") automod_rule automod_rule_edit_sync(snowflake guild_id, const automod_rule& r); /** * @brief Delete an auto moderation rule @@ -492,10 +523,11 @@ automod_rule automod_rule_edit_sync(snowflake guild_id, const automod_rule& r); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation automod_rule_delete_sync(snowflake guild_id, snowflake rule_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation automod_rule_delete_sync(snowflake guild_id, snowflake rule_id); /** * @brief Create a channel @@ -513,10 +545,11 @@ confirmation automod_rule_delete_sync(snowflake guild_id, snowflake rule_id); * @return channel returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -channel channel_create_sync(const class channel &c); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") channel channel_create_sync(const class channel &c); /** * @brief Remove a permission from a channel @@ -528,10 +561,11 @@ channel channel_create_sync(const class channel &c); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation channel_delete_permission_sync(const class channel &c, snowflake overwrite_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_delete_permission_sync(const class channel &c, snowflake overwrite_id); /** * @brief Delete a channel @@ -542,10 +576,11 @@ confirmation channel_delete_permission_sync(const class channel &c, snowflake ov * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation channel_delete_sync(snowflake channel_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_delete_sync(snowflake channel_id); /** * @brief Edit a channel's permissions @@ -561,10 +596,11 @@ confirmation channel_delete_sync(snowflake channel_id); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation channel_edit_permissions_sync(const class channel &c, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_edit_permissions_sync(const class channel &c, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member); /** * @brief Edit a channel's permissions @@ -580,10 +616,11 @@ confirmation channel_edit_permissions_sync(const class channel &c, const snowfla * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation channel_edit_permissions_sync(const snowflake channel_id, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_edit_permissions_sync(const snowflake channel_id, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member); /** * @brief Edit multiple channels positions @@ -598,10 +635,11 @@ confirmation channel_edit_permissions_sync(const snowflake channel_id, const sno * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation channel_edit_positions_sync(const std::vector &c); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_edit_positions_sync(const std::vector &c); /** * @brief Edit a channel @@ -612,10 +650,11 @@ confirmation channel_edit_positions_sync(const std::vector &c); * @return channel returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -channel channel_edit_sync(const class channel &c); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") channel channel_edit_sync(const class channel &c); /** * @brief Follow an announcement (news) channel @@ -626,10 +665,11 @@ channel channel_edit_sync(const class channel &c); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation channel_follow_news_sync(const class channel &c, snowflake target_channel_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_follow_news_sync(const class channel &c, snowflake target_channel_id); /** * @brief Get a channel @@ -640,10 +680,11 @@ confirmation channel_follow_news_sync(const class channel &c, snowflake target_c * @return channel returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -channel channel_get_sync(snowflake c); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") channel channel_get_sync(snowflake c); /** * @brief Create invite for a channel @@ -655,10 +696,11 @@ channel channel_get_sync(snowflake c); * @return invite returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -invite channel_invite_create_sync(const class channel &c, const class invite &i); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") invite channel_invite_create_sync(const class channel &c, const class invite &i); /** * @brief Get invites for a channel @@ -669,10 +711,11 @@ invite channel_invite_create_sync(const class channel &c, const class invite &i) * @return invite_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -invite_map channel_invites_get_sync(const class channel &c); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") invite_map channel_invites_get_sync(const class channel &c); /** * @brief Trigger channel typing indicator @@ -682,10 +725,11 @@ invite_map channel_invites_get_sync(const class channel &c); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation channel_typing_sync(const class channel &c); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_typing_sync(const class channel &c); /** * @brief Trigger channel typing indicator @@ -695,10 +739,11 @@ confirmation channel_typing_sync(const class channel &c); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation channel_typing_sync(snowflake cid); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_typing_sync(snowflake cid); /** * @brief Get all channels for a guild @@ -709,10 +754,11 @@ confirmation channel_typing_sync(snowflake cid); * @return channel_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -channel_map channels_get_sync(snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") channel_map channels_get_sync(snowflake guild_id); /** * @brief Set the status of a voice channel. @@ -724,10 +770,11 @@ channel_map channels_get_sync(snowflake guild_id); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation channel_set_voice_status_sync(snowflake channel_id, const std::string& status); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation channel_set_voice_status_sync(snowflake channel_id, const std::string& status); /** * @brief Create a dm channel @@ -737,10 +784,11 @@ confirmation channel_set_voice_status_sync(snowflake channel_id, const std::stri * @return channel returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -channel create_dm_channel_sync(snowflake user_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") channel create_dm_channel_sync(snowflake user_id); /** * @brief Get current user DM channels @@ -748,10 +796,11 @@ channel create_dm_channel_sync(snowflake user_id); * @return channel_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -channel_map current_user_get_dms_sync(); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") channel_map current_user_get_dms_sync(); /** * @brief Create a direct message, also create the channel for the direct message if needed @@ -765,10 +814,11 @@ channel_map current_user_get_dms_sync(); * @return message returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -message direct_message_create_sync(snowflake user_id, const message &m); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message direct_message_create_sync(snowflake user_id, const message &m); /** * @brief Adds a recipient to a Group DM using their access token @@ -781,10 +831,11 @@ message direct_message_create_sync(snowflake user_id, const message &m); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation gdm_add_sync(snowflake channel_id, snowflake user_id, const std::string &access_token, const std::string &nick); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation gdm_add_sync(snowflake channel_id, snowflake user_id, const std::string &access_token, const std::string &nick); /** * @brief Removes a recipient from a Group DM @@ -795,10 +846,11 @@ confirmation gdm_add_sync(snowflake channel_id, snowflake user_id, const std::st * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation gdm_remove_sync(snowflake channel_id, snowflake user_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation gdm_remove_sync(snowflake channel_id, snowflake user_id); /** * @brief Create single emoji. @@ -812,10 +864,11 @@ confirmation gdm_remove_sync(snowflake channel_id, snowflake user_id); * @return emoji returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -emoji guild_emoji_create_sync(snowflake guild_id, const class emoji& newemoji); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji guild_emoji_create_sync(snowflake guild_id, const class emoji& newemoji); /** * @brief Delete a guild emoji @@ -828,10 +881,11 @@ emoji guild_emoji_create_sync(snowflake guild_id, const class emoji& newemoji); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation guild_emoji_delete_sync(snowflake guild_id, snowflake emoji_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_emoji_delete_sync(snowflake guild_id, snowflake emoji_id); /** * @brief Edit a single emoji. @@ -845,10 +899,11 @@ confirmation guild_emoji_delete_sync(snowflake guild_id, snowflake emoji_id); * @return emoji returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -emoji guild_emoji_edit_sync(snowflake guild_id, const class emoji& newemoji); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji guild_emoji_edit_sync(snowflake guild_id, const class emoji& newemoji); /** * @brief Get a single emoji @@ -860,10 +915,11 @@ emoji guild_emoji_edit_sync(snowflake guild_id, const class emoji& newemoji); * @return emoji returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -emoji guild_emoji_get_sync(snowflake guild_id, snowflake emoji_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji guild_emoji_get_sync(snowflake guild_id, snowflake emoji_id); /** * @brief Get all emojis for a guild @@ -874,10 +930,11 @@ emoji guild_emoji_get_sync(snowflake guild_id, snowflake emoji_id); * @return emoji_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -emoji_map guild_emojis_get_sync(snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji_map guild_emojis_get_sync(snowflake guild_id); /** * @brief List all Application Emojis @@ -887,10 +944,11 @@ emoji_map guild_emojis_get_sync(snowflake guild_id); * @return emoji_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -emoji_map application_emojis_get_sync(); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji_map application_emojis_get_sync(); /** * @brief Get an Application Emoji @@ -901,10 +959,11 @@ emoji_map application_emojis_get_sync(); * @return emoji returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -emoji application_emoji_get_sync(snowflake emoji_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji application_emoji_get_sync(snowflake emoji_id); /** * @brief Create an Application Emoji @@ -915,10 +974,11 @@ emoji application_emoji_get_sync(snowflake emoji_id); * @return emoji returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -emoji application_emoji_create_sync(const class emoji& newemoji); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji application_emoji_create_sync(const class emoji& newemoji); /** * @brief Edit an Application Emoji @@ -929,10 +989,11 @@ emoji application_emoji_create_sync(const class emoji& newemoji); * @return emoji returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -emoji application_emoji_edit_sync(const class emoji& newemoji); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji application_emoji_edit_sync(const class emoji& newemoji); /** * @brief Delete an Application Emoji @@ -943,10 +1004,11 @@ emoji application_emoji_edit_sync(const class emoji& newemoji); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation application_emoji_delete_sync(snowflake emoji_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation application_emoji_delete_sync(snowflake emoji_id); /** * @brief Returns all entitlements for a given app, active and expired. @@ -963,10 +1025,11 @@ confirmation application_emoji_delete_sync(snowflake emoji_id); * @return entitlement_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -entitlement_map entitlements_get_sync(snowflake user_id = 0, const std::vector& sku_ids = {}, snowflake before_id = 0, snowflake after_id = 0, uint8_t limit = 100, snowflake guild_id = 0, bool exclude_ended = false); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") entitlement_map entitlements_get_sync(snowflake user_id = 0, const std::vector& sku_ids = {}, snowflake before_id = 0, snowflake after_id = 0, uint8_t limit = 100, snowflake guild_id = 0, bool exclude_ended = false); /** * @brief Creates a test entitlement to a given SKU for a given guild or user. @@ -980,10 +1043,11 @@ entitlement_map entitlements_get_sync(snowflake user_id = 0, const std::vector &message_ids, snowflake channel_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_bulk_sync(const std::vector &message_ids, snowflake channel_id); /** * @brief Delete a message from a channel. The callback function is called when the message has been edited @@ -1839,10 +1951,11 @@ confirmation message_delete_bulk_sync(const std::vector &message_ids, * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation message_delete_sync(snowflake message_id, snowflake channel_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_sync(snowflake message_id, snowflake channel_id); /** * @brief Delete own reaction from a message. The reaction string must be either an `emojiname:id` or a unicode character. @@ -1854,10 +1967,11 @@ confirmation message_delete_sync(snowflake message_id, snowflake channel_id); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation message_delete_own_reaction_sync(const struct message &m, const std::string &reaction); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_own_reaction_sync(const struct message &m, const std::string &reaction); /** * @brief Delete own reaction from a message by id. The reaction string must be either an `emojiname:id` or a unicode character. @@ -1870,10 +1984,11 @@ confirmation message_delete_own_reaction_sync(const struct message &m, const std * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation message_delete_own_reaction_sync(snowflake message_id, snowflake channel_id, const std::string &reaction); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_own_reaction_sync(snowflake message_id, snowflake channel_id, const std::string &reaction); /** * @brief Delete a user's reaction from a message. The reaction string must be either an `emojiname:id` or a unicode character @@ -1886,10 +2001,11 @@ confirmation message_delete_own_reaction_sync(snowflake message_id, snowflake ch * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation message_delete_reaction_sync(const struct message &m, snowflake user_id, const std::string &reaction); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_reaction_sync(const struct message &m, snowflake user_id, const std::string &reaction); /** * @brief Delete a user's reaction from a message by id. The reaction string must be either an `emojiname:id` or a unicode character @@ -1903,10 +2019,11 @@ confirmation message_delete_reaction_sync(const struct message &m, snowflake use * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation message_delete_reaction_sync(snowflake message_id, snowflake channel_id, snowflake user_id, const std::string &reaction); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_reaction_sync(snowflake message_id, snowflake channel_id, snowflake user_id, const std::string &reaction); /** * @brief Delete all reactions on a message using a particular emoji. The reaction string must be either an `emojiname:id` or a unicode character @@ -1918,10 +2035,11 @@ confirmation message_delete_reaction_sync(snowflake message_id, snowflake channe * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation message_delete_reaction_emoji_sync(const struct message &m, const std::string &reaction); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_reaction_emoji_sync(const struct message &m, const std::string &reaction); /** * @brief Delete all reactions on a message using a particular emoji by id. The reaction string must be either an `emojiname:id` or a unicode character @@ -1934,10 +2052,11 @@ confirmation message_delete_reaction_emoji_sync(const struct message &m, const s * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation message_delete_reaction_emoji_sync(snowflake message_id, snowflake channel_id, const std::string &reaction); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_delete_reaction_emoji_sync(snowflake message_id, snowflake channel_id, const std::string &reaction); /** * @brief Edit a message on a channel. The callback function is called when the message has been edited @@ -1948,10 +2067,11 @@ confirmation message_delete_reaction_emoji_sync(snowflake message_id, snowflake * @return message returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -message message_edit_sync(const struct message &m); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message message_edit_sync(const struct message &m); /** * @brief Edit the flags of a message on a channel. The callback function is called when the message has been edited @@ -1960,10 +2080,11 @@ message message_edit_sync(const struct message &m); * @return message returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -message message_edit_flags_sync(const struct message &m); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message message_edit_flags_sync(const struct message &m); /** * @brief Get a message @@ -1975,10 +2096,11 @@ message message_edit_flags_sync(const struct message &m); * @return message returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -message message_get_sync(snowflake message_id, snowflake channel_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message message_get_sync(snowflake message_id, snowflake channel_id); /** * @brief Get reactions on a message for a particular emoji. The reaction string must be either an `emojiname:id` or a unicode character @@ -1993,10 +2115,11 @@ message message_get_sync(snowflake message_id, snowflake channel_id); * @return user_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -user_map message_get_reactions_sync(const struct message &m, const std::string &reaction, snowflake before, snowflake after, snowflake limit); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") user_map message_get_reactions_sync(const struct message &m, const std::string &reaction, snowflake before, snowflake after, snowflake limit); /** * @brief Get reactions on a message for a particular emoji by id. The reaction string must be either an `emojiname:id` or a unicode character @@ -2012,10 +2135,11 @@ user_map message_get_reactions_sync(const struct message &m, const std::string & * @return emoji_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -emoji_map message_get_reactions_sync(snowflake message_id, snowflake channel_id, const std::string &reaction, snowflake before, snowflake after, snowflake limit); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") emoji_map message_get_reactions_sync(snowflake message_id, snowflake channel_id, const std::string &reaction, snowflake before, snowflake after, snowflake limit); /** * @brief Pin a message @@ -2027,10 +2151,11 @@ emoji_map message_get_reactions_sync(snowflake message_id, snowflake channel_id, * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation message_pin_sync(snowflake channel_id, snowflake message_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_pin_sync(snowflake channel_id, snowflake message_id); /** * @brief Get multiple messages. @@ -2047,10 +2172,11 @@ confirmation message_pin_sync(snowflake channel_id, snowflake message_id); * @return message_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -message_map messages_get_sync(snowflake channel_id, snowflake around, snowflake before, snowflake after, uint64_t limit); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message_map messages_get_sync(snowflake channel_id, snowflake around, snowflake before, snowflake after, uint64_t limit); /** * @brief Unpin a message @@ -2062,10 +2188,11 @@ message_map messages_get_sync(snowflake channel_id, snowflake around, snowflake * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation message_unpin_sync(snowflake channel_id, snowflake message_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation message_unpin_sync(snowflake channel_id, snowflake message_id); /** * @brief Get a list of users that voted for this specific answer. @@ -2079,10 +2206,11 @@ confirmation message_unpin_sync(snowflake channel_id, snowflake message_id); * @see https://discord.com/developers/docs/resources/poll#get-answer-voters * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -user_map poll_get_answer_voters_sync(const message& m, uint32_t answer_id, snowflake after, uint64_t limit); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") user_map poll_get_answer_voters_sync(const message& m, uint32_t answer_id, snowflake after, uint64_t limit); /** * @brief Get a list of users that voted for this specific answer. @@ -2097,10 +2225,11 @@ user_map poll_get_answer_voters_sync(const message& m, uint32_t answer_id, snowf * @see https://discord.com/developers/docs/resources/poll#get-answer-voters * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -user_map poll_get_answer_voters_sync(snowflake message_id, snowflake channel_id, uint32_t answer_id, snowflake after, uint64_t limit); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") user_map poll_get_answer_voters_sync(snowflake message_id, snowflake channel_id, uint32_t answer_id, snowflake after, uint64_t limit); /** * @brief Immediately end a poll. @@ -2111,10 +2240,11 @@ user_map poll_get_answer_voters_sync(snowflake message_id, snowflake channel_id, * @see https://discord.com/developers/docs/resources/poll#end-poll * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -message poll_end_sync(const message &m); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message poll_end_sync(const message &m); /** * @brief Immediately end a poll. @@ -2126,10 +2256,11 @@ message poll_end_sync(const message &m); * @see https://discord.com/developers/docs/resources/poll#end-poll * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -message poll_end_sync(snowflake message_id, snowflake channel_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message poll_end_sync(snowflake message_id, snowflake channel_id); /** * @brief Get a channel's pins @@ -2139,10 +2270,11 @@ message poll_end_sync(snowflake message_id, snowflake channel_id); * @return message_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -message_map channel_pins_get_sync(snowflake channel_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message_map channel_pins_get_sync(snowflake channel_id); /** * @brief Create a role on a guild @@ -2157,10 +2289,11 @@ message_map channel_pins_get_sync(snowflake channel_id); * @return role returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -role role_create_sync(const class role &r); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") role role_create_sync(const class role &r); /** * @brief Delete a role @@ -2175,10 +2308,11 @@ role role_create_sync(const class role &r); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation role_delete_sync(snowflake guild_id, snowflake role_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation role_delete_sync(snowflake guild_id, snowflake role_id); /** * @brief Edit a role on a guild @@ -2192,10 +2326,11 @@ confirmation role_delete_sync(snowflake guild_id, snowflake role_id); * @return role returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -role role_edit_sync(const class role &r); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") role role_edit_sync(const class role &r); /** * @brief Edit multiple role's position in a guild. Returns a list of all roles of the guild on success. @@ -2211,10 +2346,11 @@ role role_edit_sync(const class role &r); * @return role_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -role_map roles_edit_position_sync(snowflake guild_id, const std::vector &roles); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") role_map roles_edit_position_sync(snowflake guild_id, const std::vector &roles); /** * @brief Get a role for a guild @@ -2225,10 +2361,11 @@ role_map roles_edit_position_sync(snowflake guild_id, const std::vector &r * @return role_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -role_map roles_get_sync(snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") role_map roles_get_sync(snowflake guild_id); /** * @brief Get the application's role connection metadata records @@ -2239,10 +2376,11 @@ role_map roles_get_sync(snowflake guild_id); * @return application_role_connection returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -application_role_connection application_role_connection_get_sync(snowflake application_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") application_role_connection application_role_connection_get_sync(snowflake application_id); /** * @brief Update the application's role connection metadata records @@ -2255,10 +2393,11 @@ application_role_connection application_role_connection_get_sync(snowflake appli * @note An application can have a maximum of 5 metadata records. * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -application_role_connection application_role_connection_update_sync(snowflake application_id, const std::vector &connection_metadata); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") application_role_connection application_role_connection_update_sync(snowflake application_id, const std::vector &connection_metadata); /** * @brief Get user application role connection @@ -2269,10 +2408,11 @@ application_role_connection application_role_connection_update_sync(snowflake ap * @return application_role_connection returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -application_role_connection user_application_role_connection_get_sync(snowflake application_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") application_role_connection user_application_role_connection_get_sync(snowflake application_id); /** * @brief Update user application role connection @@ -2284,10 +2424,11 @@ application_role_connection user_application_role_connection_get_sync(snowflake * @return application_role_connection returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -application_role_connection user_application_role_connection_update_sync(snowflake application_id, const application_role_connection &connection); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") application_role_connection user_application_role_connection_update_sync(snowflake application_id, const application_role_connection &connection); /** * @brief Get all scheduled events for a guild @@ -2297,10 +2438,11 @@ application_role_connection user_application_role_connection_update_sync(snowfla * @return scheduled_event_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -scheduled_event_map guild_events_get_sync(snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") scheduled_event_map guild_events_get_sync(snowflake guild_id); /** * @brief Create a scheduled event on a guild @@ -2311,10 +2453,11 @@ scheduled_event_map guild_events_get_sync(snowflake guild_id); * @return scheduled_event returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -scheduled_event guild_event_create_sync(const scheduled_event& event); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") scheduled_event guild_event_create_sync(const scheduled_event& event); /** * @brief Delete a scheduled event from a guild @@ -2326,10 +2469,11 @@ scheduled_event guild_event_create_sync(const scheduled_event& event); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation guild_event_delete_sync(snowflake event_id, snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_event_delete_sync(snowflake event_id, snowflake guild_id); /** * @brief Edit/modify a scheduled event on a guild @@ -2340,10 +2484,11 @@ confirmation guild_event_delete_sync(snowflake event_id, snowflake guild_id); * @return scheduled_event returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -scheduled_event guild_event_edit_sync(const scheduled_event& event); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") scheduled_event guild_event_edit_sync(const scheduled_event& event); /** * @brief Get a scheduled event for a guild @@ -2355,10 +2500,11 @@ scheduled_event guild_event_edit_sync(const scheduled_event& event); * @return scheduled_event returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -scheduled_event guild_event_get_sync(snowflake guild_id, snowflake event_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") scheduled_event guild_event_get_sync(snowflake guild_id, snowflake event_id); /** * @brief Returns all SKUs for a given application. @@ -2370,13 +2516,14 @@ scheduled_event guild_event_get_sync(snowflake guild_id, snowflake event_id); * @return sku_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -sku_map skus_get_sync(); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") sku_map skus_get_sync(); -stage_instance stage_instance_create_sync(const stage_instance& si); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") stage_instance stage_instance_create_sync(const stage_instance& si); /** * @brief Get the stage instance associated with the channel id, if it exists. @@ -2386,13 +2533,14 @@ stage_instance stage_instance_create_sync(const stage_instance& si); * @return stage_instance returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -stage_instance stage_instance_get_sync(const snowflake channel_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") stage_instance stage_instance_get_sync(const snowflake channel_id); -stage_instance stage_instance_edit_sync(const stage_instance& si); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") stage_instance stage_instance_edit_sync(const stage_instance& si); /** * @brief Delete a stage instance. @@ -2403,10 +2551,11 @@ stage_instance stage_instance_edit_sync(const stage_instance& si); * @note This method supports audit log reasons set by the cluster::set_audit_reason() method. * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation stage_instance_delete_sync(const snowflake channel_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation stage_instance_delete_sync(const snowflake channel_id); /** * @brief Create a sticker in a guild @@ -2417,10 +2566,11 @@ confirmation stage_instance_delete_sync(const snowflake channel_id); * @return sticker returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -sticker guild_sticker_create_sync(const sticker &s); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") sticker guild_sticker_create_sync(const sticker &s); /** * @brief Delete a sticker from a guild @@ -2432,10 +2582,11 @@ sticker guild_sticker_create_sync(const sticker &s); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation guild_sticker_delete_sync(snowflake sticker_id, snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_sticker_delete_sync(snowflake sticker_id, snowflake guild_id); /** * @brief Get a guild sticker @@ -2446,10 +2597,11 @@ confirmation guild_sticker_delete_sync(snowflake sticker_id, snowflake guild_id) * @return sticker returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -sticker guild_sticker_get_sync(snowflake id, snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") sticker guild_sticker_get_sync(snowflake id, snowflake guild_id); /** * @brief Modify a sticker in a guild @@ -2460,23 +2612,25 @@ sticker guild_sticker_get_sync(snowflake id, snowflake guild_id); * @return sticker returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -sticker guild_sticker_modify_sync(const sticker &s); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") sticker guild_sticker_modify_sync(const sticker &s); /** * @brief Get all guild stickers * @see dpp::cluster::guild_stickers_get - * @see https://discord.com/developers/docs/resources/sticker#get-guild-stickers + * @see https://discord.com/developers/docs/resources/sticker#list-guild-stickers * @param guild_id Guild ID of the guild where the sticker is * @return sticker_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -sticker_map guild_stickers_get_sync(snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") sticker_map guild_stickers_get_sync(snowflake guild_id); /** * @brief Get a nitro sticker @@ -2486,22 +2640,24 @@ sticker_map guild_stickers_get_sync(snowflake guild_id); * @return sticker returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -sticker nitro_sticker_get_sync(snowflake id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") sticker nitro_sticker_get_sync(snowflake id); /** * @brief Get a list of available sticker packs * @see dpp::cluster::sticker_packs_get - * @see https://discord.com/developers/docs/resources/sticker#list-nitro-sticker-packs + * @see https://discord.com/developers/docs/resources/sticker#list-sticker-packs * @return sticker_pack_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -sticker_pack_map sticker_packs_get_sync(); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") sticker_pack_map sticker_packs_get_sync(); /** * @brief Create a new guild based on a template. @@ -2513,10 +2669,11 @@ sticker_pack_map sticker_packs_get_sync(); * @return guild returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -guild guild_create_from_template_sync(const std::string &code, const std::string &name); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild guild_create_from_template_sync(const std::string &code, const std::string &name); /** * @brief Creates a template for the guild @@ -2529,10 +2686,11 @@ guild guild_create_from_template_sync(const std::string &code, const std::string * @return dtemplate returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -dtemplate guild_template_create_sync(snowflake guild_id, const std::string &name, const std::string &description); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") dtemplate guild_template_create_sync(snowflake guild_id, const std::string &name, const std::string &description); /** * @brief Deletes the template @@ -2544,10 +2702,11 @@ dtemplate guild_template_create_sync(snowflake guild_id, const std::string &name * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation guild_template_delete_sync(snowflake guild_id, const std::string &code); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation guild_template_delete_sync(snowflake guild_id, const std::string &code); /** * @brief Modifies the template's metadata. @@ -2561,10 +2720,11 @@ confirmation guild_template_delete_sync(snowflake guild_id, const std::string &c * @return dtemplate returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -dtemplate guild_template_modify_sync(snowflake guild_id, const std::string &code, const std::string &name, const std::string &description); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") dtemplate guild_template_modify_sync(snowflake guild_id, const std::string &code, const std::string &name, const std::string &description); /** * @brief Get guild templates @@ -2575,10 +2735,11 @@ dtemplate guild_template_modify_sync(snowflake guild_id, const std::string &code * @return dtemplate_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -dtemplate_map guild_templates_get_sync(snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") dtemplate_map guild_templates_get_sync(snowflake guild_id); /** * @brief Syncs the template to the guild's current state. @@ -2590,10 +2751,11 @@ dtemplate_map guild_templates_get_sync(snowflake guild_id); * @return dtemplate returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -dtemplate guild_template_sync_sync(snowflake guild_id, const std::string &code); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") dtemplate guild_template_sync_sync(snowflake guild_id, const std::string &code); /** * @brief Get a template @@ -2603,10 +2765,11 @@ dtemplate guild_template_sync_sync(snowflake guild_id, const std::string &code); * @return dtemplate returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -dtemplate template_get_sync(const std::string &code); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") dtemplate template_get_sync(const std::string &code); /** * @brief Join a thread @@ -2616,10 +2779,11 @@ dtemplate template_get_sync(const std::string &code); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation current_user_join_thread_sync(snowflake thread_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation current_user_join_thread_sync(snowflake thread_id); /** * @brief Leave a thread @@ -2629,10 +2793,11 @@ confirmation current_user_join_thread_sync(snowflake thread_id); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation current_user_leave_thread_sync(snowflake thread_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation current_user_leave_thread_sync(snowflake thread_id); /** * @brief Get all active threads in the guild, including public and private threads. Threads are ordered by their id, in descending order. @@ -2642,10 +2807,11 @@ confirmation current_user_leave_thread_sync(snowflake thread_id); * @return active_threads returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -active_threads threads_get_active_sync(snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") active_threads threads_get_active_sync(snowflake guild_id); /** * @brief Get private archived threads in a channel which current user has joined (Sorted by ID in descending order) @@ -2657,10 +2823,11 @@ active_threads threads_get_active_sync(snowflake guild_id); * @return thread_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -thread_map threads_get_joined_private_archived_sync(snowflake channel_id, snowflake before_id, uint16_t limit); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread_map threads_get_joined_private_archived_sync(snowflake channel_id, snowflake before_id, uint16_t limit); /** * @brief Get private archived threads in a channel (Sorted by archive_timestamp in descending order) @@ -2672,10 +2839,11 @@ thread_map threads_get_joined_private_archived_sync(snowflake channel_id, snowfl * @return thread_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -thread_map threads_get_private_archived_sync(snowflake channel_id, time_t before_timestamp, uint16_t limit); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread_map threads_get_private_archived_sync(snowflake channel_id, time_t before_timestamp, uint16_t limit); /** * @brief Get public archived threads in a channel (Sorted by archive_timestamp in descending order) @@ -2687,10 +2855,11 @@ thread_map threads_get_private_archived_sync(snowflake channel_id, time_t befor * @return thread_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -thread_map threads_get_public_archived_sync(snowflake channel_id, time_t before_timestamp, uint16_t limit); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread_map threads_get_public_archived_sync(snowflake channel_id, time_t before_timestamp, uint16_t limit); /** * @brief Get a thread member @@ -2701,10 +2870,11 @@ thread_map threads_get_public_archived_sync(snowflake channel_id, time_t before_ * @return thread_member returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -thread_member thread_member_get_sync(const snowflake thread_id, const snowflake user_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread_member thread_member_get_sync(const snowflake thread_id, const snowflake user_id); /** * @brief Get members of a thread @@ -2714,10 +2884,11 @@ thread_member thread_member_get_sync(const snowflake thread_id, const snowflake * @return thread_member_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -thread_member_map thread_members_get_sync(snowflake thread_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread_member_map thread_members_get_sync(snowflake thread_id); /** * @brief Create a thread in a forum or media channel @@ -2734,10 +2905,11 @@ thread_member_map thread_members_get_sync(snowflake thread_id); * @return thread returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -thread thread_create_in_forum_sync(const std::string& thread_name, snowflake channel_id, const message& msg, auto_archive_duration_t auto_archive_duration, uint16_t rate_limit_per_user, std::vector applied_tags = {}); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread thread_create_in_forum_sync(const std::string& thread_name, snowflake channel_id, const message& msg, auto_archive_duration_t auto_archive_duration, uint16_t rate_limit_per_user, std::vector applied_tags = {}); /** * @brief Create a thread @@ -2754,10 +2926,11 @@ thread thread_create_in_forum_sync(const std::string& thread_name, snowflake cha * @return thread returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -thread thread_create_sync(const std::string& thread_name, snowflake channel_id, uint16_t auto_archive_duration, channel_type thread_type, bool invitable, uint16_t rate_limit_per_user); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread thread_create_sync(const std::string& thread_name, snowflake channel_id, uint16_t auto_archive_duration, channel_type thread_type, bool invitable, uint16_t rate_limit_per_user); /** * @brief Edit a thread @@ -2769,10 +2942,11 @@ thread thread_create_sync(const std::string& thread_name, snowflake channel_id, * @return thread returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -thread thread_edit_sync(const thread &t); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread thread_edit_sync(const thread &t); /** * @brief Create a thread with a message (Discord: ID of a thread is same as message ID) @@ -2787,10 +2961,11 @@ thread thread_edit_sync(const thread &t); * @return thread returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -thread thread_create_with_message_sync(const std::string& thread_name, snowflake channel_id, snowflake message_id, uint16_t auto_archive_duration, uint16_t rate_limit_per_user); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread thread_create_with_message_sync(const std::string& thread_name, snowflake channel_id, snowflake message_id, uint16_t auto_archive_duration, uint16_t rate_limit_per_user); /** * @brief Add a member to a thread @@ -2801,10 +2976,11 @@ thread thread_create_with_message_sync(const std::string& thread_name, snowflake * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation thread_member_add_sync(snowflake thread_id, snowflake user_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation thread_member_add_sync(snowflake thread_id, snowflake user_id); /** * @brief Remove a member from a thread @@ -2815,10 +2991,11 @@ confirmation thread_member_add_sync(snowflake thread_id, snowflake user_id); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation thread_member_remove_sync(snowflake thread_id, snowflake user_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation thread_member_remove_sync(snowflake thread_id, snowflake user_id); /** * @brief Get the thread specified by thread_id. This uses the same call as dpp::cluster::channel_get but returns a thread object. @@ -2828,10 +3005,11 @@ confirmation thread_member_remove_sync(snowflake thread_id, snowflake user_id); * @return thread returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -thread thread_get_sync(snowflake thread_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") thread thread_get_sync(snowflake thread_id); /** * @brief Edit current (bot) user. @@ -2852,10 +3030,11 @@ thread thread_get_sync(snowflake thread_id); * @throw dpp::length_exception Image data is larger than the maximum size of 256 kilobytes * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -user current_user_edit_sync(const std::string &nickname, const std::string& avatar_blob = "", const image_type avatar_type = i_png, const std::string& banner_blob = "", const image_type banner_type = i_png); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") user current_user_edit_sync(const std::string &nickname, const std::string& avatar_blob = "", const image_type avatar_type = i_png, const std::string& banner_blob = "", const image_type banner_type = i_png); /** * @brief Get current (bot) application @@ -2865,10 +3044,11 @@ user current_user_edit_sync(const std::string &nickname, const std::string& avat * @return application returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -application current_application_get_sync(); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") application current_application_get_sync(); /** * @brief Get current (bot) user @@ -2880,10 +3060,11 @@ application current_application_get_sync(); * If you do not have these scopes, these fields are empty. You can safely convert a user_identified to user with `dynamic_cast`. * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -user_identified current_user_get_sync(); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") user_identified current_user_get_sync(); /** * @brief Set the bot's voice state on a stage channel @@ -2908,10 +3089,26 @@ user_identified current_user_get_sync(); * @throw std::logic_exception You attempted to set a request_to_speak_timestamp in the past which is not the value of 0. * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation current_user_set_voice_state_sync(snowflake guild_id, snowflake channel_id, bool suppress = false, time_t request_to_speak_timestamp = 0); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation current_user_set_voice_state_sync(snowflake guild_id, snowflake channel_id, bool suppress = false, time_t request_to_speak_timestamp = 0); + +/** + * @brief Get the bot's voice state in a guild without a Gateway connection + * + * @see dpp::cluster::current_user_get_voice_state + * @see https://discord.com/developers/docs/resources/voice#get-current-user-voice-state + * @param guild_id Guild to get the voice state for + * @return voicestate returned object on completion + * \memberof dpp::cluster + * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. + * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. + * Avoid direct use of this function inside an event handler. + */ +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") voicestate current_user_get_voice_state_sync(snowflake guild_id); /** * @brief Set a user's voice state on a stage channel @@ -2935,10 +3132,27 @@ confirmation current_user_set_voice_state_sync(snowflake guild_id, snowflake cha * @param suppress True if the user's audio should be suppressed, false if it should not * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. + * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. + * Avoid direct use of this function inside an event handler. + */ +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation user_set_voice_state_sync(snowflake user_id, snowflake guild_id, snowflake channel_id, bool suppress = false); + +/** + * @brief Get a user's voice state in a guild without a Gateway connection + * + * @see dpp::cluster::user_get_voice_state + * @see https://discord.com/developers/docs/resources/voice#get-user-voice-state + * @param guild_id Guild to get the voice state for + * @param user_id The user to get the voice state of + * @return voicestate returned object on completion + * \memberof dpp::cluster + * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation user_set_voice_state_sync(snowflake user_id, snowflake guild_id, snowflake channel_id, bool suppress = false); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") voicestate user_get_voice_state_sync(snowflake guild_id, snowflake user_id); /** * @brief Get current user's connections (linked accounts, e.g. steam, xbox). @@ -2949,10 +3163,11 @@ confirmation user_set_voice_state_sync(snowflake user_id, snowflake guild_id, sn * @return connection_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -connection_map current_user_connections_get_sync(); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") connection_map current_user_connections_get_sync(); /** * @brief Get current (bot) user guilds @@ -2961,10 +3176,11 @@ connection_map current_user_connections_get_sync(); * @return guild_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -guild_map current_user_get_guilds_sync(); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") guild_map current_user_get_guilds_sync(); /** * @brief Leave a guild @@ -2974,10 +3190,11 @@ guild_map current_user_get_guilds_sync(); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation current_user_leave_guild_sync(snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation current_user_leave_guild_sync(snowflake guild_id); /** * @brief Get a user by id, without using the cache @@ -2992,10 +3209,11 @@ confirmation current_user_leave_guild_sync(snowflake guild_id); * Call `dpp::find_user` instead that looks up the user in the cache rather than a REST call. * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -user_identified user_get_sync(snowflake user_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") user_identified user_get_sync(snowflake user_id); /** * @brief Get a user by id, checking in the cache first @@ -3010,10 +3228,11 @@ user_identified user_get_sync(snowflake user_id); * where you want to for example resolve a user who may no longer be in the bot's guilds, for something like a ban log message. * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -user_identified user_get_cached_sync(snowflake user_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") user_identified user_get_cached_sync(snowflake user_id); /** * @brief Get all voice regions @@ -3022,10 +3241,11 @@ user_identified user_get_cached_sync(snowflake user_id); * @return voiceregion_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -voiceregion_map get_voice_regions_sync(); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") voiceregion_map get_voice_regions_sync(); /** * @brief Get guild voice regions. @@ -3040,13 +3260,14 @@ voiceregion_map get_voice_regions_sync(); * @return voiceregion_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -voiceregion_map guild_get_voice_regions_sync(snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") voiceregion_map guild_get_voice_regions_sync(snowflake guild_id); -webhook create_webhook_sync(const class webhook &wh); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") webhook create_webhook_sync(const class webhook &wh); /** * @brief Delete a webhook @@ -3057,10 +3278,11 @@ webhook create_webhook_sync(const class webhook &wh); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation delete_webhook_sync(snowflake webhook_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation delete_webhook_sync(snowflake webhook_id); /** * @brief Delete webhook message @@ -3073,10 +3295,11 @@ confirmation delete_webhook_sync(snowflake webhook_id); * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation delete_webhook_message_sync(const class webhook &wh, snowflake message_id, snowflake thread_id = 0); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation delete_webhook_message_sync(const class webhook &wh, snowflake message_id, snowflake thread_id = 0); /** * @brief Delete webhook with token @@ -3087,10 +3310,11 @@ confirmation delete_webhook_message_sync(const class webhook &wh, snowflake mess * @return confirmation returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -confirmation delete_webhook_with_token_sync(snowflake webhook_id, const std::string &token); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") confirmation delete_webhook_with_token_sync(snowflake webhook_id, const std::string &token); /** * @brief Edit webhook @@ -3101,10 +3325,11 @@ confirmation delete_webhook_with_token_sync(snowflake webhook_id, const std::str * @return webhook returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -webhook edit_webhook_sync(const class webhook& wh); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") webhook edit_webhook_sync(const class webhook& wh); /** * @brief Edit webhook message @@ -3123,10 +3348,11 @@ webhook edit_webhook_sync(const class webhook& wh); * @return message returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -message edit_webhook_message_sync(const class webhook &wh, const struct message &m, snowflake thread_id = 0); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message edit_webhook_message_sync(const class webhook &wh, const struct message &m, snowflake thread_id = 0); /** * @brief Edit webhook with token (token is encapsulated in the webhook object) @@ -3136,10 +3362,11 @@ message edit_webhook_message_sync(const class webhook &wh, const struct message * @return webhook returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -webhook edit_webhook_with_token_sync(const class webhook& wh); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") webhook edit_webhook_with_token_sync(const class webhook& wh); /** * @brief Execute webhook @@ -3155,10 +3382,11 @@ webhook edit_webhook_with_token_sync(const class webhook& wh); * @note If the webhook channel is a forum channel, you must provide either `thread_id` or `thread_name`. If `thread_id` is provided, the message will send in that thread. If `thread_name` is provided, a thread with that name will be created in the forum channel. * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -message execute_webhook_sync(const class webhook &wh, const struct message &m, bool wait = false, snowflake thread_id = 0, const std::string& thread_name = ""); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message execute_webhook_sync(const class webhook &wh, const struct message &m, bool wait = false, snowflake thread_id = 0, const std::string& thread_name = ""); /** * @brief Get channel webhooks @@ -3168,10 +3396,11 @@ message execute_webhook_sync(const class webhook &wh, const struct message &m, b * @return webhook_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -webhook_map get_channel_webhooks_sync(snowflake channel_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") webhook_map get_channel_webhooks_sync(snowflake channel_id); /** * @brief Get guild webhooks @@ -3181,10 +3410,11 @@ webhook_map get_channel_webhooks_sync(snowflake channel_id); * @return webhook_map returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -webhook_map get_guild_webhooks_sync(snowflake guild_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") webhook_map get_guild_webhooks_sync(snowflake guild_id); /** * @brief Get webhook @@ -3194,10 +3424,11 @@ webhook_map get_guild_webhooks_sync(snowflake guild_id); * @return webhook returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -webhook get_webhook_sync(snowflake webhook_id); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") webhook get_webhook_sync(snowflake webhook_id); /** * @brief Get webhook message @@ -3210,10 +3441,11 @@ webhook get_webhook_sync(snowflake webhook_id); * @return message returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -message get_webhook_message_sync(const class webhook &wh, snowflake message_id, snowflake thread_id = 0); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") message get_webhook_message_sync(const class webhook &wh, snowflake message_id, snowflake thread_id = 0); /** * @brief Get webhook using token @@ -3224,10 +3456,11 @@ message get_webhook_message_sync(const class webhook &wh, snowflake message_id, * @return webhook returned object on completion * \memberof dpp::cluster * @throw dpp::rest_exception upon failure to execute REST function + * @deprecated This function is deprecated, please use coroutines instead. * @warning This function is a blocking (synchronous) call and should only be used from within a separate thread. * Avoid direct use of this function inside an event handler. */ -webhook get_webhook_with_token_sync(snowflake webhook_id, const std::string &token); +DPP_DEPRECATED("Please use coroutines instead of sync functions: https://dpp.dev/coro-introduction.html") webhook get_webhook_with_token_sync(snowflake webhook_id, const std::string &token); /* End of auto-generated definitions */ diff --git a/include/dpp/collector.h b/include/dpp/collector.h index ac09bf95a6..7d25c1e659 100644 --- a/include/dpp/collector.h +++ b/include/dpp/collector.h @@ -470,4 +470,4 @@ class scheduled_event_collector : public scheduled_event_collector_t { virtual ~scheduled_event_collector() = default; }; -} // namespace dpp +} diff --git a/include/dpp/commandhandler.h b/include/dpp/commandhandler.h index 724f38f577..c8be5c9c97 100644 --- a/include/dpp/commandhandler.h +++ b/include/dpp/commandhandler.h @@ -253,7 +253,7 @@ struct DPP_EXPORT command_info_t { * functions. * @deprecated commandhandler and message commands are deprecated and dpp::slashcommand is encouraged as a replacement. */ -class DPP_EXPORT commandhandler { +class DPP_EXPORT DPP_DEPRECATED("commandhandler should not be used. Please consider using dpp::cluster::register_command instead.") commandhandler { private: /** * @brief List of guild commands to bulk register @@ -425,4 +425,4 @@ class DPP_EXPORT commandhandler { }; -} // namespace dpp +} diff --git a/include/dpp/coro/async.h b/include/dpp/coro/async.h index 4295ae1218..2feac38d8a 100644 --- a/include/dpp/coro/async.h +++ b/include/dpp/coro/async.h @@ -184,6 +184,6 @@ class async : public awaitable { DPP_CHECK_ABI_COMPAT(async<>, async_dummy); -} // namespace dpp +} #endif /* DPP_CORO */ diff --git a/include/dpp/coro/awaitable.h b/include/dpp/coro/awaitable.h index 9a6836b863..e8974c0a88 100644 --- a/include/dpp/coro/awaitable.h +++ b/include/dpp/coro/awaitable.h @@ -39,6 +39,7 @@ struct awaitable_dummy { // Do not include as coro.h includes or depending on clang version #include +#include #include #include #include diff --git a/include/dpp/coro/coroutine.h b/include/dpp/coro/coroutine.h index a9e7a5d100..648ac2b79b 100644 --- a/include/dpp/coro/coroutine.h +++ b/include/dpp/coro/coroutine.h @@ -393,7 +393,7 @@ namespace detail::coroutine { DPP_CHECK_ABI_COMPAT(coroutine, coroutine_dummy) DPP_CHECK_ABI_COMPAT(coroutine, coroutine_dummy) -} // namespace dpp +} /** * @brief Specialization of std::coroutine_traits, helps the standard library figure out a promise type from a coroutine function. diff --git a/include/dpp/coro/task.h b/include/dpp/coro/task.h index 6625bb1f3e..d57bc8ce7f 100644 --- a/include/dpp/coro/task.h +++ b/include/dpp/coro/task.h @@ -433,7 +433,7 @@ std_coroutine::coroutine_handle<> final_awaiter::await_suspend(handle_t ha DPP_CHECK_ABI_COMPAT(task, task_dummy) DPP_CHECK_ABI_COMPAT(task, task_dummy) -} // namespace dpp +} /** * @brief Specialization of std::coroutine_traits, helps the standard library figure out a promise_t type from a coroutine function. diff --git a/include/dpp/discordclient.h b/include/dpp/discordclient.h index ff2f7736f7..d57c2d775b 100644 --- a/include/dpp/discordclient.h +++ b/include/dpp/discordclient.h @@ -87,6 +87,12 @@ class DPP_EXPORT voiceconn { */ class discord_voice_client* voiceclient; + /** + * @brief True to enable DAVE E2EE + * @warning This is an EXPERIMENTAL feature! + */ + bool dave; + /** * @brief Construct a new voiceconn object */ @@ -97,8 +103,10 @@ class DPP_EXPORT voiceconn { * * @param o owner * @param _channel_id voice channel id + * @param enable_dave True to enable DAVE E2EE + * @warn DAVE is an EXPERIMENTAL feature! */ - voiceconn(class discord_client* o, snowflake _channel_id); + voiceconn(class discord_client* o, snowflake _channel_id, bool enable_dave); /** * @brief Destroy the voiceconn object @@ -473,9 +481,10 @@ class DPP_EXPORT discord_client : public websocket_client /** * @brief Handle JSON from the websocket. * @param buffer The entire buffer content from the websocket client + * @param opcode The type of frame, e.g. text or binary * @returns True if a frame has been handled */ - virtual bool handle_frame(const std::string &buffer); + virtual bool handle_frame(const std::string &buffer, ws_opcode opcode); /** * @brief Handle a websocket error. @@ -497,12 +506,13 @@ class DPP_EXPORT discord_client : public websocket_client * @param channel_id Channel ID of the voice channel * @param self_mute True if the bot should mute itself * @param self_deaf True if the bot should deafen itself + * @param enable_dave True to enable DAVE E2EE - EXPERIMENTAL * @return reference to self * @note This is NOT a synchronous blocking call! The bot isn't instantly ready to send or listen for audio, * as we have to wait for the connection to the voice server to be established! * e.g. wait for dpp::cluster::on_voice_ready event, and then send the audio within that event. */ - discord_client& connect_voice(snowflake guild_id, snowflake channel_id, bool self_mute = false, bool self_deaf = false); + discord_client& connect_voice(snowflake guild_id, snowflake channel_id, bool self_mute = false, bool self_deaf = false, bool enable_dave = false); /** * @brief Disconnect from the connected voice channel on a guild @@ -523,4 +533,4 @@ class DPP_EXPORT discord_client : public websocket_client voiceconn* get_voice(snowflake guild_id); }; -} // namespace dpp +} diff --git a/include/dpp/discordevents.h b/include/dpp/discordevents.h index 184294818b..c603239b61 100644 --- a/include/dpp/discordevents.h +++ b/include/dpp/discordevents.h @@ -229,4 +229,4 @@ std::string DPP_EXPORT base64_encode(unsigned char const* buf, unsigned int buff */ std::string DPP_EXPORT ts_to_string(time_t ts); -} // namespace dpp +} diff --git a/include/dpp/discordvoiceclient.h b/include/dpp/discordvoiceclient.h index 07e4364483..ceb9d92829 100644 --- a/include/dpp/discordvoiceclient.h +++ b/include/dpp/discordvoiceclient.h @@ -48,6 +48,7 @@ #include #include #include +#include struct OpusDecoder; struct OpusEncoder; @@ -55,8 +56,28 @@ struct OpusRepacketizer; namespace dpp { +/** + * @brief Sample rate for OPUS (48khz) + */ +[[maybe_unused]] inline constexpr int32_t opus_sample_rate_hz = 48000; + +/** + * @brief Channel count for OPUS (stereo) + */ +[[maybe_unused]] inline constexpr int32_t opus_channel_count = 2; + +/** + * @brief Discord voice protocol version + */ +[[maybe_unused]] inline constexpr uint8_t voice_protocol_version = 8; + + class audio_mixer; +namespace dave::mls { + class session; +} + // !TODO: change these to constexpr and rename every occurrence across the codebase #define AUDIO_TRACK_MARKER (uint16_t)0xFFFF @@ -64,6 +85,10 @@ class audio_mixer; inline constexpr size_t send_audio_raw_max_length = 11520; +inline constexpr size_t secret_key_size = 32; + +struct dave_state; + /* * @brief For holding a moving average of the number of current voice users, for applying a smooth gain ramp. */ @@ -100,6 +125,111 @@ struct DPP_EXPORT voice_out_packet { uint64_t duration; }; +/** + * @brief Supported DAVE (Discord Audio Visual Encryption) protocol versions + */ +enum dave_version_t : uint8_t { + /** + * @brief DAVE disabled (default for now) + */ + dave_version_none = 0, + /** + * @brief DAVE enabled, version 1 (E2EE encryption on top of openssl) + */ + dave_version_1 = 1, +}; + +/** + * @brief Discord voice websocket opcode types + */ +enum voice_websocket_opcode_t : uint8_t { + voice_opcode_connection_identify = 0, + voice_opcode_connection_select_protocol = 1, + voice_opcode_connection_ready = 2, + voice_opcode_connection_heartbeat = 3, + voice_opcode_connection_description = 4, + voice_opcode_client_speaking = 5, + voice_opcode_connection_heartbeat_ack = 6, + voice_opcode_connection_resume = 7, + voice_opcode_connection_hello = 8, + voice_opcode_connection_resumed = 9, + voice_opcode_multiple_clients_connect = 11, + voice_opcode_client_disconnect = 13, + voice_opcode_media_sink = 15, + voice_client_flags = 18, + voice_client_platform = 20, + voice_client_dave_prepare_transition = 21, + voice_client_dave_execute_transition = 22, + voice_client_dave_transition_ready = 23, + voice_client_dave_prepare_epoch = 24, + voice_client_dave_mls_external_sender = 25, + voice_client_dave_mls_key_package = 26, + voice_client_dave_mls_proposals = 27, + voice_client_dave_mls_commit_message = 28, + voice_client_dave_announce_commit_transition = 29, + voice_client_dave_mls_welcome = 30, + voice_client_dave_mls_invalid_commit_welcome = 31, +}; + +/** + * @brief DAVE E2EE Binary frame header + */ +struct dave_binary_header_t { + /** + * @brief Sequence number + */ + uint16_t seq; + + /** + * @brief Opcode type + */ + uint8_t opcode; + + /** + * @brief Data package, an opaque structure passed to the + * Discord libdave functions. + */ + std::vector package; + + /** + * @brief Fill binary header from inbound buffer + * @param buffer inbound websocket buffer + */ + dave_binary_header_t(const std::string& buffer); + + /** + * Get the data package from the packed binary frame, as a vector of uint8_t + * for use in the libdave functions + + * @return data blob + */ + [[nodiscard]] std::vector get_data() const; + + /** + * Get transition ID for process_commit and process_welcome + * + * @return Transition ID + */ + [[nodiscard]] uint16_t get_transition_id() const; + +private: + /** + * @brief Transition id, only valid when the opcode is + * commit and welcome state. Use get_transition_id() to obtain value. + */ + uint16_t transition_id; +}; + +/** + * @brief A callback for obtaining a user's privacy code. + * The privacy code is returned as the parameter to the function. + * + * This is a callback function because DAVE requires use of a very resource + * intensive SCRYPT call, which uses lots of ram and cpu and take significant + * time. + */ +using privacy_code_callback_t = std::function; + /** @brief Implements a discord voice connection. * Each discord_voice_client connects to one voice channel and derives from a websocket client. */ @@ -110,6 +240,11 @@ class DPP_EXPORT discord_voice_client : public websocket_client */ void cleanup(); + /** + * @brief A frame of silence packet + */ + static constexpr uint8_t silence_packet[3] = { 0xf8, 0xff, 0xfe }; + /** * @brief Mutex for outbound packet stream */ @@ -120,6 +255,13 @@ class DPP_EXPORT discord_voice_client : public websocket_client */ std::shared_mutex queue_mutex; + /** + * @brief Our public IP address + * + * Use discord_voice_client::discover_ip() to access this value + */ + std::string external_ip; + /** * @brief Queue of outbound messages */ @@ -297,6 +439,13 @@ class DPP_EXPORT discord_voice_client : public websocket_client */ bool paused; + /** + * @brief Whether has sent 5 frame of silence before stopping on pause. + * + * This is to avoid unintended Opus interpolation with subsequent transmissions. + */ + bool sent_stop_frames; + #ifdef HAVE_VOICE /** * @brief libopus encoder @@ -308,6 +457,13 @@ class DPP_EXPORT discord_voice_client : public websocket_client * (merges frames into one packet) */ OpusRepacketizer* repacketizer; + + /** + * @brief This holds the state information for DAVE E2EE. + * it is only allocated if E2EE is active on the voice channel. + */ + std::unique_ptr mls_state; + #else /** * @brief libopus encoder @@ -319,8 +475,26 @@ class DPP_EXPORT discord_voice_client : public websocket_client * (merges frames into one packet) */ void* repacketizer; + + /** + * @brief This holds the state information for DAVE E2EE. + * it is only allocated if E2EE is active on the voice channel. + */ + std::unique_ptr mls_state{}; #endif + /** + * @brief The list of users that have E2EE potentially enabled for + * DAVE protocol. + */ + std::set dave_mls_user_list; + + /** + * @brief The list of users that have left the voice channel but + * not yet removed from MLS group. + */ + std::set dave_mls_pending_remove_list; + /** * @brief File descriptor for UDP connection */ @@ -328,10 +502,15 @@ class DPP_EXPORT discord_voice_client : public websocket_client /** * @brief Secret key for encrypting voice. - * If it has been sent, this is non-null and points to a - * sequence of exactly 32 bytes. + * If it has been sent, this contains a sequence of exactly 32 bytes + * (secret_key_size) and has_secret_key is set to true. + */ + std::array secret_key; + + /** + * @brief True if the voice client has a secret key */ - uint8_t* secret_key; + bool has_secret_key{false}; /** * @brief Sequence number of outbound audio. This is incremented @@ -339,6 +518,13 @@ class DPP_EXPORT discord_voice_client : public websocket_client */ uint16_t sequence; + /** + * @brief Last received sequence from gateway. + * + * Needed for heartbeat and resume payload. + */ + int32_t receive_sequence; + /** * @brief Timestamp value used in outbound audio. Each packet * has the timestamp value which is incremented to match @@ -346,6 +532,17 @@ class DPP_EXPORT discord_voice_client : public websocket_client */ uint32_t timestamp; + /** + * @brief Each packet should have a nonce, a 32-bit incremental + * integer value appended to payload. + * + * We should keep track of this value and increment it for each + * packet sent. + * + * Current initial value is hardcoded to 1. + */ + uint32_t packet_nonce; + /** * @brief Last sent packet high-resolution timestamp */ @@ -391,6 +588,21 @@ class DPP_EXPORT discord_voice_client : public websocket_client */ uint8_t encode_buffer[65536]; + /** + * @brief DAVE - Discord Audio Visual Encryption + * Used for E2EE encryption. dave_protocol_none is + * the default right now. + * @warning DAVE E2EE is an EXPERIMENTAL feature! + */ + dave_version_t dave_version; + + /** + * @brief Destination address for where packets go + * on the UDP socket + */ + address_t destination{}; + + /** * @brief Send data to UDP socket immediately. * @@ -450,8 +662,10 @@ class DPP_EXPORT discord_voice_client : public websocket_client * @param packet packet data * @param len length of packet * @param duration duration of opus packet + * @param send_now send this packet right away without buffering. + * Do NOT set send_now to true outside write_ready. */ - void send(const char* packet, size_t len, uint64_t duration); + void send(const char* packet, size_t len, uint64_t duration, bool send_now = false); /** * @brief Queue a message to be sent via the websocket @@ -490,6 +704,12 @@ class DPP_EXPORT discord_voice_client : public websocket_client */ size_t encode(uint8_t *input, size_t inDataSize, uint8_t *output, size_t &outDataSize); + /** + * Updates DAVE MLS ratchets for users in the VC + * @param force True to force updating of ratchets regardless of state + */ + void update_ratchets(bool force = false); + public: /** @@ -497,12 +717,6 @@ class DPP_EXPORT discord_voice_client : public websocket_client */ class dpp::cluster* creator; - /** - * @brief This needs to be static, we only initialise libsodium once per program start, - * so initialising it on first use in a voice connection is best. - */ - static bool sodium_initialised; - /** * @brief True when the thread is shutting down */ @@ -676,9 +890,11 @@ class DPP_EXPORT discord_voice_client : public websocket_client * @param _token The voice session token to use for identifying to the websocket * @param _session_id The voice session id to identify with * @param _host The voice server hostname to connect to (hostname:port format) - * @throw dpp::voice_exception Sodium or Opus failed to initialise, or D++ is not compiled with voice support + * @param enable_dave Enable DAVE E2EE + * @throw dpp::voice_exception Opus failed to initialise, or D++ is not compiled with voice support + * @warning DAVE E2EE is an EXPERIMENTAL feature! */ - discord_voice_client(dpp::cluster* _cluster, snowflake _channel_id, snowflake _server_id, const std::string &_token, const std::string &_session_id, const std::string &_host); + discord_voice_client(dpp::cluster* _cluster, snowflake _channel_id, snowflake _server_id, const std::string &_token, const std::string &_session_id, const std::string &_host, bool enable_dave = false); /** * @brief Destroy the discord voice client object @@ -688,10 +904,11 @@ class DPP_EXPORT discord_voice_client : public websocket_client /** * @brief Handle JSON from the websocket. * @param buffer The entire buffer content from the websocket client + * @param opcode Frame type, e.g. OP_TEXT, OP_BINARY * @return bool True if a frame has been handled * @throw dpp::exception If there was an error processing the frame, or connection to UDP socket failed */ - virtual bool handle_frame(const std::string &buffer); + virtual bool handle_frame(const std::string &buffer, ws_opcode opcode); /** * @brief Handle a websocket error. @@ -709,7 +926,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client * * You should send an audio packet of `send_audio_raw_max_length` (11520) bytes. * Note that this function can be costly as it has to opus encode - * the PCM audio on the fly, and also encrypt it with libsodium. + * the PCM audio on the fly, and also encrypt it with openssl. * * @note Because this function encrypts and encodes packets before * pushing them onto the output queue, if you have a complete stream @@ -749,7 +966,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client * Some containers such as .ogg may contain OPUS * encoded data already. In this case, we don't need to encode the * frames using opus here. We can bypass the codec, only applying - * libsodium to the stream. + * openssl to the stream. * * @param opus_packet Opus packets. Discord expects opus frames * to be encoded at 48000Hz @@ -759,6 +976,10 @@ class DPP_EXPORT discord_voice_client : public websocket_client * @param duration Generally duration is 2.5, 5, 10, 20, 40 or 60 * if the timescale is 1000000 (1ms) * + * @param send_now Send this packet right away without buffering, + * this will skip duration calculation for the packet being sent + * and only safe to be set to true in write_ready. + * * @return discord_voice_client& Reference to self * * @note It is your responsibility to ensure that packets of data @@ -769,7 +990,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client * * @throw dpp::voice_exception If data length is invalid or voice support not compiled into D++ */ - discord_voice_client& send_audio_opus(uint8_t* opus_packet, const size_t length, uint64_t duration); + discord_voice_client& send_audio_opus(const uint8_t* opus_packet, const size_t length, uint64_t duration, bool send_now = false); /** * @brief Send opus packets to the voice channel @@ -777,7 +998,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client * Some containers such as .ogg may contain OPUS * encoded data already. In this case, we don't need to encode the * frames using opus here. We can bypass the codec, only applying - * libsodium to the stream. + * opens to the stream. * * Duration is calculated internally * @@ -796,7 +1017,7 @@ class DPP_EXPORT discord_voice_client : public websocket_client * * @throw dpp::voice_exception If data length is invalid or voice support not compiled into D++ */ - discord_voice_client& send_audio_opus(uint8_t* opus_packet, const size_t length); + discord_voice_client& send_audio_opus(const uint8_t* opus_packet, const size_t length); /** * @brief Send silence to the voice channel @@ -809,6 +1030,19 @@ class DPP_EXPORT discord_voice_client : public websocket_client */ discord_voice_client& send_silence(const uint64_t duration); + /** + * @brief Send stop frames to the voice channel. + * + * @param send_now send this packet right away without buffering. + * Do NOT set send_now to true outside write_ready. + * Also make sure you're not locking stream_mutex if you + * don't set send_now to true. + * + * @return discord_voice_client& Reference to self + * @throw dpp::voice_exception if voice support is not compiled into D++ + */ + discord_voice_client& send_stop_frames(bool send_now = false); + /** * @brief Sets the audio type that will be sent with send_audio_* methods. * @@ -964,7 +1198,78 @@ class DPP_EXPORT discord_voice_client : public websocket_client * for a single packet from Discord's voice servers. */ std::string discover_ip(); + + /** + * @brief Returns true if end-to-end encryption is enabled + * for the active voice call (Discord Audio Visual + * Encryption, a.k.a. DAVE). + * + * @return True if end-to-end encrypted + */ + bool is_end_to_end_encrypted() const; + + /** + * @brief Returns the privacy code for the end to end encryption + * scheme ("DAVE"). if end-to-end encryption is not active, + * or is not yet established, this will return an empty + * string. + * + * @return A sequence of six five-digit integers which + * can be matched against the Discord client, in the + * privacy tab for the properties of the voice call. + */ + std::string get_privacy_code() const; + + /** + * @brief Returns the privacy code for a given user by id, + * if they are in the voice call, and enc-to-end encryption + * is enabled. + * + * @param user User ID to fetch the privacy code for + * @param callback Callback to call with the privacy code when + * the creation of the code is complete. + * @warning This call spawns a thread, as getting a user's + * privacy code is a CPU-intensive and memory-intensive operation + * which internally uses scrypt. + */ + void get_user_privacy_code(const dpp::snowflake user, privacy_code_callback_t callback) const; + + /** + * @brief Notify gateway ready for a DAVE transition. + * + * Fires Voice Ready event when appropriate. + * + * https://daveprotocol.com/#commit-handling + * + * @param data Websocket frame data + */ + void ready_for_transition(const std::string &data); + + /** + * @brief Reset dave session, send voice_client_dave_mls_invalid_commit_welcome + * payload with current transition Id and our new key package to gateway. + * + * https://daveprotocol.com/#recovery-from-invalid-commit-or-welcome + */ + void recover_from_invalid_commit_welcome(); + + /** + * @brief Execute pending protocol upgrade/downgrade to/from dave. + * @return true if did an upgrade/downgrade + */ + bool execute_pending_upgrade_downgrade(); + + /** + * @brief Reset dave session and prepare initial session group. + */ + void reinit_dave_mls_group(); + + /** + * @brief Process roster map from commit/welcome. + * @param rmap Roster map + */ + void process_mls_group_rosters(const std::map>& rmap); }; -} // namespace dpp +} diff --git a/include/dpp/dispatcher.h b/include/dpp/dispatcher.h index 904633c5cc..be08488b7b 100644 --- a/include/dpp/dispatcher.h +++ b/include/dpp/dispatcher.h @@ -1989,30 +1989,7 @@ struct DPP_EXPORT voice_buffer_send_t : public event_dispatch_t { }; /** - * @brief voice user talking - */ -struct DPP_EXPORT voice_user_talking_t : public event_dispatch_t { - using event_dispatch_t::event_dispatch_t; - using event_dispatch_t::operator=; - - /** - * @brief voice client where user is talking - */ - class discord_voice_client* voice_client = nullptr; - - /** - * @brief talking user id - */ - snowflake user_id = {}; - - /** - * @brief flags for talking user - */ - uint8_t talking_flags = 0; -}; - -/** - * @brief voice user talking + * @brief voice ready */ struct DPP_EXPORT voice_ready_t : public event_dispatch_t { using event_dispatch_t::event_dispatch_t; @@ -2144,6 +2121,44 @@ struct DPP_EXPORT voice_client_disconnect_t : public event_dispatch_t { snowflake user_id = {}; }; +/** + * @brief Discord voice platform types + */ +enum client_platform_t : uint8_t { + /** + * @brief Web, Desktop + */ + client_platform_desktop = 0, + /** + * @brief Mobile device + */ + client_platform_mobile = 1, +}; + +/** + * @brief voice client platform type notification event + */ +struct DPP_EXPORT voice_client_platform_t : public event_dispatch_t { + using event_dispatch_t::event_dispatch_t; + using event_dispatch_t::operator=; + + /** + * @brief voice client where user is + */ + discord_voice_client* voice_client = nullptr; + + /** + * @brief user id of user who left vc + */ + snowflake user_id = {}; + + /** + * @brief Client platform for the voice user + * Either desktop, or mobile + */ + client_platform_t platform = client_platform_desktop; +}; + /** * @brief Delete stage instance */ @@ -2183,5 +2198,5 @@ struct DPP_EXPORT entitlement_delete_t : public event_dispatch_t { entitlement deleted = {}; }; -} // namespace dpp +} diff --git a/include/dpp/dns.h b/include/dpp/dns.h index 5a3a566d45..48cebcd563 100644 --- a/include/dpp/dns.h +++ b/include/dpp/dns.h @@ -31,6 +31,8 @@ #include #include #include +#include +#include namespace dpp { @@ -40,23 +42,42 @@ namespace dpp { */ struct dns_cache_entry { /** - * @brief Resolved address information + * @brief Resolved address metadata */ addrinfo addr; /** - * @brief Socket address. - * Discord only supports ipv4, but sockaddr_in6 is larger - * than sockaddr_in, sockaddr_storage will hold either. This - * means that if discord ever do support ipv6 we just flip - * one value in dns.cpp and that should be all that is needed. + * @brief Resolved address as string. + * The metadata is needed to know what type of address it is. + * Do not do silly stuff like just looking to see if '.' is in it! */ - sockaddr_storage ai_addr; + std::string resolved_addr; /** * @brief Time at which this cache entry is invalidated */ time_t expire_timestamp; + + /** + * @brief Get address length + * @return address length + */ + [[nodiscard]] int size() const; + + /** + * @brief Get the address_t that corresponds to this cache entry + * for use when connecting with ::connect() + * @param port Port number to connect to + * @return address_t prefilled with the IP and port number + */ + [[nodiscard]] const address_t get_connecting_address(uint16_t port) const; + + /** + * @brief Allocate a socket file descriptor for the given dns address + * @return File descriptor ready for calling connect(), or INVALID_SOCKET + * on failure. + */ + [[nodiscard]] socket make_connecting_socket() const; }; /** @@ -73,4 +94,4 @@ namespace dpp { * @throw dpp::connection_exception On failure to resolve hostname */ const dns_cache_entry* resolve_hostname(const std::string& hostname, const std::string& port); -} // namespace dpp +} diff --git a/include/dpp/dtemplate.h b/include/dpp/dtemplate.h index 098824e458..b0a2dc494b 100644 --- a/include/dpp/dtemplate.h +++ b/include/dpp/dtemplate.h @@ -112,4 +112,4 @@ class DPP_EXPORT dtemplate : public json_interface { */ typedef std::unordered_map dtemplate_map; -} // namespace dpp +} diff --git a/include/dpp/emoji.h b/include/dpp/emoji.h index 6249b4c9eb..7546ead720 100644 --- a/include/dpp/emoji.h +++ b/include/dpp/emoji.h @@ -249,4 +249,4 @@ class DPP_EXPORT emoji : public managed, public json_interface { */ typedef std::unordered_map emoji_map; -} // namespace dpp +} diff --git a/include/dpp/entitlement.h b/include/dpp/entitlement.h index 857b2691dd..fd181d6c5c 100644 --- a/include/dpp/entitlement.h +++ b/include/dpp/entitlement.h @@ -243,4 +243,4 @@ class DPP_EXPORT entitlement : public managed, public json_interface entitlement_map; -} // namespace dpp +} diff --git a/include/dpp/etf.h b/include/dpp/etf.h index 8e17ca3a96..f4e9f1d29b 100644 --- a/include/dpp/etf.h +++ b/include/dpp/etf.h @@ -708,4 +708,4 @@ class DPP_EXPORT etf_parser { std::string build(const nlohmann::json& j); }; -} // namespace dpp +} diff --git a/include/dpp/event.h b/include/dpp/event.h index 6921586755..1e34e1b365 100644 --- a/include/dpp/event.h +++ b/include/dpp/event.h @@ -156,4 +156,4 @@ event_decl(entitlement_create, ENTITLEMENT_CREATE); event_decl(entitlement_update, ENTITLEMENT_UPDATE); event_decl(entitlement_delete, ENTITLEMENT_DELETE); -} // namespace dpp::events +} diff --git a/include/dpp/event_router.h b/include/dpp/event_router.h index 2babc42e29..be8fc14d22 100644 --- a/include/dpp/event_router.h +++ b/include/dpp/event_router.h @@ -742,4 +742,4 @@ const T &awaitable::await_resume() { } #endif -} // namespace dpp +} diff --git a/include/dpp/exception.h b/include/dpp/exception.h index 9584e8e86d..3d5f41ff08 100644 --- a/include/dpp/exception.h +++ b/include/dpp/exception.h @@ -31,7 +31,7 @@ namespace dpp { * @brief Exception error codes possible for dpp::exception::code() * * This list is a combined list of Discord's error codes, HTTP error codes, - * zlib, opus, sodium and C library codes (e.g. DNS, socket etc). You may + * zlib, opus and C library codes (e.g. DNS, socket etc). You may * use these to easily identify a type of exception without having to resort * to string comparison against dpp::exception::what() * @@ -98,7 +98,6 @@ enum exception_error_code { err_no_voice_support = 29, err_invalid_voice_packet_length = 30, err_opus = 31, - err_sodium = 32, err_etf = 33, err_cache = 34, err_icon_size = 35, @@ -591,6 +590,8 @@ class exception : public std::exception derived_exception(file_exception, exception); derived_exception(connection_exception, exception); derived_exception(voice_exception, exception); + derived_exception(encryption_exception, voice_exception); + derived_exception(decryption_exception, voice_exception); derived_exception(rest_exception, exception); derived_exception(invalid_token_exception, rest_exception); derived_exception(length_exception, exception); @@ -601,5 +602,5 @@ class exception : public std::exception # endif /* DPP_CORO */ #endif -} // namespace dpp +} diff --git a/include/dpp/export.h b/include/dpp/export.h index e293aafccc..7895e79ec4 100644 --- a/include/dpp/export.h +++ b/include/dpp/export.h @@ -115,12 +115,10 @@ extern bool DPP_EXPORT validate_configuration(); } -#ifndef _WIN32 - #define SOCKET int -#else - #ifndef NOMINMAX - #define NOMINMAX - #endif +#ifdef _WIN32 + #ifndef NOMINMAX + #define NOMINMAX + #endif #include #endif diff --git a/include/dpp/guild.h b/include/dpp/guild.h index 28c9bf720a..b031371e20 100644 --- a/include/dpp/guild.h +++ b/include/dpp/guild.h @@ -1288,12 +1288,14 @@ class DPP_EXPORT guild : public managed, public json_interface { * @param user_id User id to join * @param self_mute True if the bot should mute itself * @param self_deaf True if the bot should deafen itself + * @param dave True to enable DAVE E2EE + * @warning DAVE is EXPERIMENTAL and subject to change. * @return True if the user specified is in a vc, false if they aren't * @note This is NOT a synchronous blocking call! The bot isn't instantly ready to send or listen for audio, * as we have to wait for the connection to the voice server to be established! * e.g. wait for dpp::cluster::on_voice_ready event, and then send the audio within that event. */ - bool connect_member_voice(snowflake user_id, bool self_mute = false, bool self_deaf = false); + bool connect_member_voice(snowflake user_id, bool self_mute = false, bool self_deaf = false, bool dave = false); /** * @brief Get the banner url of the guild if it have one, otherwise returns an empty string @@ -2046,4 +2048,4 @@ typedef std::unordered_map guild_member_map; */ guild_member DPP_EXPORT find_guild_member(const snowflake guild_id, const snowflake user_id); -} // namespace dpp +} diff --git a/include/dpp/httpsclient.h b/include/dpp/httpsclient.h index 0090c68af4..5a0ff481cf 100644 --- a/include/dpp/httpsclient.h +++ b/include/dpp/httpsclient.h @@ -356,4 +356,4 @@ class DPP_EXPORT https_client : public ssl_client { }; -} // namespace dpp +} diff --git a/include/dpp/integration.h b/include/dpp/integration.h index acde8addb3..ce806c0ca4 100644 --- a/include/dpp/integration.h +++ b/include/dpp/integration.h @@ -349,5 +349,5 @@ typedef std::unordered_map integration_map; */ typedef std::unordered_map connection_map; -} // namespace dpp +} diff --git a/include/dpp/intents.h b/include/dpp/intents.h index d225a9d5e1..f2e82b0c74 100644 --- a/include/dpp/intents.h +++ b/include/dpp/intents.h @@ -152,4 +152,4 @@ enum intents { i_unverified_default_intents = dpp::i_default_intents | dpp::i_message_content }; -} // namespace dpp +} diff --git a/include/dpp/invite.h b/include/dpp/invite.h index c20708056a..d1d9a939b9 100644 --- a/include/dpp/invite.h +++ b/include/dpp/invite.h @@ -242,4 +242,4 @@ class DPP_EXPORT invite : public json_interface { */ typedef std::unordered_map invite_map; -} // namespace dpp +} diff --git a/include/dpp/isa/avx.h b/include/dpp/isa/avx.h index 367980bd9e..6cae986288 100644 --- a/include/dpp/isa/avx.h +++ b/include/dpp/isa/avx.h @@ -24,6 +24,8 @@ #include #include +#include +#include namespace dpp { @@ -105,6 +107,6 @@ namespace dpp { } }; -} // namespace dpp +} #endif \ No newline at end of file diff --git a/include/dpp/isa/avx2.h b/include/dpp/isa/avx2.h index 1e02eaa935..29ed493e43 100644 --- a/include/dpp/isa/avx2.h +++ b/include/dpp/isa/avx2.h @@ -24,6 +24,8 @@ #include #include +#include +#include namespace dpp { @@ -108,6 +110,6 @@ namespace dpp { } }; -} // namespace dpp +} #endif \ No newline at end of file diff --git a/include/dpp/isa/avx512.h b/include/dpp/isa/avx512.h index 68f3583801..333a1a64f7 100644 --- a/include/dpp/isa/avx512.h +++ b/include/dpp/isa/avx512.h @@ -24,6 +24,8 @@ #include #include +#include +#include namespace dpp { @@ -111,6 +113,6 @@ namespace dpp { } }; -} // namespace dpp +} #endif \ No newline at end of file diff --git a/include/dpp/isa/fallback.h b/include/dpp/isa/fallback.h index 147ff51d0a..5d246d4d29 100644 --- a/include/dpp/isa/fallback.h +++ b/include/dpp/isa/fallback.h @@ -20,7 +20,10 @@ ************************************************************************************/ #pragma once +#include #include +#include +#include namespace dpp { @@ -73,4 +76,4 @@ namespace dpp { } }; -} // namespace dpp +} diff --git a/include/dpp/isa/neon.h b/include/dpp/isa/neon.h new file mode 100644 index 0000000000..8d27d8b241 --- /dev/null +++ b/include/dpp/isa/neon.h @@ -0,0 +1,120 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ +#pragma once + +#if defined _MSC_VER || defined __GNUC__ || defined __clang__ + +#include +#include +#include +#include + +namespace dpp { + + using neon_float = float32x4_t; + + /** + * @brief A class for audio mixing operations using ARM NEON instructions. + */ + class audio_mixer { + public: + + /** + * @brief The number of 32-bit values per CPU register. + */ + inline static constexpr int32_t byte_blocks_per_register{ 4 }; + + /** + * @brief Collect a single register worth of data from data_in, apply gain and increment, and store the result in data_out. + * This version uses ARM NEON instructions. + * + * @param data_in Pointer to the input array of int32_t values. + * @param data_out Pointer to the output array of int16_t values. + * @param current_gain The gain to be applied to the elements. + * @param increment The increment value to be added to each element. + */ + inline void collect_single_register(int32_t* data_in, int16_t* data_out, float current_gain, float increment) { + neon_float gathered_values = gather_values(data_in); + neon_float gain_vector = vdupq_n_f32(current_gain); + static constexpr float data[4] = { 0.0f, 1.0f, 2.0f, 3.0f }; + neon_float floats = vld1q_f32(data); + neon_float increment_vector = vmulq_f32(vdupq_n_f32(increment), floats)); + neon_float current_samples_new = vmulq_f32(gathered_values, vaddq_f32(gain_vector, increment_vector)); + + // Clamping the values between int16_t min and max + neon_float min_val = vdupq_n_f32(static_cast(std::numeric_limits::min())); + neon_float max_val = vdupq_n_f32(static_cast(std::numeric_limits::max())); + + current_samples_new = vmaxq_f32(current_samples_new, min_val); + current_samples_new = vminq_f32(current_samples_new, max_val); + + store_values(current_samples_new, data_out); + } + + /** + * @brief Combine a register worth of elements from decoded_data and store the result in up_sampled_vector. + * This version uses ARM NEON instructions. + * + * @param up_sampled_vector Pointer to the array of int32_t values. + * @param decoded_data Pointer to the array of int16_t values. + */ + inline void combine_samples(int32_t* up_sampled_vector, const int16_t* decoded_data) { + neon_float up_sampled = gather_values(up_sampled_vector); + neon_float decoded = gather_values(decoded_data); + neon_float newValues = vaddq_f32(up_sampled, decoded); + store_values(newValues, up_sampled_vector); + } + + protected: + /** + * @brief Array for storing the values to be loaded/stored. + */ + alignas(16) float values[byte_blocks_per_register]{}; + + /** + * @brief Stores values from a 128-bit NEON vector to a storage location. + * @tparam value_type The target value type for storage. + * @param values_to_store The 128-bit NEON vector containing values to store. + * @param storage_location Pointer to the storage location. + */ + template inline void store_values(const neon_float& values_to_store, value_type* storage_location) { + vst1q_f32(values, values_to_store); + for (int64_t x = 0; x < byte_blocks_per_register; ++x) { + storage_location[x] = static_cast(values[x]); + } + } + + /** + * @brief Specialization for gathering non-float values into a NEON register. + * @tparam value_type The type of values being gathered. + * @return A NEON register containing gathered values. + */ + template inline neon_float gather_values(value_type* values_new) { + for (uint64_t x = 0; x < byte_blocks_per_register; ++x) { + values[x] = static_cast(values_new[x]); + } + return vld1q_f32(values); + } + }; + +} // namespace dpp + +#endif \ No newline at end of file diff --git a/include/dpp/isa_detection.h b/include/dpp/isa_detection.h index 2f7925efc5..54b641a7f0 100644 --- a/include/dpp/isa_detection.h +++ b/include/dpp/isa_detection.h @@ -20,7 +20,9 @@ ************************************************************************************/ #pragma once -#if AVX_TYPE == 512 +#if AVX_TYPE == 1024 + #include "isa/neon.h" +#elif AVX_TYPE == 512 #include "isa/avx512.h" #elif AVX_TYPE == 2 #include "isa/avx2.h" diff --git a/include/dpp/json_interface.h b/include/dpp/json_interface.h index 0fd209ed00..720b7a2eba 100644 --- a/include/dpp/json_interface.h +++ b/include/dpp/json_interface.h @@ -70,4 +70,4 @@ struct json_interface { } }; -} // namespace dpp +} diff --git a/include/dpp/managed.h b/include/dpp/managed.h index 4ccd2e5318..66d6b210a1 100644 --- a/include/dpp/managed.h +++ b/include/dpp/managed.h @@ -113,4 +113,4 @@ class DPP_EXPORT managed { } }; -} // namespace dpp +} diff --git a/include/dpp/message.h b/include/dpp/message.h index f63dc5e056..6229caf806 100644 --- a/include/dpp/message.h +++ b/include/dpp/message.h @@ -2048,6 +2048,24 @@ struct DPP_EXPORT interaction_metadata_type { }; /** + * @brief Message Reference type + */ +enum DPP_EXPORT message_ref_type : uint8_t { + /** + * A reply or crosspost + */ + mrt_default = 0, + /** + * A forwarded message + */ + mrt_forward = 1, +}; + +template struct message_snapshot { + std::vector messages; +}; + + /** * @brief Represents messages sent and received on Discord */ struct DPP_EXPORT message : public managed, json_interface { @@ -2177,6 +2195,10 @@ struct DPP_EXPORT message : public managed, json_interface { * @brief Reference to another message, e.g. a reply */ struct message_ref { + /** + * @brief Message reference type, set to 1 to forward a message + */ + message_ref_type type{mrt_default}; /** * @brief ID of the originating message. */ @@ -2199,10 +2221,15 @@ struct DPP_EXPORT message : public managed, json_interface { bool fail_if_not_exists; } message_reference; + /** + * @brief Message snapshots for a forwarded message + */ + message_snapshot message_snapshots; + /** * @brief Reference to an interaction */ - [[deprecated("Use interaction_metadata instead.")]] struct message_interaction_struct{ + DPP_DEPRECATED("Use interaction_metadata instead.") struct message_interaction_struct{ /** * @brief ID of the interaction. */ @@ -2384,9 +2411,10 @@ struct DPP_EXPORT message : public managed, json_interface { * @param _guild_id guild id to reply to (optional) * @param _channel_id channel id to reply to (optional) * @param fail_if_not_exists true if the message send should fail if these values are invalid (optional) + * @param type Type of reference * @return message& reference to self */ - message& set_reference(snowflake _message_id, snowflake _guild_id = 0, snowflake _channel_id = 0, bool fail_if_not_exists = false); + message& set_reference(snowflake _message_id, snowflake _guild_id = 0, snowflake _channel_id = 0, bool fail_if_not_exists = false, message_ref_type type = mrt_default); /** * @brief Set the allowed mentions object for pings on the message @@ -2665,4 +2693,4 @@ typedef std::unordered_map sticker_map; */ typedef std::unordered_map sticker_pack_map; -} // namespace dpp +} diff --git a/include/dpp/misc-enum.h b/include/dpp/misc-enum.h index 19fd2fcd9a..d9ac781f08 100644 --- a/include/dpp/misc-enum.h +++ b/include/dpp/misc-enum.h @@ -86,4 +86,4 @@ enum loglevel { ll_critical }; -} // namespace dpp +} diff --git a/include/dpp/once.h b/include/dpp/once.h index f81a02cfdb..ef24f205bb 100644 --- a/include/dpp/once.h +++ b/include/dpp/once.h @@ -43,4 +43,4 @@ template auto run_once() { return !std::exchange(called, true); }; -} // namespace dpp +} diff --git a/include/dpp/permissions.h b/include/dpp/permissions.h index 22b1b555ba..4124e3cd80 100644 --- a/include/dpp/permissions.h +++ b/include/dpp/permissions.h @@ -456,4 +456,4 @@ class DPP_EXPORT permission { } }; -} // namespace dpp +} diff --git a/include/dpp/presence.h b/include/dpp/presence.h index 7515b14bac..3918bf2d60 100644 --- a/include/dpp/presence.h +++ b/include/dpp/presence.h @@ -595,4 +595,4 @@ class DPP_EXPORT presence : public json_interface { */ typedef std::unordered_map presence_map; -} // namespace dpp +} diff --git a/include/dpp/prune.h b/include/dpp/prune.h index 36e6275f6d..08b7592597 100644 --- a/include/dpp/prune.h +++ b/include/dpp/prune.h @@ -77,4 +77,4 @@ struct DPP_EXPORT prune : public json_interface { json to_json(bool with_id = false) const; // Intentional shadow of json_interface, mostly present for documentation }; -} // namespace dpp +} diff --git a/include/dpp/queues.h b/include/dpp/queues.h index 3597e78931..c502647936 100644 --- a/include/dpp/queues.h +++ b/include/dpp/queues.h @@ -671,4 +671,4 @@ class DPP_EXPORT request_queue { bool is_globally_ratelimited() const; }; -} // namespace dpp +} diff --git a/include/dpp/restrequest.h b/include/dpp/restrequest.h index 93fdd27071..2fcff85a77 100644 --- a/include/dpp/restrequest.h +++ b/include/dpp/restrequest.h @@ -294,4 +294,4 @@ template inline void rest_request_vector(dpp::cluster* c, const char* b } -} // namespace dpp +} diff --git a/include/dpp/restresults.h b/include/dpp/restresults.h index 3dd0ebb69c..a78b9e9dfe 100644 --- a/include/dpp/restresults.h +++ b/include/dpp/restresults.h @@ -156,6 +156,7 @@ typedef std::variant< ban_map, voiceregion, voiceregion_map, + voicestate, integration, integration_map, webhook, @@ -334,4 +335,4 @@ typedef std::function command_completion_e * @brief Automatically JSON encoded HTTP result */ typedef std::function json_encode_t; -} // namespace dpp +} diff --git a/include/dpp/role.h b/include/dpp/role.h index a3da13f367..1a76862823 100644 --- a/include/dpp/role.h +++ b/include/dpp/role.h @@ -994,5 +994,5 @@ typedef std::unordered_map role_map; */ typedef std::vector application_role_connection_metadata_list; -} // namespace dpp +} diff --git a/include/dpp/sku.h b/include/dpp/sku.h index f9d7c03908..7515a06260 100644 --- a/include/dpp/sku.h +++ b/include/dpp/sku.h @@ -173,4 +173,4 @@ class DPP_EXPORT sku : public managed, public json_interface { */ typedef std::unordered_map sku_map; -} // namespace dpp +} diff --git a/include/dpp/snowflake.h b/include/dpp/snowflake.h index 109f16a53a..871dca729b 100644 --- a/include/dpp/snowflake.h +++ b/include/dpp/snowflake.h @@ -269,7 +269,7 @@ class DPP_EXPORT snowflake final { } }; -} // namespace dpp +} template<> struct std::hash diff --git a/include/dpp/socket.h b/include/dpp/socket.h index 04d1080035..3888b2e022 100644 --- a/include/dpp/socket.h +++ b/include/dpp/socket.h @@ -1,19 +1,53 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ #pragma once -#ifndef _WIN32 -#ifndef SOCKET -#define SOCKET int -#endif +#include +#ifdef _WIN32 + #include + #include + #include + #define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) + #define pollfd WSAPOLLFD +#else + #include + #include + #include #endif +#include +#include + namespace dpp { - /** - * @brief Represents a socket file descriptor. - * This is used to ensure parity between windows and unix-like systems. - */ - typedef SOCKET socket; -} // namespace dpp +/** + * @brief Represents a socket file descriptor. + * This is used to ensure parity between windows and unix-like systems. + */ +#ifndef _WIN32 + using socket = int; +#else + using socket = SOCKET; +#endif #ifndef SOCKET_ERROR /** @@ -28,3 +62,92 @@ namespace dpp */ #define INVALID_SOCKET ~0 #endif + +/** + * @brief Represents an IPv4 address for use with socket functions such as + * bind(). + * + * Avoids type punning with C style casts from sockaddr_in to sockaddr pointers. + */ +class DPP_EXPORT address_t { + /** + * @brief Internal sockaddr struct + */ + sockaddr socket_addr{}; + +public: + + /** + * @brief Create a new address_t + * @param ip IPv4 address + * @param port Port number + * @note Leave both as defaults to create a default bind-to-any setting + */ + address_t(const std::string_view ip = "0.0.0.0", uint16_t port = 0); + + /** + * @brief Get sockaddr + * @return sockaddr pointer + */ + [[nodiscard]] sockaddr *get_socket_address(); + + /** + * @brief Returns size of sockaddr_in + * @return sockaddr_in size + * @note It is important the size this returns is sizeof(sockaddr_in) not + * sizeof(sockaddr), this is NOT a bug but requirement of C socket functions. + */ + [[nodiscard]] size_t size(); + + /** + * @brief Get the port bound to a file descriptor + * @param fd File descriptor + * @return Port number, or 0 if no port bound + */ + [[nodiscard]] uint16_t get_port(socket fd); +}; + +/** + * @brief Allocates a dpp::socket, closing it on destruction + */ +struct DPP_EXPORT raii_socket { + /** + * @brief File descriptor + */ + socket fd; + + /** + * @brief Construct a socket. + * Calls socket() and returns a new file descriptor + */ + raii_socket(); + + /** + * @brief Non-copyable + */ + raii_socket(raii_socket&) = delete; + + /** + * @brief Non-movable + */ + raii_socket(raii_socket&&) = delete; + + /** + * @brief Non-copyable + */ + raii_socket operator=(raii_socket&) = delete; + + /** + * @brief Non-movable + */ + raii_socket operator=(raii_socket&&) = delete; + + /** + * @brief Destructor + * Frees the socket by closing it + */ + ~raii_socket(); +}; + + +} diff --git a/include/dpp/sslclient.h b/include/dpp/sslclient.h index 8869aef420..00371ef287 100644 --- a/include/dpp/sslclient.h +++ b/include/dpp/sslclient.h @@ -242,7 +242,7 @@ class DPP_EXPORT ssl_client * @param data Data to be written to the buffer. * @note The data may not be written immediately and may be written at a later time to the socket. */ - virtual void write(const std::string_view data); + void socket_write(const std::string_view data); /** * @brief Close socket connection @@ -257,4 +257,4 @@ class DPP_EXPORT ssl_client virtual void log(dpp::loglevel severity, const std::string &msg) const; }; -} // namespace dpp +} diff --git a/include/dpp/stage_instance.h b/include/dpp/stage_instance.h index 6b0d2719a0..2bd90884a8 100644 --- a/include/dpp/stage_instance.h +++ b/include/dpp/stage_instance.h @@ -109,4 +109,4 @@ struct DPP_EXPORT stage_instance : public managed, public json_interface stage_instance_map; -} // namespace dpp +} diff --git a/include/dpp/stringops.h b/include/dpp/stringops.h index 9383fa4d06..cd99d9cc4f 100644 --- a/include/dpp/stringops.h +++ b/include/dpp/stringops.h @@ -220,4 +220,4 @@ template std::string leading_zeroes(T i, size_t width) return stream.str(); } -} // namespace dpp +} diff --git a/include/dpp/sync.h b/include/dpp/sync.h index 3c69f43a05..fdce669b2d 100644 --- a/include/dpp/sync.h +++ b/include/dpp/sync.h @@ -78,4 +78,4 @@ template T sync(class cluster* c, F func, Ts&& return _f.get(); } -} // namespace dpp +} diff --git a/include/dpp/timed_listener.h b/include/dpp/timed_listener.h index c5d7d0229b..d4dca777fc 100644 --- a/include/dpp/timed_listener.h +++ b/include/dpp/timed_listener.h @@ -102,4 +102,4 @@ template class timed_listene } }; -} // namespace dpp +} diff --git a/include/dpp/timer.h b/include/dpp/timer.h index e3e10943d1..c3dfbfa97e 100644 --- a/include/dpp/timer.h +++ b/include/dpp/timer.h @@ -129,4 +129,4 @@ class DPP_EXPORT oneshot_timer ~oneshot_timer(); }; -} // namespace dpp +} diff --git a/include/dpp/user.h b/include/dpp/user.h index cc7acadadd..57a8d73444 100644 --- a/include/dpp/user.h +++ b/include/dpp/user.h @@ -581,4 +581,4 @@ void from_json(const nlohmann::json& j, user_identified& u); */ typedef std::unordered_map user_map; -} // namespace dpp +} diff --git a/include/dpp/utility.h b/include/dpp/utility.h index f78d3b5b9e..cf6c417104 100644 --- a/include/dpp/utility.h +++ b/include/dpp/utility.h @@ -574,7 +574,7 @@ double DPP_EXPORT time_f(); /** * @brief Returns true if D++ was built with voice support * - * @return bool True if voice support is compiled in (libsodium/libopus) + * @return bool True if voice support is compiled in (libopus) */ bool DPP_EXPORT has_voice(); @@ -736,7 +736,7 @@ uint32_t DPP_EXPORT hsl(int h, int s, int l); * @param data The start of the data to display * @param length The length of data to display */ -std::string DPP_EXPORT debug_dump(uint8_t* data, size_t length); +std::string DPP_EXPORT debug_dump(const uint8_t* data, size_t length); /** * @brief Returns the length of a UTF-8 string in codepoints. @@ -1066,4 +1066,4 @@ struct alignas(T) dummy { }; } // namespace utility -} // namespace dpp +} diff --git a/include/dpp/version.h b/include/dpp/version.h index 92941b5eee..38601074b4 100644 --- a/include/dpp/version.h +++ b/include/dpp/version.h @@ -21,10 +21,10 @@ ************************************************************************************/ #pragma once -#if !defined(DPP_VERSION_LONG) -#define DPP_VERSION_LONG 0x00100031 -#define DPP_VERSION_SHORT 100031 -#define DPP_VERSION_TEXT "D++ 10.0.31 (19-May-2024)" +#ifndef DPP_VERSION_LONG +#define DPP_VERSION_LONG 0x00100034 +#define DPP_VERSION_SHORT 100034 +#define DPP_VERSION_TEXT "D++ 10.0.34 (20-Oct-2024)" #define DPP_VERSION_MAJOR ((DPP_VERSION_LONG & 0x00ff0000) >> 16) #define DPP_VERSION_MINOR ((DPP_VERSION_LONG & 0x0000ff00) >> 8) diff --git a/include/dpp/voiceregion.h b/include/dpp/voiceregion.h index 15d9306294..f3fd56055c 100644 --- a/include/dpp/voiceregion.h +++ b/include/dpp/voiceregion.h @@ -123,4 +123,4 @@ class DPP_EXPORT voiceregion : public json_interface { */ typedef std::unordered_map voiceregion_map; -} // namespace dpp +} diff --git a/include/dpp/voicestate.h b/include/dpp/voicestate.h index b5f9b55bcf..dc29652524 100644 --- a/include/dpp/voicestate.h +++ b/include/dpp/voicestate.h @@ -176,4 +176,4 @@ class DPP_EXPORT voicestate : public json_interface { /** A container of voicestates */ typedef std::unordered_map voicestate_map; -} // namespace dpp +} diff --git a/include/dpp/webhook.h b/include/dpp/webhook.h index 304565c26b..bef620814b 100644 --- a/include/dpp/webhook.h +++ b/include/dpp/webhook.h @@ -196,4 +196,4 @@ class DPP_EXPORT webhook : public managed, public json_interface { */ typedef std::unordered_map webhook_map; -} // namespace dpp +} diff --git a/include/dpp/wsclient.h b/include/dpp/wsclient.h index 98b48aa3de..fbbf72dee7 100644 --- a/include/dpp/wsclient.h +++ b/include/dpp/wsclient.h @@ -91,7 +91,12 @@ enum ws_opcode : uint8_t { /** * @brief Low level pong. */ - OP_PONG = 0x0a + OP_PONG = 0x0a, + + /** + * @brief Automatic selection of type + */ + OP_AUTO = 0xff, }; /** @@ -188,8 +193,10 @@ class DPP_EXPORT websocket_client : public ssl_client { /** * @brief Write to websocket. Encapsulates data in frames if the status is CONNECTED. * @param data The data to send. + * @param _opcode The opcode of the data to send, either binary or text. The default + * is to use the socket's opcode as set in the constructor. */ - virtual void write(const std::string_view data); + virtual void write(const std::string_view data, ws_opcode _opcode = OP_AUTO); /** * @brief Processes incoming frames from the SSL socket input buffer. @@ -206,9 +213,10 @@ class DPP_EXPORT websocket_client : public ssl_client { * @brief Receives raw frame content only without headers * * @param buffer The buffer contents + * @param opcode Frame type, e.g. OP_TEXT, OP_BINARY * @return True if the frame was successfully handled. False if no valid frame is in the buffer. */ - virtual bool handle_frame(const std::string& buffer); + virtual bool handle_frame(const std::string& buffer, ws_opcode opcode); /** * @brief Called upon error frame. @@ -229,4 +237,4 @@ class DPP_EXPORT websocket_client : public ssl_client { void send_close_packet(); }; -} // namespace dpp +} diff --git a/library-vcpkg/CMakeLists.txt b/library-vcpkg/CMakeLists.txt index dded1b52a5..5e7b9ae6d7 100644 --- a/library-vcpkg/CMakeLists.txt +++ b/library-vcpkg/CMakeLists.txt @@ -1,11 +1,29 @@ -file(GLOB THE_SOURCES "${DPP_ROOT_PATH}/src/dpp/events/*.cpp" "${DPP_ROOT_PATH}/src/dpp/cluster/*.cpp" "${DPP_ROOT_PATH}/src/dpp/*.cpp" "${DPP_ROOT_PATH}/src/dpp/*.rc") +# +# D++ (DPP), The Lightweight C++ Discord Library +# +# Copyright 2021 Craig Edwards +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +add_compile_definitions(HAVE_VOICE) + +file(GLOB THE_SOURCES "${DPP_ROOT_PATH}/src/dpp/events/*.cpp" "${DPP_ROOT_PATH}/src/dpp/voice/enabled/*.cpp" "${DPP_ROOT_PATH}/src/dpp/dave/*.cpp" "${DPP_ROOT_PATH}/src/dpp/cluster/*.cpp" "${DPP_ROOT_PATH}/src/dpp/*.cpp" "${DPP_ROOT_PATH}/src/dpp/*.rc") set(LIB_NAME "${PROJECT_NAME}") -if(WIN32) - add_library("${LIB_NAME}" SHARED "${THE_SOURCES}") -else() - add_library("${LIB_NAME}" STATIC "${THE_SOURCES}") +add_library("${LIB_NAME}" SHARED "${THE_SOURCES}") +if(NOT WIN32) find_package(Threads REQUIRED) endif() @@ -31,8 +49,12 @@ if(WIN32) add_compile_definitions(WIN32_LEAN_AND_MEAN) add_compile_definitions(_CRT_SECURE_NO_WARNINGS) add_compile_definitions(_CRT_NONSTDC_NO_DEPRECATE) + # Fake an ssl version number to satisfy MLSPP + set(OPENSSL_VERSION "1.1.1f") endif() + + target_compile_options( "${LIB_NAME}" PUBLIC @@ -40,8 +62,8 @@ target_compile_options( PRIVATE "$<$:$<$:/sdl;/Od;/DEBUG;/MP;/DFD_SETSIZE=1024>>" "$<$:$<$:/O2;/Oi;/Oy;/GL;/Gy;/sdl;/MP;/DFD_SETSIZE=1024>>" - "$<$:$<$:-Wall;-Wempty-body;-Wno-psabi;-Wunknown-pragmas;-Wignored-qualifiers;-Wimplicit-fallthrough;-Wmissing-field-initializers;-Wsign-compare;-Wtype-limits;-Wuninitialized;-Wshift-negative-value;-pthread;-g;-Og;-fPIC>>" - "$<$:$<$:-Wall;-Wempty-body;-Wno-psabi;-Wunknown-pragmas;-Wignored-qualifiers;-Wimplicit-fallthrough;-Wmissing-field-initializers;-Wsign-compare;-Wtype-limits;-Wuninitialized;-Wshift-negative-value;-pthread;-O3;-fPIC>>" + "$<$:$<$:-fPIC;-Wall;-Wempty-body;-Wno-deprecated-declarations;-Wno-psabi;-Wunknown-pragmas;-Wignored-qualifiers;-Wimplicit-fallthrough;-Wmissing-field-initializers;-Wsign-compare;-Wtype-limits;-Wuninitialized;-Wshift-negative-value;-pthread;-g;-Og;-fPIC>>" + "$<$:$<$:-fPIC;-Wall;-Wempty-body;-Wno-deprecated-declarations;-Wno-psabi;-Wunknown-pragmas;-Wignored-qualifiers;-Wimplicit-fallthrough;-Wmissing-field-initializers;-Wsign-compare;-Wtype-limits;-Wuninitialized;-Wshift-negative-value;-pthread;-O3;-fPIC>>" "${AVX_FLAG}" ) @@ -60,6 +82,12 @@ target_include_directories( "$" ) +find_package(OpenSSL REQUIRED) +add_subdirectory("${DPP_ROOT_PATH}/mlspp" "mlspp") +include_directories("${DPP_ROOT_PATH}/mlspp/include") +include_directories("${DPP_ROOT_PATH}/mlspp/lib/bytes/include") +include_directories("${DPP_ROOT_PATH}/mlspp/lib/hpke/include") + set_target_properties( "${LIB_NAME}" PROPERTIES OUTPUT_NAME "dpp" @@ -71,12 +99,13 @@ target_link_options( "$<$:$<$:/DEBUG>>" ) -add_compile_definitions(HAVE_VOICE) - find_package(nlohmann_json CONFIG REQUIRED) -find_package(OpenSSL REQUIRED) -find_package(Opus CONFIG REQUIRED) -find_package(unofficial-sodium CONFIG REQUIRED) +if (DPP_TEST_VCPKG) + include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/FindOpus.cmake") + find_package(Opus REQUIRED) +else() + find_package(Opus CONFIG REQUIRED) +endif() find_package(ZLIB REQUIRED) target_link_libraries( @@ -85,11 +114,20 @@ target_link_libraries( $<$:OpenSSL::SSL> $<$:OpenSSL::Crypto> $<$:Opus::opus> - $<$:unofficial-sodium::sodium> $<$:ZLIB::ZLIB> $<$:Threads::Threads> ) +# Private statically linked dependencies +target_link_libraries( + ${LIB_NAME} PRIVATE + mlspp + mls_vectors + bytes + tls_syntax + hpke +) + set(CONFIG_FILE_NAME "${PROJECT_NAME}Config.cmake") set(EXPORTED_TARGETS_NAME "${PROJECT_NAME}Targets") set(EXPORTED_TARGETS_FILE_NAME "${EXPORTED_TARGETS_NAME}.cmake") diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 00b2a8b651..d631d61f31 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -1,3 +1,20 @@ +# +# D++ (DPP), The Lightweight C++ Discord Library +# +# Copyright 2021 Craig Edwards +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + # Check for various C functions check_cxx_symbol_exists(prctl "sys/prctl.h" HAVE_PRCTL) check_cxx_symbol_exists(pthread_setname_np "pthread.h" HAVE_PTHREAD_SETNAME_NP) @@ -31,7 +48,30 @@ STRING(REPLACE "AVX" "" AVX_TYPE ${AVX_TYPE}) add_compile_definitions(AVX_TYPE=${AVX_TYPE}) add_compile_options(${AVX_FLAG}) +if(NOT BUILD_SHARED_LIBS) + if(UNIX) + message("-- Building ${Green}static${ColourReset} library.") + + if(UNIX AND NOT APPLE) + set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") + message("-- INFO: Changed lib suffix to ${CMAKE_FIND_LIBRARY_SUFFIXES}") + endif() + + set(OPENSSL_USE_STATIC_LIBS TRUE) + set(OPUS_USE_STATIC_LIBS TRUE) + + else() + message(WARNING "-- Building of static library not supported on non UNIX systems.") + endif() +else() + message("-- Building ${Green}dynamic${ColourReset} library.") +endif() + + + if(WIN32 AND NOT MINGW) + # Fake an ssl version number to satisfy MLSPP + set(OPENSSL_VERSION "1.1.1f") if (NOT WINDOWS_32_BIT) message("-- Building for windows with precompiled packaged dependencies") #set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE) @@ -44,17 +84,12 @@ if(WIN32 AND NOT MINGW) link_libraries("${CMAKE_CURRENT_SOURCE_DIR}/../win32/lib/libssl.lib") link_libraries("${CMAKE_CURRENT_SOURCE_DIR}/../win32/lib/libcrypto.lib") link_libraries("${CMAKE_CURRENT_SOURCE_DIR}/../win32/lib/zlib.lib") - link_libraries("${CMAKE_CURRENT_SOURCE_DIR}/../win32/lib/libsodium.lib") link_libraries("${CMAKE_CURRENT_SOURCE_DIR}/../win32/lib/opus.lib") set(OPUS_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/../win32/include") set(OPUS_LIBRARIES "${CMAKE_CURRENT_SOURCE_DIR}/../win32/lib/opus.lib") - set(sodium_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../win32/include") - set(sodium_LIBRARY_DEBUG "${CMAKE_CURRENT_SOURCE_DIR}/../win32/lib/libsodium.lib") - set(sodium_LIBRARY_RELEASE "${CMAKE_CURRENT_SOURCE_DIR}/../win32/lib/libsodium.lib") set(HAVE_OPUS_OPUS_H "${CMAKE_CURRENT_SOURCE_DIR}/../win32/include/opus/opus.h") set(OPUS_FOUND 1) - SET(sodium_VERSION_STRING "win32 bundled") include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../win32/include") @@ -95,48 +130,21 @@ if (UNIX) endif() endif() -if(NOT BUILD_SHARED_LIBS) - if(UNIX) - message("-- Building static library.") - - if(UNIX AND NOT APPLE) - set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") - endif() - - set(OPENSSL_USE_STATIC_LIBS ON) - set(sodium_USE_STATIC_LIBS ON) - set(OPUS_USE_STATIC_LIBS TRUE) - else() - message(WARNING "-- Building of static library not supported on non UNIX systems.") - endif() -endif() - if (BUILD_VOICE_SUPPORT) if (MINGW OR NOT WIN32) - find_package(PkgConfig QUIET) - if(PKG_CONFIG_FOUND) - include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/FindSodium.cmake") - else() - message("-- Could not detect ${Green}libsodium${ColourReset} due to missing ${Green}pkgconfig${ColourReset}. VOICE support will be ${Red}disabled${ColourReset}") - endif() - include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/FindOpus.cmake") + include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/FindOpus.cmake") endif() - if(DEFINED OPUS_FOUND) + if(HAVE_OPUS_OPUS_H) message("-- Found Opus ${Green}${OPUS_LIBRARIES}${ColourReset}") - if((WIN32 OR PKG_CONFIG_FOUND) AND DEFINED sodium_VERSION_STRING AND DEFINED sodium_LIBRARY_RELEASE) - add_compile_definitions(HAVE_VOICE) - - message("-- Sodium ${Green}${sodium_VERSION_STRING}${ColourReset}") - - set(HAVE_VOICE 1) - endif() + add_compile_definitions(HAVE_VOICE) + set(HAVE_VOICE 1) endif() if(HAVE_VOICE) - message("-- Detected ${Green}libsodium${ColourReset} and ${Green}libopus${ColourReset}. VOICE support will be ${Green}enabled${ColourReset}") + message("-- Detected ${Green}libopus${ColourReset}. VOICE support will be ${Green}enabled${ColourReset}") else(HAVE_VOICE) - message("-- Could not detect ${Green}libsodium${ColourReset} and/or ${Green}libopus${ColourReset}. VOICE support will be ${Red}disabled${ColourReset}") + message("-- Could not detect ${Green}libopus${ColourReset}. VOICE support will be ${Red}disabled${ColourReset}") endif(HAVE_VOICE) else() message("-- Voice support disabled by cmake option") @@ -149,6 +157,7 @@ set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) if(MINGW OR NOT WIN32) find_package(ZLIB REQUIRED) + message("-- ZLIB: ${Green}${ZLIB_LIBRARIES}${ColourReset}") endif(MINGW OR NOT WIN32) if(APPLE) @@ -160,6 +169,9 @@ if(APPLE) find_package(OpenSSL REQUIRED) else() if(MINGW OR NOT WIN32) + if(NOT BUILD_SHARED_LIBS) + set(OPENSSL_USE_STATIC_LIBS TRUE) + endif() find_package(OpenSSL REQUIRED) endif() endif() @@ -197,7 +209,7 @@ if(MSVC) endif() string(REGEX REPLACE "/W[1|2|3|4]" "/W3" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-unused-private-field -Wno-psabi -Wempty-body -Wignored-qualifiers -Wimplicit-fallthrough -Wmissing-field-initializers -Wsign-compare -Wtype-limits -Wuninitialized -Wshift-negative-value -pthread") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wall -Wno-unused-private-field -Wno-psabi -Wempty-body -Wignored-qualifiers -Wimplicit-fallthrough -Wmissing-field-initializers -Wsign-compare -Wtype-limits -Wuninitialized -Wshift-negative-value -pthread") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og") if (NOT MINGW) @@ -209,11 +221,22 @@ set(modules_dir "../src") file(GLOB subdirlist ${modules_dir}/dpp) +if (HAVE_VOICE) + add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../mlspp" "mlspp") + include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../mlspp/include") + include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../mlspp/lib/bytes/include") + include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../mlspp/lib/hpke/include") +endif() + foreach (fullmodname ${subdirlist}) get_filename_component(modname ${fullmodname} NAME) set (modsrc "") - - file(GLOB modsrc "${modules_dir}/dpp/*.cpp" "${modules_dir}/dpp/events/*.cpp" "${modules_dir}/dpp/cluster/*.cpp" "${modules_dir}/dpp/*.rc") + + if (HAVE_VOICE) + file(GLOB modsrc "${modules_dir}/dpp/*.cpp" "${modules_dir}/dpp/voice/enabled/*.cpp" "${modules_dir}/dpp/dave/*.cpp" "${modules_dir}/dpp/events/*.cpp" "${modules_dir}/dpp/cluster/*.cpp" "${modules_dir}/dpp/*.rc") + else() + file(GLOB modsrc "${modules_dir}/dpp/*.cpp" "${modules_dir}/dpp/voice/stub/*.cpp" "${modules_dir}/dpp/events/*.cpp" "${modules_dir}/dpp/cluster/*.cpp" "${modules_dir}/dpp/*.rc") + endif() if(BUILD_SHARED_LIBS) add_library(${modname} SHARED ${modsrc}) @@ -241,16 +264,15 @@ foreach (fullmodname ${subdirlist}) endif() if (WIN32 AND NOT MINGW) + if (NOT WINDOWS_32_BIT) target_link_libraries(${modname} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../win32/lib/libssl.lib" "${CMAKE_CURRENT_SOURCE_DIR}/../win32/lib/libcrypto.lib" - "${CMAKE_CURRENT_SOURCE_DIR}/../win32/lib/libsodium.lib" "${CMAKE_CURRENT_SOURCE_DIR}/../win32/lib/opus.lib" "${CMAKE_CURRENT_SOURCE_DIR}/../win32/lib/zlib.lib") else() target_link_libraries(${modname} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../win32/32/lib/libssl.lib" "${CMAKE_CURRENT_SOURCE_DIR}/../win32/32/lib/libcrypto.lib" - "${CMAKE_CURRENT_SOURCE_DIR}/../win32/32/lib/libsodium.lib" "${CMAKE_CURRENT_SOURCE_DIR}/../win32/32/lib/opus.lib" "${CMAKE_CURRENT_SOURCE_DIR}/../win32/32/lib/zlib.lib") endif() @@ -263,12 +285,34 @@ foreach (fullmodname ${subdirlist}) endif() if (HAVE_VOICE) - target_link_libraries(${modname} PUBLIC ${sodium_LIBRARY_RELEASE} ${OPUS_LIBRARIES}) - - include_directories(${OPUS_INCLUDE_DIRS} ${sodium_INCLUDE_DIR}) + target_link_libraries(${modname} PUBLIC ${OPUS_LIBRARIES}) + include_directories(${OPUS_INCLUDE_DIRS}) endif() endforeach() +if (HAVE_VOICE) + # Private statically linked dependencies + if(NOT BUILD_SHARED_LIBS AND NOT APPLE) + target_link_libraries(dpp PRIVATE + mlspp.a + mls_vectors.a + bytes.a + tls_syntax.a + hpke.a + ) + message("-- INFO: Linking static dependencies") + else() + target_link_libraries(dpp PRIVATE + mlspp + mls_vectors + bytes + tls_syntax + hpke + ) + message("-- INFO: Linking dynamic dependencies") + endif() +endif() + target_compile_features(dpp PUBLIC cxx_std_17) target_compile_features(dpp PRIVATE cxx_constexpr) target_compile_features(dpp PRIVATE cxx_auto_type) @@ -289,7 +333,7 @@ if(HAVE_PTHREAD_SETNAME_NP) endif() if(DPP_CORO) - message("-- ${Yellow}Enabled experimental coroutine feature${ColourReset}") + message("-- ${Yellow}Enabled coroutine feature${ColourReset}") set(CMAKE_CXX_STANDARD 20) target_compile_features(dpp PUBLIC cxx_std_20) if(WIN32 AND NOT MINGW AND NOT DPP_CLANG_CL) @@ -345,6 +389,22 @@ if(DPP_FORMATTERS) target_compile_definitions(dpp PUBLIC DPP_FORMATTERS) endif() +if (NOT BUILD_SHARED_LIBS) + add_library(dppstatic STATIC + $ + $ + $ + $ + $ + $ + ) + if (HAVE_VOICE) + target_link_libraries(dppstatic ${ZLIB_LIBRARIES} ${OPENSSL_LIBRARIES} ${OPUS_LIBRARIES} -static-libgcc -static-libstdc++) + else() + target_link_libraries(dppstatic ${ZLIB_LIBRARIES} ${OPENSSL_LIBRARIES}) + endif() +endif() + if (DPP_BUILD_TEST) enable_testing(${CMAKE_CURRENT_SOURCE_DIR}/..) file(GLOB testnamelist "${CMAKE_CURRENT_SOURCE_DIR}/../src/*") @@ -363,11 +423,11 @@ if (DPP_BUILD_TEST) if (MSVC) target_compile_options(${testname} PRIVATE /utf-8) endif() - set (static_if_needed "") - if(NOT BUILD_SHARED_LIBS) - set (static_if_needed "-static") + if(BUILD_SHARED_LIBS) + target_link_libraries(${testname} PUBLIC ${modname}) + else() + target_link_libraries(${testname} PUBLIC dppstatic) endif() - target_link_libraries(${testname} PUBLIC ${modname} ${static_if_needed}) endif() endforeach() add_test( @@ -381,13 +441,11 @@ if(WIN32 AND NOT MINGW) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/../win32/bin/zlib1.dll" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" COPYONLY) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/../win32/bin/libcrypto-1_1-x64.dll" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" COPYONLY) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/../win32/bin/libssl-1_1-x64.dll" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" COPYONLY) - configure_file("${CMAKE_CURRENT_SOURCE_DIR}/../win32/bin/libsodium.dll" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" COPYONLY) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/../win32/bin/opus.dll" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" COPYONLY) else() configure_file("${CMAKE_CURRENT_SOURCE_DIR}/../win32/32/bin/zlib1.dll" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" COPYONLY) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/../win32/32/bin/libcrypto-1_1.dll" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" COPYONLY) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/../win32/32/bin/libssl-1_1.dll" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" COPYONLY) - configure_file("${CMAKE_CURRENT_SOURCE_DIR}/../win32/32/bin/libsodium.dll" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" COPYONLY) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/../win32/32/bin/opus.dll" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" COPYONLY) endif() endif() diff --git a/mlspp/CMakeLists.txt b/mlspp/CMakeLists.txt new file mode 100755 index 0000000000..52f26b5ba6 --- /dev/null +++ b/mlspp/CMakeLists.txt @@ -0,0 +1,117 @@ +cmake_minimum_required(VERSION 3.13) + +project(mlspp + VERSION 0.1 + LANGUAGES CXX +) + +option(TESTING "Build tests" OFF) +option(CLANG_TIDY "Perform linting with clang-tidy" OFF) +option(SANITIZERS "Enable sanitizers" OFF) +option(MLS_NAMESPACE_SUFFIX "Namespace Suffix for CXX and CMake Export") +option(DISABLE_GREASE "Disables the inclusion of MLS protocol recommended GREASE values" ON) +option(REQUIRE_BORINGSSL "Require BoringSSL instead of OpenSSL" OFF) + +if(MLS_NAMESPACE_SUFFIX) + set(MLS_CXX_NAMESPACE "mls_${MLS_NAMESPACE_SUFFIX}" CACHE STRING "Top-level Namespace for CXX") + set(MLS_EXPORT_NAMESPACE "MLSPP${MLS_NAMESPACE_SUFFIX}" CACHE STRING "Namespace for CMake Export") +else() + set(MLS_CXX_NAMESPACE "../include/dpp/mlspp/mls" CACHE STRING "Top-level Namespace for CXX") + set(MLS_EXPORT_NAMESPACE "MLSPP" CACHE STRING "Namespace for CMake Export") +endif() +message(STATUS "CXX Namespace: ${MLS_CXX_NAMESPACE}") +message(STATUS "CMake Export Namespace: ${MLS_EXPORT_NAMESPACE}") + + +### +### Global Config +### +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +configure_file( + "cmake/namespace.h.in" + "${CMAKE_CURRENT_SOURCE_DIR}/include/namespace.h" + @ONLY +) + +include(CheckCXXCompilerFlag) +include(CMakePackageConfigHelpers) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") + add_compile_options(-Wall -fPIC) +elseif(MSVC) + add_compile_options(/W2) + add_definitions(-DWINDOWS) + + # MSVC helpfully recommends safer equivalents for things like + # getenv, but they are not portable. + add_definitions(-D_CRT_SECURE_NO_WARNINGS) +endif() + +if("$ENV{MACOSX_DEPLOYMENT_TARGET}" STREQUAL "10.11") + add_compile_options(-DVARIANT_COMPAT) +endif() + +add_compile_options(-DDISABLE_GREASE) + +### +### Dependencies +### + +# Configure vcpkg to only build release libraries +set(VCPKG_BUILD_TYPE release) + +if (${OPENSSL_VERSION} VERSION_GREATER_EQUAL 3) + add_compile_definitions(WITH_OPENSSL3) +elseif(${OPENSSL_VERSION} VERSION_LESS 1.1.1) + message(FATAL_ERROR "OpenSSL 1.1.1 or greater is required") +endif() +message(STATUS "OpenSSL Found: ${OPENSSL_VERSION}") +message(STATUS "OpenSSL Include: ${OPENSSL_INCLUDE_DIR}") +message(STATUS "OpenSSL Libraries: ${OPENSSL_LIBRARIES}") + +# Internal libraries +add_subdirectory(lib) + +### +### Library Config +### + +set(LIB_NAME "${PROJECT_NAME}") + +file(GLOB_RECURSE LIB_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h") +file(GLOB_RECURSE LIB_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") + +add_library(${LIB_NAME} STATIC ${LIB_HEADERS} ${LIB_SOURCES}) +add_dependencies(${LIB_NAME} bytes tls_syntax hpke) +target_link_libraries(${LIB_NAME} bytes tls_syntax hpke) +target_include_directories(${LIB_NAME} + PUBLIC + $ + $ + PRIVATE + ${OPENSSL_INCLUDE_DIR} +) + +### +### Exports +### +set(CMAKE_EXPORT_PACKAGE_REGISTRY ON) +export(PACKAGE ${MLS_EXPORT_NAMESPACE}) + +configure_package_config_file(cmake/config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/${MLS_EXPORT_NAMESPACE}Config.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_DATADIR}/${MLS_EXPORT_NAMESPACE} + NO_SET_AND_CHECK_MACRO) + +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/${MLS_EXPORT_NAMESPACE}ConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion) + +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/lib/bytes/include") +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/lib/hpke/include") +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/lib/mls_vectors/include") +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/lib/tls_syntax/include") diff --git a/mlspp/LICENSE b/mlspp/LICENSE new file mode 100755 index 0000000000..044f922aa7 --- /dev/null +++ b/mlspp/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2018, Cisco Systems +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/mlspp/cmake/config.cmake.in b/mlspp/cmake/config.cmake.in new file mode 100755 index 0000000000..82145af779 --- /dev/null +++ b/mlspp/cmake/config.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include(${CMAKE_CURRENT_LIST_DIR}/@MLS_EXPORT_NAMESPACE@Targets.cmake) +check_required_components(mlspp) diff --git a/mlspp/cmake/namespace.h.in b/mlspp/cmake/namespace.h.in new file mode 100755 index 0000000000..79dfad2cf5 --- /dev/null +++ b/mlspp/cmake/namespace.h.in @@ -0,0 +1,4 @@ +#pragma once + +// Configurable top-level MLS namespace +#define MLS_NAMESPACE @MLS_CXX_NAMESPACE@ diff --git a/mlspp/include/mls/common.h b/mlspp/include/mls/common.h new file mode 100755 index 0000000000..a32f9ef730 --- /dev/null +++ b/mlspp/include/mls/common.h @@ -0,0 +1,274 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +using namespace std::literals::string_literals; + +// Expose the bytes library globally +#include +using namespace mlspp::bytes_ns; + +// Expose the compatibility library globally +#include +namespace var = mlspp::tls::var; +namespace opt = mlspp::tls::opt; + +namespace mlspp { + +// Make variant equality work in the same way as optional equality, with +// automatic unwrapping. In other words +// +// v == T(x) <=> hold_alternative(v) && get(v) == x +// +// For consistency, we also define symmetric and negated version. In this +// house, we obey the symmetric law of equivalence relations! +template +bool +operator==(const var::variant& v, const T& t) +{ + return var::visit( + [&](const auto& arg) { + using U = std::decay_t; + if constexpr (std::is_same_v) { + return arg == t; + } else { + return false; + } + }, + v); +} + +template +bool +operator==(const T& t, const var::variant& v) +{ + return v == t; +} + +template +bool +operator!=(const var::variant& v, const T& t) +{ + return !(v == t); +} + +template +bool +operator!=(const T& t, const var::variant& v) +{ + return !(v == t); +} + +using epoch_t = uint64_t; + +/// +/// Get the current system clock time in the format MLS expects +/// + +uint64_t +seconds_since_epoch(); + +/// +/// Easy construction of overloaded lambdas +/// + +template +struct overloaded : Ts... +{ + using Ts::operator()...; + + // XXX(RLB) MSVC has a bug where it incorrectly computes the size of this + // type. Microsoft claims they have fixed it in the latest MSVC, and GitHub + // claims they are running a version with the fix. But in practice, we still + // hit it. Including this dummy variable is a work-around. + // + // https://developercommunity.visualstudio.com/t/runtime-stack-corruption-using-stdvisit/346200 + int dummy = 0; +}; + +// clang-format off +// XXX(RLB): For some reason, different versions of clang-format disagree on how +// this should be formatted. Probably because it's new syntax with C++17? +// Exempting it from clang-format for now. +template overloaded(Ts...) -> overloaded; +// clang-format on + +/// +/// Auto-generate equality and inequality operators for TLS-serializable things +/// + +template +inline typename std::enable_if::type +operator==(const T& lhs, const T& rhs) +{ + return lhs._tls_fields_w() == rhs._tls_fields_w(); +} + +template +inline typename std::enable_if::type +operator!=(const T& lhs, const T& rhs) +{ + return lhs._tls_fields_w() != rhs._tls_fields_w(); +} + +/// +/// Error types +/// + +// The `using parent = X` / `using parent::parent` construction here +// imports the constructors of the parent. + +class NotImplementedError : public std::exception +{ +public: + using parent = std::exception; + using parent::parent; +}; + +class ProtocolError : public std::runtime_error +{ +public: + using parent = std::runtime_error; + using parent::parent; +}; + +class IncompatibleNodesError : public std::invalid_argument +{ +public: + using parent = std::invalid_argument; + using parent::parent; +}; + +class InvalidParameterError : public std::invalid_argument +{ +public: + using parent = std::invalid_argument; + using parent::parent; +}; + +class InvalidPathError : public std::invalid_argument +{ +public: + using parent = std::invalid_argument; + using parent::parent; +}; + +class InvalidIndexError : public std::invalid_argument +{ +public: + using parent = std::invalid_argument; + using parent::parent; +}; + +class InvalidMessageTypeError : public std::invalid_argument +{ +public: + using parent = std::invalid_argument; + using parent::parent; +}; + +class MissingNodeError : public std::out_of_range +{ +public: + using parent = std::out_of_range; + using parent::parent; +}; + +class MissingStateError : public std::out_of_range +{ +public: + using parent = std::out_of_range; + using parent::parent; +}; + +// A slightly more elegant way to silence -Werror=unused-variable +template +void +silence_unused(const T& val) +{ + (void)val; +} + +namespace stdx { + +// XXX(RLB) This method takes any container in, but always puts the resuls in +// std::vector. The output could be made generic with a Rust-like syntax, +// defining a PendingTransform object that caches the inputs, with a template +// `collect()` method that puts them in an output container. Which makes the +// calling syntax as follows: +// +// auto out = stdx::transform(in, f).collect(); +// +// (You always need the explicit specialization, even if assigning it to an +// explicitly typed variable, because C++ won't infer return types.) +// +// Given that the above syntax is pretty chatty, and we never need anything +// other than vectors here anyway, I have left this as-is. +template +std::vector +transform(const Container& c, const UnaryOperation& op) +{ + auto out = std::vector{}; + auto ins = std::inserter(out, out.begin()); + std::transform(c.begin(), c.end(), ins, op); + return out; +} + +template +bool +any_of(const Container& c, const UnaryPredicate& pred) +{ + return std::any_of(c.begin(), c.end(), pred); +} + +template +bool +all_of(const Container& c, const UnaryPredicate& pred) +{ + return std::all_of(c.begin(), c.end(), pred); +} + +template +auto +count_if(const Container& c, const UnaryPredicate& pred) +{ + return std::count_if(c.begin(), c.end(), pred); +} + +template +bool +contains(const Container& c, const Value& val) +{ + return std::find(c.begin(), c.end(), val) != c.end(); +} + +template +auto +find_if(Container& c, const UnaryPredicate& pred) +{ + return std::find_if(c.begin(), c.end(), pred); +} + +template +auto +find_if(const Container& c, const UnaryPredicate& pred) +{ + return std::find_if(c.begin(), c.end(), pred); +} + +template +auto +upper_bound(const Container& c, const Value& val) +{ + return std::upper_bound(c.begin(), c.end(), val); +} + +} // namespace stdx + +} // namespace mlspp diff --git a/mlspp/include/mls/core_types.h b/mlspp/include/mls/core_types.h new file mode 100755 index 0000000000..6c591c0623 --- /dev/null +++ b/mlspp/include/mls/core_types.h @@ -0,0 +1,380 @@ +#pragma once + +#include "mls/credential.h" +#include "mls/crypto.h" +#include "mls/tree_math.h" + +namespace mlspp { + +// enum { +// reserved(0), +// mls10(1), +// (255) +// } ProtocolVersion; +enum class ProtocolVersion : uint16_t +{ + mls10 = 0x01, +}; + +extern const std::array all_supported_versions; + +// struct { +// ExtensionType extension_type; +// opaque extension_data; +// } Extension; +struct Extension +{ + using Type = uint16_t; + + Type type; + bytes data; + + TLS_SERIALIZABLE(type, data) +}; + +struct ExtensionType +{ + static constexpr Extension::Type application_id = 1; + static constexpr Extension::Type ratchet_tree = 2; + static constexpr Extension::Type required_capabilities = 3; + static constexpr Extension::Type external_pub = 4; + static constexpr Extension::Type external_senders = 5; + + // XXX(RLB) There is no IANA-registered type for this extension yet, so we use + // a value from the vendor-specific space + static constexpr Extension::Type sframe_parameters = 0xff02; +}; + +struct ExtensionList +{ + std::vector extensions; + + // XXX(RLB) It would be good if this maintained extensions in order. It might + // be possible to do this automatically by changing the storage to a + // map and extending the TLS code to marshal that type. + template + inline void add(const T& obj) + { + auto data = tls::marshal(obj); + add(T::type, std::move(data)); + } + + void add(Extension::Type type, bytes data); + + template + std::optional find() const + { + for (const auto& ext : extensions) { + if (ext.type == T::type) { + return tls::get(ext.data); + } + } + + return std::nullopt; + } + + bool has(uint16_t type) const; + + TLS_SERIALIZABLE(extensions) +}; + +// enum { +// reserved(0), +// key_package(1), +// update(2), +// commit(3), +// (255) +// } LeafNodeSource; +enum struct LeafNodeSource : uint8_t +{ + key_package = 1, + update = 2, + commit = 3, +}; + +// struct { +// ProtocolVersion versions; +// CipherSuite ciphersuites; +// ExtensionType extensions; +// ProposalType proposals; +// CredentialType credentials; +// } Capabilities; +struct Capabilities +{ + std::vector versions; + std::vector cipher_suites; + std::vector extensions; + std::vector proposals; + std::vector credentials; + + static Capabilities create_default(); + bool extensions_supported(const std::vector& required) const; + bool proposals_supported(const std::vector& required) const; + bool credential_supported(const Credential& credential) const; + + template + bool credentials_supported(const Container& required) const + { + return stdx::all_of(required, [&](CredentialType type) { + return stdx::contains(credentials, type); + }); + } + + TLS_SERIALIZABLE(versions, cipher_suites, extensions, proposals, credentials) +}; + +// struct { +// uint64 not_before; +// uint64 not_after; +// } Lifetime; +struct Lifetime +{ + uint64_t not_before; + uint64_t not_after; + + static Lifetime create_default(); + + TLS_SERIALIZABLE(not_before, not_after) +}; + +// struct { +// HPKEPublicKey encryption_key; +// SignaturePublicKey signature_key; +// Credential credential; +// Capabilities capabilities; +// +// LeafNodeSource leaf_node_source; +// select (leaf_node_source) { +// case add: +// Lifetime lifetime; +// +// case update: +// struct {} +// +// case commit: +// opaque parent_hash; +// } +// +// Extension extensions; +// // SignWithLabel(., "LeafNodeTBS", LeafNodeTBS) +// opaque signature; +// } LeafNode; +struct Empty +{ + TLS_SERIALIZABLE() +}; + +struct ParentHash +{ + bytes parent_hash; + TLS_SERIALIZABLE(parent_hash); +}; + +struct LeafNodeOptions +{ + std::optional credential; + std::optional capabilities; + std::optional extensions; +}; + +// TODO Move this to treekem.h +struct LeafNode +{ + HPKEPublicKey encryption_key; + SignaturePublicKey signature_key; + Credential credential; + Capabilities capabilities; + + var::variant content; + + ExtensionList extensions; + bytes signature; + + LeafNode() = default; + LeafNode(const LeafNode&) = default; + LeafNode(LeafNode&&) = default; + LeafNode& operator=(const LeafNode&) = default; + LeafNode& operator=(LeafNode&&) = default; + + LeafNode(CipherSuite cipher_suite, + HPKEPublicKey encryption_key_in, + SignaturePublicKey signature_key_in, + Credential credential_in, + Capabilities capabilities_in, + Lifetime lifetime_in, + ExtensionList extensions_in, + const SignaturePrivateKey& sig_priv); + + LeafNode for_update(CipherSuite cipher_suite, + const bytes& group_id, + LeafIndex leaf_index, + HPKEPublicKey encryption_key, + const LeafNodeOptions& opts, + const SignaturePrivateKey& sig_priv_in) const; + + LeafNode for_commit(CipherSuite cipher_suite, + const bytes& group_id, + LeafIndex leaf_index, + HPKEPublicKey encryption_key, + const bytes& parent_hash, + const LeafNodeOptions& opts, + const SignaturePrivateKey& sig_priv_in) const; + + void set_capabilities(Capabilities capabilities_in); + + LeafNodeSource source() const; + + struct MemberBinding + { + bytes group_id; + LeafIndex leaf_index; + TLS_SERIALIZABLE(group_id, leaf_index); + }; + + void sign(CipherSuite cipher_suite, + const SignaturePrivateKey& sig_priv, + const std::optional& binding); + bool verify(CipherSuite cipher_suite, + const std::optional& binding) const; + + bool verify_expiry(uint64_t now) const; + bool verify_extension_support(const ExtensionList& ext_list) const; + + TLS_SERIALIZABLE(encryption_key, + signature_key, + credential, + capabilities, + content, + extensions, + signature) + TLS_TRAITS(tls::pass, + tls::pass, + tls::pass, + tls::pass, + tls::variant, + tls::pass, + tls::pass) + +private: + LeafNode clone_with_options(HPKEPublicKey encryption_key, + const LeafNodeOptions& opts) const; + bytes to_be_signed(const std::optional& binding) const; +}; + +// Concrete extension types +struct RequiredCapabilitiesExtension +{ + std::vector extensions; + std::vector proposals; + + static const Extension::Type type; + TLS_SERIALIZABLE(extensions, proposals) +}; + +struct ApplicationIDExtension +{ + bytes id; + + static const Extension::Type type; + TLS_SERIALIZABLE(id) +}; + +/// +/// NodeType, ParentNode, and KeyPackage +/// + +// TODO move this to treekem.h +struct ParentNode +{ + HPKEPublicKey public_key; + bytes parent_hash; + std::vector unmerged_leaves; + + bytes hash(CipherSuite suite) const; + + TLS_SERIALIZABLE(public_key, parent_hash, unmerged_leaves) +}; + +// TODO Move this to messages.h +// struct { +// ProtocolVersion version; +// CipherSuite cipher_suite; +// HPKEPublicKey init_key; +// LeafNode leaf_node; +// Extension extensions; +// // SignWithLabel(., "KeyPackageTBS", KeyPackageTBS) +// opaque signature; +// } KeyPackage; +struct KeyPackage +{ + ProtocolVersion version; + CipherSuite cipher_suite; + HPKEPublicKey init_key; + LeafNode leaf_node; + ExtensionList extensions; + bytes signature; + + KeyPackage(); + KeyPackage(CipherSuite suite_in, + HPKEPublicKey init_key_in, + LeafNode leaf_node_in, + ExtensionList extensions_in, + const SignaturePrivateKey& sig_priv_in); + + KeyPackageRef ref() const; + + void sign(const SignaturePrivateKey& sig_priv); + bool verify() const; + + TLS_SERIALIZABLE(version, + cipher_suite, + init_key, + leaf_node, + extensions, + signature) + +private: + bytes to_be_signed() const; +}; + +/// +/// UpdatePath +/// + +// struct { +// HPKEPublicKey public_key; +// HPKECiphertext encrypted_path_secret; +// } UpdatePathNode; +struct UpdatePathNode +{ + HPKEPublicKey public_key; + std::vector encrypted_path_secret; + + TLS_SERIALIZABLE(public_key, encrypted_path_secret) +}; + +// struct { +// LeafNode leaf_node; +// UpdatePathNode nodes; +// } UpdatePath; +struct UpdatePath +{ + LeafNode leaf_node; + std::vector nodes; + + TLS_SERIALIZABLE(leaf_node, nodes) +}; + +} // namespace mlspp + +namespace mlspp::tls { + +TLS_VARIANT_MAP(mlspp::LeafNodeSource, + mlspp::Lifetime, + key_package) +TLS_VARIANT_MAP(mlspp::LeafNodeSource, mlspp::Empty, update) +TLS_VARIANT_MAP(mlspp::LeafNodeSource, + mlspp::ParentHash, + commit) + +} // namespace mlspp::tls diff --git a/mlspp/include/mls/credential.h b/mlspp/include/mls/credential.h new file mode 100755 index 0000000000..bcf77a36cc --- /dev/null +++ b/mlspp/include/mls/credential.h @@ -0,0 +1,228 @@ +#pragma once + +#include +#include + +namespace mlspp { + +namespace hpke { +struct UserInfoVC; +} + +// struct { +// opaque identity<0..2^16-1>; +// SignaturePublicKey public_key; +// } BasicCredential; +struct BasicCredential +{ + BasicCredential() {} + + BasicCredential(bytes identity_in) + : identity(std::move(identity_in)) + { + } + + bytes identity; + + TLS_SERIALIZABLE(identity) +}; + +struct X509Credential +{ + struct CertData + { + bytes data; + + TLS_SERIALIZABLE(data) + }; + + X509Credential() = default; + explicit X509Credential(const std::vector& der_chain_in); + + SignatureScheme signature_scheme() const; + SignaturePublicKey public_key() const; + bool valid_for(const SignaturePublicKey& pub) const; + + // TODO(rlb) This should be const or exposed via a method + std::vector der_chain; + +private: + SignaturePublicKey _public_key; + SignatureScheme _signature_scheme; +}; + +tls::ostream& +operator<<(tls::ostream& str, const X509Credential& obj); + +tls::istream& +operator>>(tls::istream& str, X509Credential& obj); + +struct UserInfoVCCredential +{ + UserInfoVCCredential() = default; + explicit UserInfoVCCredential(std::string userinfo_vc_jwt_in); + + std::string userinfo_vc_jwt; + + bool valid_for(const SignaturePublicKey& pub) const; + bool valid_from(const PublicJWK& pub) const; + + friend tls::ostream operator<<(tls::ostream& str, + const UserInfoVCCredential& obj); + friend tls::istream operator>>(tls::istream& str, UserInfoVCCredential& obj); + friend bool operator==(const UserInfoVCCredential& lhs, + const UserInfoVCCredential& rhs); + friend bool operator!=(const UserInfoVCCredential& lhs, + const UserInfoVCCredential& rhs); + +private: + std::shared_ptr _vc; +}; + +bool +operator==(const X509Credential& lhs, const X509Credential& rhs); + +enum struct CredentialType : uint16_t +{ + reserved = 0, + basic = 1, + x509 = 2, + + userinfo_vc_draft_00 = 0xFE00, + multi_draft_00 = 0xFF00, + + // GREASE values, included here mainly so that debugger output looks nice + GREASE_0 = 0x0A0A, + GREASE_1 = 0x1A1A, + GREASE_2 = 0x2A2A, + GREASE_3 = 0x3A3A, + GREASE_4 = 0x4A4A, + GREASE_5 = 0x5A5A, + GREASE_6 = 0x6A6A, + GREASE_7 = 0x7A7A, + GREASE_8 = 0x8A8A, + GREASE_9 = 0x9A9A, + GREASE_A = 0xAAAA, + GREASE_B = 0xBABA, + GREASE_C = 0xCACA, + GREASE_D = 0xDADA, + GREASE_E = 0xEAEA, +}; + +// struct { +// Credential credential; +// SignaturePublicKey credential_key; +// opaque signature; +// } CredentialBinding +// +// struct { +// CredentialBinding bindings; +// } MultiCredential; +struct CredentialBinding; +struct CredentialBindingInput; + +struct MultiCredential +{ + MultiCredential() = default; + MultiCredential(const std::vector& binding_inputs, + const SignaturePublicKey& signature_key); + + std::vector bindings; + + bool valid_for(const SignaturePublicKey& pub) const; + + TLS_SERIALIZABLE(bindings) +}; + +// struct { +// CredentialType credential_type; +// select (credential_type) { +// case basic: +// BasicCredential; +// +// case x509: +// opaque cert_data<1..2^24-1>; +// }; +// } Credential; +struct Credential +{ + Credential() = default; + + CredentialType type() const; + + template + const T& get() const + { + return var::get(_cred); + } + + static Credential basic(const bytes& identity); + static Credential x509(const std::vector& der_chain); + static Credential userinfo_vc(const std::string& userinfo_vc_jwt); + static Credential multi( + const std::vector& binding_inputs, + const SignaturePublicKey& signature_key); + + bool valid_for(const SignaturePublicKey& pub) const; + + TLS_SERIALIZABLE(_cred) + TLS_TRAITS(tls::variant) + +private: + using SpecificCredential = var::variant; + + Credential(SpecificCredential specific); + SpecificCredential _cred; +}; + +// XXX(RLB): This struct needs to appear below Credential so that all types are +// concrete at the appropriate points. +struct CredentialBindingInput +{ + CipherSuite cipher_suite; + Credential credential; + const SignaturePrivateKey& credential_priv; +}; + +struct CredentialBinding +{ + CipherSuite cipher_suite; + Credential credential; + SignaturePublicKey credential_key; + bytes signature; + + CredentialBinding() = default; + CredentialBinding(CipherSuite suite_in, + Credential credential_in, + const SignaturePrivateKey& credential_priv, + const SignaturePublicKey& signature_key); + + bool valid_for(const SignaturePublicKey& signature_key) const; + + TLS_SERIALIZABLE(cipher_suite, credential, credential_key, signature) + +private: + bytes to_be_signed(const SignaturePublicKey& signature_key) const; +}; + +} // namespace mlspp + +namespace mlspp::tls { + +TLS_VARIANT_MAP(mlspp::CredentialType, + mlspp::BasicCredential, + basic) +TLS_VARIANT_MAP(mlspp::CredentialType, + mlspp::X509Credential, + x509) +TLS_VARIANT_MAP(mlspp::CredentialType, + mlspp::UserInfoVCCredential, + userinfo_vc_draft_00) +TLS_VARIANT_MAP(mlspp::CredentialType, + mlspp::MultiCredential, + multi_draft_00) + +} // namespace mlspp::tls diff --git a/mlspp/include/mls/crypto.h b/mlspp/include/mls/crypto.h new file mode 100755 index 0000000000..f9924de02d --- /dev/null +++ b/mlspp/include/mls/crypto.h @@ -0,0 +1,266 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace mlspp { + +/// Signature Code points, borrowed from RFC 8446 +enum struct SignatureScheme : uint16_t +{ + ecdsa_secp256r1_sha256 = 0x0403, + ecdsa_secp384r1_sha384 = 0x0805, + ecdsa_secp521r1_sha512 = 0x0603, + ed25519 = 0x0807, + ed448 = 0x0808, + rsa_pkcs1_sha256 = 0x0401, +}; + +SignatureScheme +tls_signature_scheme(hpke::Signature::ID id); + +/// Cipher suites + +struct KeyAndNonce +{ + bytes key; + bytes nonce; +}; + +// opaque HashReference; +// HashReference KeyPackageRef; +// HashReference ProposalRef; +using HashReference = bytes; +using KeyPackageRef = HashReference; +using ProposalRef = HashReference; + +struct CipherSuite +{ + enum struct ID : uint16_t + { + unknown = 0x0000, + X25519_AES128GCM_SHA256_Ed25519 = 0x0001, + P256_AES128GCM_SHA256_P256 = 0x0002, + X25519_CHACHA20POLY1305_SHA256_Ed25519 = 0x0003, + X448_AES256GCM_SHA512_Ed448 = 0x0004, + P521_AES256GCM_SHA512_P521 = 0x0005, + X448_CHACHA20POLY1305_SHA512_Ed448 = 0x0006, + P384_AES256GCM_SHA384_P384 = 0x0007, + + // GREASE values, included here mainly so that debugger output looks nice + GREASE_0 = 0x0A0A, + GREASE_1 = 0x1A1A, + GREASE_2 = 0x2A2A, + GREASE_3 = 0x3A3A, + GREASE_4 = 0x4A4A, + GREASE_5 = 0x5A5A, + GREASE_6 = 0x6A6A, + GREASE_7 = 0x7A7A, + GREASE_8 = 0x8A8A, + GREASE_9 = 0x9A9A, + GREASE_A = 0xAAAA, + GREASE_B = 0xBABA, + GREASE_C = 0xCACA, + GREASE_D = 0xDADA, + GREASE_E = 0xEAEA, + }; + + CipherSuite(); + CipherSuite(ID id_in); + + ID cipher_suite() const { return id; } + SignatureScheme signature_scheme() const; + + size_t secret_size() const { return get().digest.hash_size; } + size_t key_size() const { return get().hpke.aead.key_size; } + size_t nonce_size() const { return get().hpke.aead.nonce_size; } + + bytes zero() const { return bytes(secret_size(), 0); } + const hpke::HPKE& hpke() const { return get().hpke; } + const hpke::Digest& digest() const { return get().digest; } + const hpke::Signature& sig() const { return get().sig; } + + bytes expand_with_label(const bytes& secret, + const std::string& label, + const bytes& context, + size_t length) const; + bytes derive_secret(const bytes& secret, const std::string& label) const; + bytes derive_tree_secret(const bytes& secret, + const std::string& label, + uint32_t generation, + size_t length) const; + + template + bytes ref(const T& value) const + { + return raw_ref(reference_label(), tls::marshal(value)); + } + + bytes raw_ref(const bytes& label, const bytes& value) const + { + // RefHash(label, value) = Hash(RefHashInput) + // + // struct { + // opaque label; + // opaque value; + // } RefHashInput; + auto w = tls::ostream(); + w << label << value; + return digest().hash(w.bytes()); + } + + TLS_SERIALIZABLE(id) + +private: + ID id; + + struct Ciphers + { + hpke::HPKE hpke; + const hpke::Digest& digest; + const hpke::Signature& sig; + }; + + const Ciphers& get() const; + + template + static const bytes& reference_label(); +}; + +#if WITH_BORINGSSL +extern const std::array all_supported_suites; +#else +extern const std::array all_supported_suites; +#endif + +// Utilities +using mlspp::hpke::random_bytes; + +// HPKE Keys +namespace encrypt_label { +extern const std::string update_path_node; +extern const std::string welcome; +} // namespace encrypt_label + +struct HPKECiphertext +{ + bytes kem_output; + bytes ciphertext; + + TLS_SERIALIZABLE(kem_output, ciphertext) +}; + +struct HPKEPublicKey +{ + bytes data; + + HPKECiphertext encrypt(CipherSuite suite, + const std::string& label, + const bytes& context, + const bytes& pt) const; + + std::tuple do_export(CipherSuite suite, + const bytes& info, + const std::string& label, + size_t size) const; + + TLS_SERIALIZABLE(data) +}; + +struct HPKEPrivateKey +{ + static HPKEPrivateKey generate(CipherSuite suite); + static HPKEPrivateKey parse(CipherSuite suite, const bytes& data); + static HPKEPrivateKey derive(CipherSuite suite, const bytes& secret); + + HPKEPrivateKey() = default; + + bytes data; + HPKEPublicKey public_key; + + bytes decrypt(CipherSuite suite, + const std::string& label, + const bytes& context, + const HPKECiphertext& ct) const; + + bytes do_export(CipherSuite suite, + const bytes& info, + const bytes& kem_output, + const std::string& label, + size_t size) const; + + void set_public_key(CipherSuite suite); + + TLS_SERIALIZABLE(data) + +private: + HPKEPrivateKey(bytes priv_data, bytes pub_data); +}; + +// Signature Keys +namespace sign_label { +extern const std::string mls_content; +extern const std::string leaf_node; +extern const std::string key_package; +extern const std::string group_info; +extern const std::string multi_credential; +} // namespace sign_label + +struct SignaturePublicKey +{ + static SignaturePublicKey from_jwk(CipherSuite suite, + const std::string& json_str); + + bytes data; + + bool verify(const CipherSuite& suite, + const std::string& label, + const bytes& message, + const bytes& signature) const; + + std::string to_jwk(CipherSuite suite) const; + + TLS_SERIALIZABLE(data) +}; + +struct PublicJWK +{ + SignatureScheme signature_scheme; + std::optional key_id; + SignaturePublicKey public_key; + + static PublicJWK parse(const std::string& jwk_json); +}; + +struct SignaturePrivateKey +{ + static SignaturePrivateKey generate(CipherSuite suite); + static SignaturePrivateKey parse(CipherSuite suite, const bytes& data); + static SignaturePrivateKey derive(CipherSuite suite, const bytes& secret); + static SignaturePrivateKey from_jwk(CipherSuite suite, + const std::string& json_str); + + SignaturePrivateKey() = default; + + bytes data; + SignaturePublicKey public_key; + + bytes sign(const CipherSuite& suite, + const std::string& label, + const bytes& message) const; + + void set_public_key(CipherSuite suite); + std::string to_jwk(CipherSuite suite) const; + + TLS_SERIALIZABLE(data) + +private: + SignaturePrivateKey(bytes priv_data, bytes pub_data); +}; + +} // namespace mlspp diff --git a/mlspp/include/mls/key_schedule.h b/mlspp/include/mls/key_schedule.h new file mode 100755 index 0000000000..b30dd766e4 --- /dev/null +++ b/mlspp/include/mls/key_schedule.h @@ -0,0 +1,205 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace mlspp { + +struct HashRatchet +{ + CipherSuite suite; + bytes next_secret; + uint32_t next_generation; + std::map cache; + + size_t key_size; + size_t nonce_size; + size_t secret_size; + + // These defaults are necessary for use with containers + HashRatchet() = default; + HashRatchet(const HashRatchet& other) = default; + HashRatchet(HashRatchet&& other) = default; + HashRatchet& operator=(const HashRatchet& other) = default; + HashRatchet& operator=(HashRatchet&& other) = default; + + HashRatchet(CipherSuite suite_in, bytes base_secret_in); + + std::tuple next(); + KeyAndNonce get(uint32_t generation); + void erase(uint32_t generation); +}; + +struct SecretTree +{ + SecretTree() = default; + SecretTree(CipherSuite suite_in, + LeafCount group_size_in, + bytes encryption_secret_in); + + bool has_leaf(LeafIndex sender) { return sender < group_size; } + + bytes get(LeafIndex sender); + +private: + CipherSuite suite; + LeafCount group_size; + NodeIndex root; + std::map secrets; + size_t secret_size; +}; + +using ReuseGuard = std::array; + +struct GroupKeySource +{ + enum struct RatchetType + { + handshake, + application, + }; + + GroupKeySource() = default; + GroupKeySource(CipherSuite suite_in, + LeafCount group_size, + bytes encryption_secret); + + bool has_leaf(LeafIndex sender) { return secret_tree.has_leaf(sender); } + + std::tuple next(ContentType content_type, + LeafIndex sender); + KeyAndNonce get(ContentType content_type, + LeafIndex sender, + uint32_t generation, + ReuseGuard reuse_guard); + void erase(ContentType type, LeafIndex sender, uint32_t generation); + +private: + CipherSuite suite; + SecretTree secret_tree; + + using Key = std::tuple; + std::map chains; + + HashRatchet& chain(RatchetType type, LeafIndex sender); + HashRatchet& chain(ContentType type, LeafIndex sender); + + static const std::array all_ratchet_types; +}; + +struct KeyScheduleEpoch +{ +private: + CipherSuite suite; + +public: + bytes joiner_secret; + bytes epoch_secret; + + bytes sender_data_secret; + bytes encryption_secret; + bytes exporter_secret; + bytes epoch_authenticator; + bytes external_secret; + bytes confirmation_key; + bytes membership_key; + bytes resumption_psk; + bytes init_secret; + + HPKEPrivateKey external_priv; + + KeyScheduleEpoch() = default; + + // Full initializer, used by invited joiner + static KeyScheduleEpoch joiner(CipherSuite suite_in, + const bytes& joiner_secret, + const std::vector& psks, + const bytes& context); + + // Ciphersuite-only initializer, used by external joiner + KeyScheduleEpoch(CipherSuite suite_in); + + // Initial epoch + KeyScheduleEpoch(CipherSuite suite_in, + const bytes& init_secret, + const bytes& context); + + static std::tuple external_init( + CipherSuite suite, + const HPKEPublicKey& external_pub); + bytes receive_external_init(const bytes& kem_output) const; + + KeyScheduleEpoch next(const bytes& commit_secret, + const std::vector& psks, + const std::optional& force_init_secret, + const bytes& context) const; + + GroupKeySource encryption_keys(LeafCount size) const; + bytes confirmation_tag(const bytes& confirmed_transcript_hash) const; + bytes do_export(const std::string& label, + const bytes& context, + size_t size) const; + PSKWithSecret resumption_psk_w_secret(ResumptionPSKUsage usage, + const bytes& group_id, + epoch_t epoch); + + static bytes make_psk_secret(CipherSuite suite, + const std::vector& psks); + static bytes welcome_secret(CipherSuite suite, + const bytes& joiner_secret, + const std::vector& psks); + static KeyAndNonce sender_data_keys(CipherSuite suite, + const bytes& sender_data_secret, + const bytes& ciphertext); + + // TODO(RLB) make these methods private, but accessible to test vectors + KeyScheduleEpoch(CipherSuite suite_in, + const bytes& init_secret, + const bytes& commit_secret, + const bytes& psk_secret, + const bytes& context); + KeyScheduleEpoch next_raw(const bytes& commit_secret, + const bytes& psk_secret, + const std::optional& force_init_secret, + const bytes& context) const; + static bytes welcome_secret_raw(CipherSuite suite, + const bytes& joiner_secret, + const bytes& psk_secret); + +private: + KeyScheduleEpoch(CipherSuite suite_in, + const bytes& joiner_secret, + const bytes& psk_secret, + const bytes& context); +}; + +bool +operator==(const KeyScheduleEpoch& lhs, const KeyScheduleEpoch& rhs); + +struct TranscriptHash +{ + CipherSuite suite; + bytes confirmed; + bytes interim; + + // For a new group + TranscriptHash(CipherSuite suite_in); + + // For joining a group + TranscriptHash(CipherSuite suite_in, + bytes confirmed_in, + const bytes& confirmation_tag); + + void update(const AuthenticatedContent& content_auth); + void update_confirmed(const AuthenticatedContent& content_auth); + void update_interim(const bytes& confirmation_tag); + void update_interim(const AuthenticatedContent& content_auth); +}; + +bool +operator==(const TranscriptHash& lhs, const TranscriptHash& rhs); + +} // namespace mlspp diff --git a/mlspp/include/mls/messages.h b/mlspp/include/mls/messages.h new file mode 100755 index 0000000000..583ccc2733 --- /dev/null +++ b/mlspp/include/mls/messages.h @@ -0,0 +1,752 @@ +#pragma once + +#include "mls/common.h" +#include "mls/core_types.h" +#include "mls/credential.h" +#include "mls/crypto.h" +#include "mls/treekem.h" +#include +#include + +namespace mlspp { + +struct ExternalPubExtension +{ + HPKEPublicKey external_pub; + + static const uint16_t type; + TLS_SERIALIZABLE(external_pub) +}; + +struct RatchetTreeExtension +{ + TreeKEMPublicKey tree; + + static const uint16_t type; + TLS_SERIALIZABLE(tree) +}; + +struct ExternalSender +{ + SignaturePublicKey signature_key; + Credential credential; + + TLS_SERIALIZABLE(signature_key, credential); +}; + +struct ExternalSendersExtension +{ + std::vector senders; + + static const uint16_t type; + TLS_SERIALIZABLE(senders); +}; + +struct SFrameParameters +{ + uint16_t cipher_suite; + uint8_t epoch_bits; + + static const uint16_t type; + TLS_SERIALIZABLE(cipher_suite, epoch_bits) +}; + +struct SFrameCapabilities +{ + std::vector cipher_suites; + + bool compatible(const SFrameParameters& params) const; + + static const uint16_t type; + TLS_SERIALIZABLE(cipher_suites) +}; + +/// +/// PSKs +/// +enum struct PSKType : uint8_t +{ + reserved = 0, + external = 1, + resumption = 2, +}; + +struct ExternalPSK +{ + bytes psk_id; + TLS_SERIALIZABLE(psk_id) +}; + +enum struct ResumptionPSKUsage : uint8_t +{ + reserved = 0, + application = 1, + reinit = 2, + branch = 3, +}; + +struct ResumptionPSK +{ + ResumptionPSKUsage usage; + bytes psk_group_id; + epoch_t psk_epoch; + TLS_SERIALIZABLE(usage, psk_group_id, psk_epoch) +}; + +struct PreSharedKeyID +{ + var::variant content; + bytes psk_nonce; + TLS_SERIALIZABLE(content, psk_nonce) + TLS_TRAITS(tls::variant, tls::pass) +}; + +struct PreSharedKeys +{ + std::vector psks; + TLS_SERIALIZABLE(psks) +}; + +struct PSKWithSecret +{ + PreSharedKeyID id; + bytes secret; +}; + +// struct { +// ProtocolVersion version = mls10; +// CipherSuite cipher_suite; +// opaque group_id; +// uint64 epoch; +// opaque tree_hash; +// opaque confirmed_transcript_hash; +// Extension extensions; +// } GroupContext; +struct GroupContext +{ + ProtocolVersion version{ ProtocolVersion::mls10 }; + CipherSuite cipher_suite; + bytes group_id; + epoch_t epoch; + bytes tree_hash; + bytes confirmed_transcript_hash; + ExtensionList extensions; + + GroupContext() = default; + GroupContext(CipherSuite cipher_suite_in, + bytes group_id_in, + epoch_t epoch_in, + bytes tree_hash_in, + bytes confirmed_transcript_hash_in, + ExtensionList extensions_in); + + TLS_SERIALIZABLE(version, + cipher_suite, + group_id, + epoch, + tree_hash, + confirmed_transcript_hash, + extensions) +}; + +// struct { +// GroupContext group_context; +// Extension extensions; +// MAC confirmation_tag; +// uint32 signer; +// // SignWithLabel(., "GroupInfoTBS", GroupInfoTBS) +// opaque signature; +// } GroupInfo; +struct GroupInfo +{ + GroupContext group_context; + ExtensionList extensions; + bytes confirmation_tag; + LeafIndex signer; + bytes signature; + + GroupInfo() = default; + GroupInfo(GroupContext group_context_in, + ExtensionList extensions_in, + bytes confirmation_tag_in); + + bytes to_be_signed() const; + void sign(const TreeKEMPublicKey& tree, + LeafIndex signer_index, + const SignaturePrivateKey& priv); + bool verify(const TreeKEMPublicKey& tree) const; + + // These methods exist only to simplify unit testing + void sign(LeafIndex signer_index, const SignaturePrivateKey& priv); + bool verify(const SignaturePublicKey& pub) const; + + TLS_SERIALIZABLE(group_context, + extensions, + confirmation_tag, + signer, + signature) +}; + +// struct { +// opaque joiner_secret<1..255>; +// optional path_secret; +// PreSharedKeys psks; +// } GroupSecrets; +struct GroupSecrets +{ + struct PathSecret + { + bytes secret; + + TLS_SERIALIZABLE(secret) + }; + + bytes joiner_secret; + std::optional path_secret; + PreSharedKeys psks; + + TLS_SERIALIZABLE(joiner_secret, path_secret, psks) +}; + +// struct { +// opaque key_package_hash<1..255>; +// HPKECiphertext encrypted_group_secrets; +// } EncryptedGroupSecrets; +struct EncryptedGroupSecrets +{ + KeyPackageRef new_member; + HPKECiphertext encrypted_group_secrets; + + TLS_SERIALIZABLE(new_member, encrypted_group_secrets) +}; + +// struct { +// ProtocolVersion version = mls10; +// CipherSuite cipher_suite; +// EncryptedGroupSecrets group_secretss<1..2^32-1>; +// opaque encrypted_group_info<1..2^32-1>; +// } Welcome; +struct Welcome +{ + CipherSuite cipher_suite; + std::vector secrets; + bytes encrypted_group_info; + + Welcome(); + Welcome(CipherSuite suite, + const bytes& joiner_secret, + const std::vector& psks, + const GroupInfo& group_info); + + void encrypt(const KeyPackage& kp, const std::optional& path_secret); + std::optional find(const KeyPackage& kp) const; + GroupSecrets decrypt_secrets(int kp_index, + const HPKEPrivateKey& init_priv) const; + GroupInfo decrypt(const bytes& joiner_secret, + const std::vector& psks) const; + + TLS_SERIALIZABLE(cipher_suite, secrets, encrypted_group_info) + +private: + bytes _joiner_secret; + PreSharedKeys _psks; + static KeyAndNonce group_info_key_nonce( + CipherSuite suite, + const bytes& joiner_secret, + const std::vector& psks); +}; + +/// +/// Proposals & Commit +/// + +// Add +struct Add +{ + KeyPackage key_package; + TLS_SERIALIZABLE(key_package) +}; + +// Update +struct Update +{ + LeafNode leaf_node; + TLS_SERIALIZABLE(leaf_node) +}; + +// Remove +struct Remove +{ + LeafIndex removed; + TLS_SERIALIZABLE(removed) +}; + +// PreSharedKey +struct PreSharedKey +{ + PreSharedKeyID psk; + TLS_SERIALIZABLE(psk) +}; + +// ReInit +struct ReInit +{ + bytes group_id; + ProtocolVersion version; + CipherSuite cipher_suite; + ExtensionList extensions; + + TLS_SERIALIZABLE(group_id, version, cipher_suite, extensions) +}; + +// ExternalInit +struct ExternalInit +{ + bytes kem_output; + TLS_SERIALIZABLE(kem_output) +}; + +// GroupContextExtensions +struct GroupContextExtensions +{ + ExtensionList group_context_extensions; + TLS_SERIALIZABLE(group_context_extensions) +}; + +struct ProposalType; + +struct Proposal +{ + using Type = uint16_t; + + var::variant + content; + + Type proposal_type() const; + + TLS_SERIALIZABLE(content) + TLS_TRAITS(tls::variant) +}; + +struct ProposalType +{ + static constexpr Proposal::Type invalid = 0; + static constexpr Proposal::Type add = 1; + static constexpr Proposal::Type update = 2; + static constexpr Proposal::Type remove = 3; + static constexpr Proposal::Type psk = 4; + static constexpr Proposal::Type reinit = 5; + static constexpr Proposal::Type external_init = 6; + static constexpr Proposal::Type group_context_extensions = 7; + + constexpr ProposalType() + : val(invalid) + { + } + + constexpr ProposalType(Proposal::Type pt) + : val(pt) + { + } + + Proposal::Type val; + TLS_SERIALIZABLE(val) +}; + +enum struct ProposalOrRefType : uint8_t +{ + reserved = 0, + value = 1, + reference = 2, +}; + +struct ProposalOrRef +{ + var::variant content; + + TLS_SERIALIZABLE(content) + TLS_TRAITS(tls::variant) +}; + +// struct { +// ProposalOrRef proposals<0..2^32-1>; +// optional path; +// } Commit; +struct Commit +{ + std::vector proposals; + std::optional path; + + // Validate that the commit is acceptable as an external commit, and if so, + // produce the public key from the ExternalInit proposal + std::optional valid_external() const; + + TLS_SERIALIZABLE(proposals, path) +}; + +// struct { +// opaque group_id<0..255>; +// uint32 epoch; +// uint32 sender; +// ContentType content_type; +// +// select (PublicMessage.content_type) { +// case handshake: +// GroupOperation operation; +// opaque confirmation<0..255>; +// +// case application: +// opaque application_data<0..2^32-1>; +// } +// +// opaque signature<0..2^16-1>; +// } PublicMessage; +struct ApplicationData +{ + bytes data; + TLS_SERIALIZABLE(data) +}; + +struct GroupContext; + +enum struct WireFormat : uint16_t +{ + reserved = 0, + mls_public_message = 1, + mls_private_message = 2, + mls_welcome = 3, + mls_group_info = 4, + mls_key_package = 5, +}; + +enum struct ContentType : uint8_t +{ + invalid = 0, + application = 1, + proposal = 2, + commit = 3, +}; + +enum struct SenderType : uint8_t +{ + invalid = 0, + member = 1, + external = 2, + new_member_proposal = 3, + new_member_commit = 4, +}; + +struct MemberSender +{ + LeafIndex sender; + TLS_SERIALIZABLE(sender); +}; + +struct ExternalSenderIndex +{ + uint32_t sender_index; + TLS_SERIALIZABLE(sender_index) +}; + +struct NewMemberProposalSender +{ + TLS_SERIALIZABLE() +}; + +struct NewMemberCommitSender +{ + TLS_SERIALIZABLE() +}; + +struct Sender +{ + var::variant + sender; + + SenderType sender_type() const; + + TLS_SERIALIZABLE(sender) + TLS_TRAITS(tls::variant) +}; + +/// +/// MLSMessage and friends +/// +struct GroupKeySource; + +struct GroupContent +{ + using RawContent = var::variant; + + bytes group_id; + epoch_t epoch; + Sender sender; + bytes authenticated_data; + RawContent content; + + GroupContent() = default; + GroupContent(bytes group_id_in, + epoch_t epoch_in, + Sender sender_in, + bytes authenticated_data_in, + RawContent content_in); + GroupContent(bytes group_id_in, + epoch_t epoch_in, + Sender sender_in, + bytes authenticated_data_in, + ContentType content_type); + + ContentType content_type() const; + + TLS_SERIALIZABLE(group_id, epoch, sender, authenticated_data, content) + TLS_TRAITS(tls::pass, + tls::pass, + tls::pass, + tls::pass, + tls::variant) +}; + +struct GroupContentAuthData +{ + ContentType content_type = ContentType::invalid; + bytes signature; + std::optional confirmation_tag; + + friend tls::ostream& operator<<(tls::ostream& str, + const GroupContentAuthData& obj); + friend tls::istream& operator>>(tls::istream& str, GroupContentAuthData& obj); + friend bool operator==(const GroupContentAuthData& lhs, + const GroupContentAuthData& rhs); +}; + +struct AuthenticatedContent +{ + WireFormat wire_format; + GroupContent content; + GroupContentAuthData auth; + + AuthenticatedContent() = default; + + static AuthenticatedContent sign(WireFormat wire_format, + GroupContent content, + CipherSuite suite, + const SignaturePrivateKey& sig_priv, + const std::optional& context); + bool verify(CipherSuite suite, + const SignaturePublicKey& sig_pub, + const std::optional& context) const; + + bytes confirmed_transcript_hash_input() const; + bytes interim_transcript_hash_input() const; + + void set_confirmation_tag(const bytes& confirmation_tag); + bool check_confirmation_tag(const bytes& confirmation_tag) const; + + friend tls::ostream& operator<<(tls::ostream& str, + const AuthenticatedContent& obj); + friend tls::istream& operator>>(tls::istream& str, AuthenticatedContent& obj); + friend bool operator==(const AuthenticatedContent& lhs, + const AuthenticatedContent& rhs); + +private: + AuthenticatedContent(WireFormat wire_format_in, GroupContent content_in); + AuthenticatedContent(WireFormat wire_format_in, + GroupContent content_in, + GroupContentAuthData auth_in); + + bytes to_be_signed(const std::optional& context) const; + + friend struct PublicMessage; + friend struct PrivateMessage; +}; + +struct ValidatedContent +{ + const AuthenticatedContent& authenticated_content() const; + + friend bool operator==(const ValidatedContent& lhs, + const ValidatedContent& rhs); + +private: + AuthenticatedContent content_auth; + + ValidatedContent(AuthenticatedContent content_auth_in); + + friend struct PublicMessage; + friend struct PrivateMessage; + friend class State; +}; + +struct PublicMessage +{ + PublicMessage() = default; + + bytes get_group_id() const { return content.group_id; } + epoch_t get_epoch() const { return content.epoch; } + + static PublicMessage protect(AuthenticatedContent content_auth, + CipherSuite suite, + const std::optional& membership_key, + const std::optional& context); + std::optional unprotect( + CipherSuite suite, + const std::optional& membership_key, + const std::optional& context) const; + + bool contains(const AuthenticatedContent& content_auth) const; + + // TODO(RLB) Make this private and expose only to tests + AuthenticatedContent authenticated_content() const; + + friend tls::ostream& operator<<(tls::ostream& str, const PublicMessage& obj); + friend tls::istream& operator>>(tls::istream& str, PublicMessage& obj); + friend bool operator==(const PublicMessage& lhs, const PublicMessage& rhs); + friend bool operator!=(const PublicMessage& lhs, const PublicMessage& rhs); + +private: + GroupContent content; + GroupContentAuthData auth; + std::optional membership_tag; + + PublicMessage(AuthenticatedContent content_auth); + + bytes membership_mac(CipherSuite suite, + const bytes& membership_key, + const std::optional& context) const; +}; + +struct PrivateMessage +{ + PrivateMessage() = default; + + bytes get_group_id() const { return group_id; } + epoch_t get_epoch() const { return epoch; } + + static PrivateMessage protect(AuthenticatedContent content_auth, + CipherSuite suite, + GroupKeySource& keys, + const bytes& sender_data_secret, + size_t padding_size); + std::optional unprotect( + CipherSuite suite, + GroupKeySource& keys, + const bytes& sender_data_secret) const; + + TLS_SERIALIZABLE(group_id, + epoch, + content_type, + authenticated_data, + encrypted_sender_data, + ciphertext) + +private: + bytes group_id; + epoch_t epoch; + ContentType content_type; + bytes authenticated_data; + bytes encrypted_sender_data; + bytes ciphertext; + + PrivateMessage(GroupContent content, + bytes encrypted_sender_data_in, + bytes ciphertext_in); +}; + +struct MLSMessage +{ + ProtocolVersion version = ProtocolVersion::mls10; + var::variant + message; + + bytes group_id() const; + epoch_t epoch() const; + WireFormat wire_format() const; + + MLSMessage() = default; + MLSMessage(PublicMessage public_message); + MLSMessage(PrivateMessage private_message); + MLSMessage(Welcome welcome); + MLSMessage(GroupInfo group_info); + MLSMessage(KeyPackage key_package); + + TLS_SERIALIZABLE(version, message) + TLS_TRAITS(tls::pass, tls::variant) +}; + +MLSMessage +external_proposal(CipherSuite suite, + const bytes& group_id, + epoch_t epoch, + const Proposal& proposal, + uint32_t signer_index, + const SignaturePrivateKey& sig_priv); + +} // namespace mlspp + +namespace mlspp::tls { + +TLS_VARIANT_MAP(mlspp::PSKType, mlspp::ExternalPSK, external) +TLS_VARIANT_MAP(mlspp::PSKType, + mlspp::ResumptionPSK, + resumption) + +TLS_VARIANT_MAP(mlspp::ProposalOrRefType, + mlspp::Proposal, + value) +TLS_VARIANT_MAP(mlspp::ProposalOrRefType, + mlspp::ProposalRef, + reference) + +TLS_VARIANT_MAP(mlspp::ProposalType, mlspp::Add, add) +TLS_VARIANT_MAP(mlspp::ProposalType, mlspp::Update, update) +TLS_VARIANT_MAP(mlspp::ProposalType, mlspp::Remove, remove) +TLS_VARIANT_MAP(mlspp::ProposalType, mlspp::PreSharedKey, psk) +TLS_VARIANT_MAP(mlspp::ProposalType, mlspp::ReInit, reinit) +TLS_VARIANT_MAP(mlspp::ProposalType, + mlspp::ExternalInit, + external_init) +TLS_VARIANT_MAP(mlspp::ProposalType, + mlspp::GroupContextExtensions, + group_context_extensions) + +TLS_VARIANT_MAP(mlspp::ContentType, + mlspp::ApplicationData, + application) +TLS_VARIANT_MAP(mlspp::ContentType, mlspp::Proposal, proposal) +TLS_VARIANT_MAP(mlspp::ContentType, mlspp::Commit, commit) + +TLS_VARIANT_MAP(mlspp::SenderType, mlspp::MemberSender, member) +TLS_VARIANT_MAP(mlspp::SenderType, + mlspp::ExternalSenderIndex, + external) +TLS_VARIANT_MAP(mlspp::SenderType, + mlspp::NewMemberProposalSender, + new_member_proposal) +TLS_VARIANT_MAP(mlspp::SenderType, + mlspp::NewMemberCommitSender, + new_member_commit) + +TLS_VARIANT_MAP(mlspp::WireFormat, + mlspp::PublicMessage, + mls_public_message) +TLS_VARIANT_MAP(mlspp::WireFormat, + mlspp::PrivateMessage, + mls_private_message) +TLS_VARIANT_MAP(mlspp::WireFormat, mlspp::Welcome, mls_welcome) +TLS_VARIANT_MAP(mlspp::WireFormat, + mlspp::GroupInfo, + mls_group_info) +TLS_VARIANT_MAP(mlspp::WireFormat, + mlspp::KeyPackage, + mls_key_package) + +} // namespace mlspp::tls diff --git a/mlspp/include/mls/session.h b/mlspp/include/mls/session.h new file mode 100755 index 0000000000..045fbfdb7e --- /dev/null +++ b/mlspp/include/mls/session.h @@ -0,0 +1,98 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace mlspp { + +class PendingJoin; +class Session; + +class Client +{ +public: + Client(CipherSuite suite_in, + SignaturePrivateKey sig_priv_in, + Credential cred_in); + + Session begin_session(const bytes& group_id) const; + + PendingJoin start_join() const; + +private: + const CipherSuite suite; + const SignaturePrivateKey sig_priv; + const Credential cred; +}; + +class PendingJoin +{ +public: + PendingJoin(PendingJoin&& other) noexcept; + PendingJoin& operator=(PendingJoin&& other) noexcept; + ~PendingJoin(); + bytes key_package() const; + Session complete(const bytes& welcome) const; + +private: + struct Inner; + std::unique_ptr inner; + + PendingJoin(Inner* inner); + friend class Client; +}; + +class Session +{ +public: + Session(Session&& other) noexcept; + Session& operator=(Session&& other) noexcept; + ~Session(); + + // Settings + void encrypt_handshake(bool enabled); + + // Message producers + bytes add(const bytes& key_package_data); + bytes update(); + bytes remove(uint32_t index); + std::tuple commit(const bytes& proposal); + std::tuple commit(const std::vector& proposals); + std::tuple commit(); + + // Message consumers + bool handle(const bytes& handshake_data); + + // Information about the current state + epoch_t epoch() const; + LeafIndex index() const; + CipherSuite cipher_suite() const; + const ExtensionList& extensions() const; + const TreeKEMPublicKey& tree() const; + bytes do_export(const std::string& label, + const bytes& context, + size_t size) const; + GroupInfo group_info() const; + std::vector roster() const; + bytes epoch_authenticator() const; + + // Application message protection + bytes protect(const bytes& plaintext); + bytes unprotect(const bytes& ciphertext); + +protected: + struct Inner; + std::unique_ptr inner; + + Session(Inner* inner); + friend class Client; + friend class PendingJoin; + + friend bool operator==(const Session& lhs, const Session& rhs); + friend bool operator!=(const Session& lhs, const Session& rhs); +}; + +} // namespace mlspp diff --git a/mlspp/include/mls/state.h b/mlspp/include/mls/state.h new file mode 100755 index 0000000000..84e4926263 --- /dev/null +++ b/mlspp/include/mls/state.h @@ -0,0 +1,431 @@ +#pragma once + +#include "mls/crypto.h" +#include "mls/key_schedule.h" +#include "mls/messages.h" +#include "mls/treekem.h" +#include +#include +#include + +namespace mlspp { + +// Index into the session roster +struct RosterIndex : public UInt32 +{ + using UInt32::UInt32; +}; + +struct CommitOpts +{ + std::vector extra_proposals; + bool inline_tree; + bool force_path; + LeafNodeOptions leaf_node_opts; +}; + +struct MessageOpts +{ + bool encrypt = false; + bytes authenticated_data; + size_t padding_size = 0; +}; + +class State +{ +public: + /// + /// Constructors + /// + + // Initialize an empty group + State(bytes group_id, + CipherSuite suite, + HPKEPrivateKey enc_priv, + SignaturePrivateKey sig_priv, + const LeafNode& leaf_node, + ExtensionList extensions); + + // Initialize a group from a Welcome + State(const HPKEPrivateKey& init_priv, + HPKEPrivateKey leaf_priv, + SignaturePrivateKey sig_priv, + const KeyPackage& key_package, + const Welcome& welcome, + const std::optional& tree, + std::map psks); + + // Join a group from outside + // XXX(RLB) To be fully general, we would need a few more options here, e.g., + // whether to include PSKs or evict our prior appearance. + static std::tuple external_join( + const bytes& leaf_secret, + SignaturePrivateKey sig_priv, + const KeyPackage& key_package, + const GroupInfo& group_info, + const std::optional& tree, + const MessageOpts& msg_opts, + std::optional remove_prior, + const std::map& psks); + + // Propose that a new member be added a group + static MLSMessage new_member_add(const bytes& group_id, + epoch_t epoch, + const KeyPackage& new_member, + const SignaturePrivateKey& sig_priv); + + /// + /// Message factories + /// + + Proposal add_proposal(const KeyPackage& key_package) const; + Proposal update_proposal(HPKEPrivateKey leaf_priv, + const LeafNodeOptions& opts); + Proposal remove_proposal(RosterIndex index) const; + Proposal remove_proposal(LeafIndex removed) const; + Proposal group_context_extensions_proposal(ExtensionList exts) const; + Proposal pre_shared_key_proposal(const bytes& external_psk_id) const; + Proposal pre_shared_key_proposal(const bytes& group_id, epoch_t epoch) const; + static Proposal reinit_proposal(bytes group_id, + ProtocolVersion version, + CipherSuite cipher_suite, + ExtensionList extensions); + + MLSMessage add(const KeyPackage& key_package, const MessageOpts& msg_opts); + MLSMessage update(HPKEPrivateKey leaf_priv, + const LeafNodeOptions& opts, + const MessageOpts& msg_opts); + MLSMessage remove(RosterIndex index, const MessageOpts& msg_opts); + MLSMessage remove(LeafIndex removed, const MessageOpts& msg_opts); + MLSMessage group_context_extensions(ExtensionList exts, + const MessageOpts& msg_opts); + MLSMessage pre_shared_key(const bytes& external_psk_id, + const MessageOpts& msg_opts); + MLSMessage pre_shared_key(const bytes& group_id, + epoch_t epoch, + const MessageOpts& msg_opts); + MLSMessage reinit(bytes group_id, + ProtocolVersion version, + CipherSuite cipher_suite, + ExtensionList extensions, + const MessageOpts& msg_opts); + + std::tuple commit( + const bytes& leaf_secret, + const std::optional& opts, + const MessageOpts& msg_opts); + + /// + /// Generic handshake message handlers + /// + std::optional handle(const MLSMessage& msg); + std::optional handle(const MLSMessage& msg, + std::optional cached_state); + + std::optional handle(const ValidatedContent& content_auth); + std::optional handle(const ValidatedContent& content_auth, + std::optional cached_state); + + /// + /// PSK management + /// + void add_resumption_psk(const bytes& group_id, epoch_t epoch, bytes secret); + void remove_resumption_psk(const bytes& group_id, epoch_t epoch); + void add_external_psk(const bytes& id, const bytes& secret); + void remove_external_psk(const bytes& id); + + /// + /// Accessors + /// + const bytes& group_id() const { return _group_id; } + epoch_t epoch() const { return _epoch; } + LeafIndex index() const { return _index; } + CipherSuite cipher_suite() const { return _suite; } + const ExtensionList& extensions() const { return _extensions; } + const TreeKEMPublicKey& tree() const { return _tree; } + const bytes& resumption_psk() const { return _key_schedule.resumption_psk; } + + bytes do_export(const std::string& label, + const bytes& context, + size_t size) const; + GroupInfo group_info(bool inline_tree) const; + + // Ordered list of credentials from non-blank leaves + std::vector roster() const; + + bytes epoch_authenticator() const; + + /// + /// Unwrap messages so that applications can inspect them + /// + ValidatedContent unwrap(const MLSMessage& msg); + + /// + /// Application encryption and decryption + /// + MLSMessage protect(const bytes& authenticated_data, + const bytes& pt, + size_t padding_size); + std::tuple unprotect(const MLSMessage& ct); + + // Assemble a group context for this state + GroupContext group_context() const; + + // Subgroup branching + std::tuple create_branch( + bytes group_id, + HPKEPrivateKey enc_priv, + SignaturePrivateKey sig_priv, + const LeafNode& leaf_node, + ExtensionList extensions, + const std::vector& key_packages, + const bytes& leaf_secret, + const CommitOpts& commit_opts) const; + State handle_branch(const HPKEPrivateKey& init_priv, + HPKEPrivateKey enc_priv, + SignaturePrivateKey sig_priv, + const KeyPackage& key_package, + const Welcome& welcome, + const std::optional& tree) const; + + // Reinitialization + struct Tombstone + { + std::tuple create_welcome( + HPKEPrivateKey enc_priv, + SignaturePrivateKey sig_priv, + const LeafNode& leaf_node, + const std::vector& key_packages, + const bytes& leaf_secret, + const CommitOpts& commit_opts) const; + State handle_welcome(const HPKEPrivateKey& init_priv, + HPKEPrivateKey enc_priv, + SignaturePrivateKey sig_priv, + const KeyPackage& key_package, + const Welcome& welcome, + const std::optional& tree) const; + + TLS_SERIALIZABLE(prior_group_id, prior_epoch, resumption_psk, reinit); + + const bytes epoch_authenticator; + const ReInit reinit; + + private: + Tombstone(const State& state_in, ReInit reinit_in); + + bytes prior_group_id; + epoch_t prior_epoch; + bytes resumption_psk; + + friend class State; + }; + + std::tuple reinit_commit( + const bytes& leaf_secret, + const std::optional& opts, + const MessageOpts& msg_opts); + Tombstone handle_reinit_commit(const MLSMessage& commit); + +protected: + // Shared confirmed state + // XXX(rlb@ipv.sx): Can these be made const? + CipherSuite _suite; + bytes _group_id; + epoch_t _epoch; + TreeKEMPublicKey _tree; + TreeKEMPrivateKey _tree_priv; + TranscriptHash _transcript_hash; + ExtensionList _extensions; + + // Shared secret state + KeyScheduleEpoch _key_schedule; + GroupKeySource _keys; + + // Per-participant state + LeafIndex _index; + SignaturePrivateKey _identity_priv; + + // Storage for PSKs + std::map _external_psks; + + using EpochRef = std::tuple; + std::map _resumption_psks; + + // Cache of Proposals and update secrets + struct CachedProposal + { + ProposalRef ref; + Proposal proposal; + std::optional sender; + }; + std::list _pending_proposals; + + struct CachedUpdate + { + HPKEPrivateKey update_priv; + Update proposal; + }; + std::optional _cached_update; + + // Assemble a preliminary, unjoined group state + State(SignaturePrivateKey sig_priv, + const GroupInfo& group_info, + const std::optional& tree); + + // Assemble a group from a Welcome, allowing for resumption PSKs + State(const HPKEPrivateKey& init_priv, + HPKEPrivateKey leaf_priv, + SignaturePrivateKey sig_priv, + const KeyPackage& key_package, + const Welcome& welcome, + const std::optional& tree, + std::map external_psks, + std::map resumption_psks); + + // Import a tree from an externally-provided tree or an extension + TreeKEMPublicKey import_tree(const bytes& tree_hash, + const std::optional& external, + const ExtensionList& extensions); + bool validate_tree() const; + + // Form a commit, covering all the cases with slightly different validation + // rules: + // * Normal + // * External + // * Branch + // * Reinit + struct NormalCommitParams + {}; + + struct ExternalCommitParams + { + KeyPackage joiner_key_package; + bytes force_init_secret; + }; + + struct RestartCommitParams + { + ResumptionPSKUsage allowed_usage; + }; + + struct ReInitCommitParams + {}; + + using CommitParams = var::variant; + + std::tuple commit( + const bytes& leaf_secret, + const std::optional& opts, + const MessageOpts& msg_opts, + CommitParams params); + + std::optional handle( + const MLSMessage& msg, + std::optional cached_state, + const std::optional& expected_params); + std::optional handle( + const ValidatedContent& val_content, + std::optional cached_state, + const std::optional& expected_params); + + // Create an MLSMessage encapsulating some content + template + AuthenticatedContent sign(const Sender& sender, + Inner&& content, + const bytes& authenticated_data, + bool encrypt) const; + + MLSMessage protect(AuthenticatedContent&& content_auth, size_t padding_size); + + template + MLSMessage protect_full(Inner&& content, const MessageOpts& msg_opts); + + // Apply the changes requested by various messages + LeafIndex apply(const Add& add); + void apply(LeafIndex target, const Update& update); + void apply(LeafIndex target, + const Update& update, + const HPKEPrivateKey& leaf_priv); + LeafIndex apply(const Remove& remove); + void apply(const GroupContextExtensions& gce); + std::vector apply(const std::vector& proposals, + Proposal::Type required_type); + std::tuple, std::vector> apply( + const std::vector& proposals); + + // Verify that a specific key package or all members support a given set of + // extensions + bool extensions_supported(const ExtensionList& exts) const; + + // Extract proposals and PSKs from cache + void cache_proposal(AuthenticatedContent content_auth); + std::optional resolve( + const ProposalOrRef& id, + std::optional sender_index) const; + std::vector must_resolve( + const std::vector& ids, + std::optional sender_index) const; + + std::vector resolve( + const std::vector& psks) const; + + // Check properties of proposals + bool valid(const LeafNode& leaf_node, + LeafNodeSource required_source, + std::optional index) const; + bool valid(const KeyPackage& key_package) const; + bool valid(const Add& add) const; + bool valid(LeafIndex sender, const Update& update) const; + bool valid(const Remove& remove) const; + bool valid(const PreSharedKey& psk) const; + static bool valid(const ReInit& reinit); + bool valid(const ExternalInit& external_init) const; + bool valid(const GroupContextExtensions& gce) const; + bool valid(std::optional sender, const Proposal& proposal) const; + + bool valid(const std::vector& proposals, + LeafIndex commit_sender, + const CommitParams& params) const; + bool valid_normal(const std::vector& proposals, + LeafIndex commit_sender) const; + bool valid_external(const std::vector& proposals) const; + static bool valid_reinit(const std::vector& proposals); + static bool valid_restart(const std::vector& proposals, + ResumptionPSKUsage allowed_usage); + + static bool valid_external_proposal_type(const Proposal::Type proposal_type); + + CommitParams infer_commit_type( + const std::optional& sender, + const std::vector& proposals, + const std::optional& expected_params) const; + static bool path_required(const std::vector& proposals); + + // Compare the **shared** attributes of the states + friend bool operator==(const State& lhs, const State& rhs); + friend bool operator!=(const State& lhs, const State& rhs); + + // Derive and set the secrets for an epoch, given some new entropy + void update_epoch_secrets(const bytes& commit_secret, + const std::vector& psks, + const std::optional& force_init_secret); + + // Signature verification over a handshake message + bool verify_internal(const AuthenticatedContent& content_auth) const; + bool verify_external(const AuthenticatedContent& content_auth) const; + bool verify_new_member_proposal( + const AuthenticatedContent& content_auth) const; + bool verify_new_member_commit(const AuthenticatedContent& content_auth) const; + bool verify(const AuthenticatedContent& content_auth) const; + + // Convert a Roster entry into LeafIndex + LeafIndex leaf_for_roster_entry(RosterIndex index) const; + + // Create a draft successor state + State successor() const; +}; + +} // namespace mlspp diff --git a/mlspp/include/mls/tree_math.h b/mlspp/include/mls/tree_math.h new file mode 100755 index 0000000000..6a11f47a6f --- /dev/null +++ b/mlspp/include/mls/tree_math.h @@ -0,0 +1,107 @@ +#pragma once + +#include +#include +#include + +// The below functions provide the index calculus for the tree +// structures used in MLS. They are premised on a "flat" +// representation of a balanced binary tree. Leaf nodes are +// even-numbered nodes, with the n-th leaf at 2*n. Intermediate +// nodes are held in odd-numbered nodes. For example, a 11-element +// tree has the following structure: +// +// X +// X +// X X X +// X X X X X +// X X X X X X X X X X X +// 0 1 2 3 4 5 6 7 8 9 a b c d e f 10 11 12 13 14 +// +// This allows us to compute relationships between tree nodes simply +// by manipulating indices, rather than having to maintain +// complicated structures in memory, even for partial trees. (The +// storage for a tree can just be a map[int]Node dictionary or an +// array.) The basic rule is that the high-order bits of parent and +// child nodes have the following relation: +// +// 01x = <00x, 10x> + +namespace mlspp { + +// Index types go in the overall namespace +// XXX(rlb@ipv.sx): Seems like this stuff can probably get +// simplified down a fair bit. +struct UInt32 +{ + uint32_t val; + + UInt32() + : val(0) + { + } + + explicit UInt32(uint32_t val_in) + : val(val_in) + { + } + + TLS_SERIALIZABLE(val) +}; + +struct NodeCount; + +struct LeafCount : public UInt32 +{ + using UInt32::UInt32; + explicit LeafCount(const NodeCount w); + + static LeafCount full(const LeafCount n); +}; + +struct NodeCount : public UInt32 +{ + using UInt32::UInt32; + explicit NodeCount(const LeafCount n); +}; + +struct NodeIndex; + +struct LeafIndex : public UInt32 +{ + using UInt32::UInt32; + explicit LeafIndex(const NodeIndex x); + bool operator<(const LeafIndex other) const { return val < other.val; } + bool operator<(const LeafCount other) const { return val < other.val; } + + NodeIndex ancestor(LeafIndex other) const; +}; + +struct NodeIndex : public UInt32 +{ + using UInt32::UInt32; + explicit NodeIndex(const LeafIndex x); + bool operator<(const NodeIndex other) const { return val < other.val; } + bool operator<(const NodeCount other) const { return val < other.val; } + + static NodeIndex root(LeafCount n); + + bool is_leaf() const; + bool is_below(NodeIndex other) const; + + NodeIndex left() const; + NodeIndex right() const; + NodeIndex parent() const; + NodeIndex sibling() const; + + // Returns the sibling of this node "relative to this ancestor" -- the child + // of `ancestor` that is not in the direct path of this node. + NodeIndex sibling(NodeIndex ancestor) const; + + std::vector dirpath(LeafCount n); + std::vector copath(LeafCount n); + + uint32_t level() const; +}; + +} // namespace mlspp diff --git a/mlspp/include/mls/treekem.h b/mlspp/include/mls/treekem.h new file mode 100755 index 0000000000..e3e1028d3d --- /dev/null +++ b/mlspp/include/mls/treekem.h @@ -0,0 +1,255 @@ +#pragma once + +#include "mls/common.h" +#include "mls/core_types.h" +#include "mls/crypto.h" +#include "mls/tree_math.h" +#include + +#define ENABLE_TREE_DUMP 1 + +namespace mlspp { + +enum struct NodeType : uint8_t +{ + reserved = 0x00, + leaf = 0x01, + parent = 0x02, +}; + +struct Node +{ + var::variant node; + + const HPKEPublicKey& public_key() const; + std::optional parent_hash() const; + + TLS_SERIALIZABLE(node) + TLS_TRAITS(tls::variant) +}; + +struct OptionalNode +{ + std::optional node; + + bool blank() const { return !node.has_value(); } + bool leaf() const + { + return !blank() && var::holds_alternative(opt::get(node).node); + } + + LeafNode& leaf_node() { return var::get(opt::get(node).node); } + + const LeafNode& leaf_node() const + { + return var::get(opt::get(node).node); + } + + ParentNode& parent_node() + { + return var::get(opt::get(node).node); + } + + const ParentNode& parent_node() const + { + return var::get(opt::get(node).node); + } + + TLS_SERIALIZABLE(node) +}; + +struct TreeKEMPublicKey; + +struct TreeKEMPrivateKey +{ + CipherSuite suite; + LeafIndex index; + bytes update_secret; + std::map path_secrets; + std::map private_key_cache; + + static TreeKEMPrivateKey solo(CipherSuite suite, + LeafIndex index, + HPKEPrivateKey leaf_priv); + static TreeKEMPrivateKey create(const TreeKEMPublicKey& pub, + LeafIndex from, + const bytes& leaf_secret); + static TreeKEMPrivateKey joiner(const TreeKEMPublicKey& pub, + LeafIndex index, + HPKEPrivateKey leaf_priv, + NodeIndex intersect, + const std::optional& path_secret); + + void set_leaf_priv(HPKEPrivateKey priv); + std::tuple shared_path_secret(LeafIndex to) const; + + bool have_private_key(NodeIndex n) const; + std::optional private_key(NodeIndex n); + std::optional private_key(NodeIndex n) const; + + void decap(LeafIndex from, + const TreeKEMPublicKey& pub, + const bytes& context, + const UpdatePath& path, + const std::vector& except); + + void truncate(LeafCount size); + + bool consistent(const TreeKEMPrivateKey& other) const; + bool consistent(const TreeKEMPublicKey& other) const; + +#if ENABLE_TREE_DUMP + void dump() const; +#endif + + // TODO(RLB) Make this private but exposed to test vectors + void implant(const TreeKEMPublicKey& pub, + NodeIndex start, + const bytes& path_secret); +}; + +struct TreeKEMPublicKey +{ + CipherSuite suite; + LeafCount size{ 0 }; + std::vector nodes; + + explicit TreeKEMPublicKey(CipherSuite suite); + + TreeKEMPublicKey() = default; + TreeKEMPublicKey(const TreeKEMPublicKey& other) = default; + TreeKEMPublicKey(TreeKEMPublicKey&& other) = default; + TreeKEMPublicKey& operator=(const TreeKEMPublicKey& other) = default; + TreeKEMPublicKey& operator=(TreeKEMPublicKey&& other) = default; + + LeafIndex allocate_leaf(); + LeafIndex add_leaf(const LeafNode& leaf); + void update_leaf(LeafIndex index, const LeafNode& leaf); + void blank_path(LeafIndex index); + + TreeKEMPrivateKey update(LeafIndex from, + const bytes& leaf_secret, + const bytes& group_id, + const SignaturePrivateKey& sig_priv, + const LeafNodeOptions& opts); + UpdatePath encap(const TreeKEMPrivateKey& priv, + const bytes& context, + const std::vector& except) const; + + void merge(LeafIndex from, const UpdatePath& path); + void set_hash_all(); + const bytes& get_hash(NodeIndex index); + bytes root_hash() const; + + bool parent_hash_valid(LeafIndex from, const UpdatePath& path) const; + bool parent_hash_valid() const; + + bool has_leaf(LeafIndex index) const; + std::optional find(const LeafNode& leaf) const; + std::optional leaf_node(LeafIndex index) const; + std::vector resolve(NodeIndex index) const; + + template + bool all_leaves(const UnaryPredicate& pred) const + { + for (LeafIndex i{ 0 }; i < size; i.val++) { + const auto& node = node_at(i); + if (node.blank()) { + continue; + } + + if (!pred(i, node.leaf_node())) { + return false; + } + } + + return true; + } + + template + bool any_leaf(const UnaryPredicate& pred) const + { + for (LeafIndex i{ 0 }; i < size; i.val++) { + const auto& node = node_at(i); + if (node.blank()) { + continue; + } + + if (pred(i, node.leaf_node())) { + return true; + } + } + + return false; + } + + using FilteredDirectPath = + std::vector>>; + FilteredDirectPath filtered_direct_path(NodeIndex index) const; + + void truncate(); + + OptionalNode& node_at(NodeIndex n); + const OptionalNode& node_at(NodeIndex n) const; + OptionalNode& node_at(LeafIndex n); + const OptionalNode& node_at(LeafIndex n) const; + + TLS_SERIALIZABLE(nodes) + +#if ENABLE_TREE_DUMP + void dump() const; +#endif + +private: + std::map hashes; + + void clear_hash_all(); + void clear_hash_path(LeafIndex index); + + bool has_parent_hash(NodeIndex child, const bytes& target_ph) const; + + bytes parent_hash(const ParentNode& parent, NodeIndex copath_child) const; + std::vector parent_hashes( + LeafIndex from, + const FilteredDirectPath& fdp, + const std::vector& path_nodes) const; + + using TreeHashCache = std::map>; + const bytes& original_tree_hash(TreeHashCache& cache, + NodeIndex index, + std::vector parent_except) const; + bytes original_parent_hash(TreeHashCache& cache, + NodeIndex parent, + NodeIndex sibling) const; + + bool exists_in_tree(const HPKEPublicKey& key, + std::optional except) const; + bool exists_in_tree(const SignaturePublicKey& key, + std::optional except) const; + + OptionalNode blank_node; + + friend struct TreeKEMPrivateKey; +}; + +tls::ostream& +operator<<(tls::ostream& str, const TreeKEMPublicKey& obj); +tls::istream& +operator>>(tls::istream& str, TreeKEMPublicKey& obj); + +struct LeafNodeHashInput; +struct ParentNodeHashInput; + +} // namespace mlspp + +namespace mlspp::tls { + +TLS_VARIANT_MAP(mlspp::NodeType, mlspp::LeafNodeHashInput, leaf) +TLS_VARIANT_MAP(mlspp::NodeType, + mlspp::ParentNodeHashInput, + parent) + +TLS_VARIANT_MAP(mlspp::NodeType, mlspp::LeafNode, leaf) +TLS_VARIANT_MAP(mlspp::NodeType, mlspp::ParentNode, parent) + +} // namespace mlspp::tls diff --git a/mlspp/include/namespace.h b/mlspp/include/namespace.h new file mode 100755 index 0000000000..d07ba5ee94 --- /dev/null +++ b/mlspp/include/namespace.h @@ -0,0 +1,4 @@ +#pragma once + +// Configurable top-level MLS namespace +#define MLS_NAMESPACE ../include/dpp/mlspp/mls diff --git a/mlspp/include/version.h b/mlspp/include/version.h new file mode 100755 index 0000000000..0cead31c4e --- /dev/null +++ b/mlspp/include/version.h @@ -0,0 +1,5 @@ +#pragma once + +/* Global version strings */ +extern const char VERSION[]; +extern const char HASHVAR[]; diff --git a/mlspp/lib/CMakeLists.txt b/mlspp/lib/CMakeLists.txt new file mode 100755 index 0000000000..31f9546e84 --- /dev/null +++ b/mlspp/lib/CMakeLists.txt @@ -0,0 +1,4 @@ +add_subdirectory(bytes) +add_subdirectory(hpke) +add_subdirectory(tls_syntax) +add_subdirectory(mls_vectors) diff --git a/mlspp/lib/bytes/CMakeLists.txt b/mlspp/lib/bytes/CMakeLists.txt new file mode 100755 index 0000000000..ee4bb15937 --- /dev/null +++ b/mlspp/lib/bytes/CMakeLists.txt @@ -0,0 +1,25 @@ +set(CURRENT_LIB_NAME bytes) + +### +### Library Config +### + +file(GLOB_RECURSE LIB_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h") +file(GLOB_RECURSE LIB_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") + +add_library(${CURRENT_LIB_NAME} STATIC ${LIB_HEADERS} ${LIB_SOURCES}) +add_dependencies(${CURRENT_LIB_NAME} tls_syntax) +include_directories("${PROJECT_SOURCE_DIR}/../bytes/include") +target_link_libraries(${CURRENT_LIB_NAME} tls_syntax) +target_include_directories(${CURRENT_LIB_NAME} + PUBLIC + $ + $ + $ +) + +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../lib/bytes/include") +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../lib/hpke/include") +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../lib/mls_vectors/include") +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../lib/tls_syntax/include") + diff --git a/mlspp/lib/bytes/include/bytes/bytes.h b/mlspp/lib/bytes/include/bytes/bytes.h new file mode 100755 index 0000000000..094466f791 --- /dev/null +++ b/mlspp/lib/bytes/include/bytes/bytes.h @@ -0,0 +1,127 @@ +#pragma once + +#include +#include +#include + +namespace mlspp::bytes_ns { + +struct bytes +{ + // Ensure defaults + bytes() = default; + bytes(const bytes&) = default; + bytes& operator=(const bytes&) = default; + bytes(bytes&&) = default; + bytes& operator=(bytes&&) = default; + + // Zeroize on drop + ~bytes() + { + auto ptr = static_cast(_data.data()); + std::fill(ptr, ptr + _data.size(), uint8_t(0)); + } + + // Mimic std::vector ctors + bytes(size_t count, const uint8_t& value = 0) + : _data(count, value) + { + } + + bytes(std::initializer_list init) + : _data(init) + { + } + + template + bytes(const std::array& data) + : _data(data.begin(), data.end()) + { + } + + // Slice out sub-vectors (to avoid an iterator ctor) + bytes slice(size_t begin_index, size_t end_index) const + { + const auto begin_it = _data.begin() + begin_index; + const auto end_it = _data.begin() + end_index; + return std::vector(begin_it, end_it); + } + + // Freely convert to/from std::vector + bytes(const std::vector& vec) + : _data(vec) + { + } + + bytes(std::vector&& vec) + : _data(vec) + { + } + + operator const std::vector&() const { return _data; } + operator std::vector&() { return _data; } + operator std::vector&&() && { return std::move(_data); } + + const std::vector& as_vec() const { return _data; } + std::vector& as_vec() { return _data; } + + // Pass through methods + auto data() const { return _data.data(); } + auto data() { return _data.data(); } + + auto size() const { return _data.size(); } + auto empty() const { return _data.empty(); } + + auto begin() const { return _data.begin(); } + auto begin() { return _data.begin(); } + + auto end() const { return _data.end(); } + auto end() { return _data.end(); } + + const auto& at(size_t pos) const { return _data.at(pos); } + auto& at(size_t pos) { return _data.at(pos); } + + void resize(size_t count) { _data.resize(count); } + void reserve(size_t len) { _data.reserve(len); } + void push_back(uint8_t byte) { _data.push_back(byte); } + + // Equality operators + bool operator==(const bytes& other) const; + bool operator!=(const bytes& other) const; + + bool operator==(const std::vector& other) const; + bool operator!=(const std::vector& other) const; + + // Arithmetic operators + bytes& operator+=(const bytes& other); + bytes operator+(const bytes& rhs) const; + bytes operator^(const bytes& rhs) const; + + // Sorting operators (to allow usage as map keys) + bool operator<(const bytes& rhs) const; + + // Other, external operators + friend std::ostream& operator<<(std::ostream& out, const bytes& data); + friend bool operator==(const std::vector& lhs, const bytes& rhs); + friend bool operator!=(const std::vector& lhs, const bytes& rhs); + + // TLS syntax serialization + TLS_SERIALIZABLE(_data); + +private: + std::vector _data; +}; + +std::string +to_ascii(const bytes& data); + +bytes +from_ascii(const std::string& ascii); + +std::string +to_hex(const bytes& data); + +bytes +from_hex(const std::string& hex); + +} // namespace mlspp::bytes_ns diff --git a/mlspp/lib/bytes/include/tls/compat.h b/mlspp/lib/bytes/include/tls/compat.h new file mode 100755 index 0000000000..095cc17ff6 --- /dev/null +++ b/mlspp/lib/bytes/include/tls/compat.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include + +#ifdef VARIANT_COMPAT +#include +#else +#include +#endif // VARIANT_COMPAT + +namespace mlspp::tls { + +namespace var = std; + +// In a similar vein, we provide our own safe accessors for std::optional, since +// std::optional::value() is not available on macOS 10.11. +namespace opt { + +template +T& +get(std::optional& opt) +{ + if (!opt) { + throw std::runtime_error("bad_optional_access"); + } + return *opt; +} + +template +const T& +get(const std::optional& opt) +{ + if (!opt) { + throw std::runtime_error("bad_optional_access"); + } + return *opt; +} + +template +T&& +get(std::optional&& opt) +{ + if (!opt) { + throw std::runtime_error("bad_optional_access"); + } + return std::move(*opt); +} + +template +const T&& +get(const std::optional&& opt) +{ + if (!opt) { + throw std::runtime_error("bad_optional_access"); + } + return std::move(*opt); +} + +} // namespace opt +} // namespace mlspp::tls diff --git a/mlspp/lib/bytes/include/tls/tls_syntax.h b/mlspp/lib/bytes/include/tls/tls_syntax.h new file mode 100755 index 0000000000..09d5940d9d --- /dev/null +++ b/mlspp/lib/bytes/include/tls/tls_syntax.h @@ -0,0 +1,569 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace mlspp::tls { + +// For indicating no min or max in vector definitions +const size_t none = std::numeric_limits::max(); + +class WriteError : public std::invalid_argument +{ +public: + using parent = std::invalid_argument; + using parent::parent; +}; + +class ReadError : public std::invalid_argument +{ +public: + using parent = std::invalid_argument; + using parent::parent; +}; + +/// +/// Declarations of Streams and Traits +/// + +class ostream +{ +public: + static const size_t none = std::numeric_limits::max(); + + void write_raw(const std::vector& bytes); + + const std::vector& bytes() const { return _buffer; } + size_t size() const { return _buffer.size(); } + bool empty() const { return _buffer.empty(); } + +private: + std::vector _buffer; + ostream& write_uint(uint64_t value, int length); + + friend ostream& operator<<(ostream& out, bool data); + friend ostream& operator<<(ostream& out, uint8_t data); + friend ostream& operator<<(ostream& out, uint16_t data); + friend ostream& operator<<(ostream& out, uint32_t data); + friend ostream& operator<<(ostream& out, uint64_t data); + + template + friend ostream& operator<<(ostream& out, const std::vector& data); + + friend struct varint; +}; + +class istream +{ +public: + istream(const std::vector& data) + : _buffer(data) + { + // So that we can use the constant-time pop_back + std::reverse(_buffer.begin(), _buffer.end()); + } + + size_t size() const { return _buffer.size(); } + bool empty() const { return _buffer.empty(); } + + std::vector bytes() + { + auto bytes = _buffer; + std::reverse(bytes.begin(), bytes.end()); + return bytes; + } + +private: + istream() {} + std::vector _buffer; + uint8_t next(); + + template + istream& read_uint(T& data, size_t length) + { + uint64_t value = 0; + for (size_t i = 0; i < length; i += 1) { + value = (value << unsigned(8)) + next(); + } + data = static_cast(value); + return *this; + } + + friend istream& operator>>(istream& in, bool& data); + friend istream& operator>>(istream& in, uint8_t& data); + friend istream& operator>>(istream& in, uint16_t& data); + friend istream& operator>>(istream& in, uint32_t& data); + friend istream& operator>>(istream& in, uint64_t& data); + + template + friend istream& operator>>(istream& in, std::vector& data); + + friend struct varint; +}; + +// Traits must have static encode and decode methods, of the following form: +// +// static ostream& encode(ostream& str, const T& val); +// static istream& decode(istream& str, T& val); +// +// Trait types will never be constructed; only these static methods are used. +// The value arguments to encode and decode can be as strict or as loose as +// desired. +// +// Ultimately, all interesting encoding should be done through traits. +// +// * vectors +// * variants +// * varints + +struct pass +{ + template + static ostream& encode(ostream& str, const T& val); + + template + static istream& decode(istream& str, T& val); +}; + +template +struct variant +{ + template + static inline Ts type(const var::variant& data); + + template + static ostream& encode(ostream& str, const var::variant& data); + + template + static inline typename std::enable_if::type + read_variant(istream&, Te, var::variant&); + + template + static inline typename std::enable_if < + I::type read_variant(istream& str, + Te target_type, + var::variant& v); + + template + static istream& decode(istream& str, var::variant& data); +}; + +struct varint +{ + static ostream& encode(ostream& str, const uint64_t& val); + static istream& decode(istream& str, uint64_t& val); +}; + +/// +/// Writer implementations +/// + +// Primitive writers defined in .cpp file + +// Array writer +template +ostream& +operator<<(ostream& out, const std::array& data) +{ + for (const auto& item : data) { + out << item; + } + return out; +} + +// Optional writer +template +ostream& +operator<<(ostream& out, const std::optional& opt) +{ + if (!opt) { + return out << uint8_t(0); + } + + return out << uint8_t(1) << opt::get(opt); +} + +// Enum writer +template::value, int> = 0> +ostream& +operator<<(ostream& str, const T& val) +{ + auto u = static_cast>(val); + return str << u; +} + +// Vector writer +template +ostream& +operator<<(ostream& str, const std::vector& vec) +{ + // Pre-encode contents + ostream temp; + for (const auto& item : vec) { + temp << item; + } + + // Write the encoded length, then the pre-encoded data + varint::encode(str, temp._buffer.size()); + str.write_raw(temp.bytes()); + + return str; +} + +/// +/// Reader implementations +/// + +// Primitive type readers defined in .cpp file + +// Array reader +template +istream& +operator>>(istream& in, std::array& data) +{ + for (auto& item : data) { + in >> item; + } + return in; +} + +// Optional reader +template +istream& +operator>>(istream& in, std::optional& opt) +{ + uint8_t present = 0; + in >> present; + + switch (present) { + case 0: + opt.reset(); + return in; + + case 1: + opt.emplace(); + return in >> opt::get(opt); + + default: + throw std::invalid_argument("Malformed optional"); + } +} + +// Enum reader +// XXX(rlb): It would be nice if this could enforce that the values are valid, +// but C++ doesn't seem to have that ability. When used as a tag for variants, +// the variant reader will enforce, at least. +template::value, int> = 0> +istream& +operator>>(istream& str, T& val) +{ + std::underlying_type_t u; + str >> u; + val = static_cast(u); + return str; +} + +// Vector reader +template +istream& +operator>>(istream& str, std::vector& vec) +{ + // Read the encoded data size + auto size = uint64_t(0); + varint::decode(str, size); + if (size > str._buffer.size()) { + throw ReadError("Vector is longer than remaining data"); + } + + // Read the elements of the vector + // NB: Remember that we store the vector in reverse order + // NB: This requires that T be default-constructible + istream r; + r._buffer = + std::vector{ str._buffer.end() - size, str._buffer.end() }; + + vec.clear(); + while (r._buffer.size() > 0) { + vec.emplace_back(); + r >> vec.back(); + } + + // Truncate the primary buffer + str._buffer.erase(str._buffer.end() - size, str._buffer.end()); + + return str; +} + +// Abbreviations +template +std::vector +marshal(const T& value) +{ + ostream w; + w << value; + return w.bytes(); +} + +template +void +unmarshal(const std::vector& data, T& value) +{ + istream r(data); + r >> value; +} + +template +T +get(const std::vector& data, Tp... args) +{ + T value(args...); + unmarshal(data, value); + return value; +} + +// Use this macro to define struct serialization with minimal boilerplate +#define TLS_SERIALIZABLE(...) \ + static const bool _tls_serializable = true; \ + auto _tls_fields_r() \ + { \ + return std::forward_as_tuple(__VA_ARGS__); \ + } \ + auto _tls_fields_w() const \ + { \ + return std::forward_as_tuple(__VA_ARGS__); \ + } + +// If your struct contains nontrivial members (e.g., vectors), use this to +// define traits for them. +#define TLS_TRAITS(...) \ + static const bool _tls_has_traits = true; \ + using _tls_traits = std::tuple<__VA_ARGS__>; + +template +struct is_serializable +{ + template + static std::true_type test(decltype(U::_tls_serializable)); + + template + static std::false_type test(...); + + static const bool value = decltype(test(true))::value; +}; + +template +struct has_traits +{ + template + static std::true_type test(decltype(U::_tls_has_traits)); + + template + static std::false_type test(...); + + static const bool value = decltype(test(true))::value; +}; + +/// +/// Trait implementations +/// + +// Pass-through (normal encoding/decoding) +template +ostream& +pass::encode(ostream& str, const T& val) +{ + return str << val; +} + +template +istream& +pass::decode(istream& str, T& val) +{ + return str >> val; +} + +// Variant encoding +template +constexpr Ts +variant_map(); + +#define TLS_VARIANT_MAP(EnumType, MappedType, enum_value) \ + template<> \ + constexpr EnumType variant_map() \ + { \ + return EnumType::enum_value; \ + } + +template +template +inline Ts +variant::type(const var::variant& data) +{ + const auto get_type = [](const auto& v) { + return variant_map>(); + }; + return var::visit(get_type, data); +} + +template +template +ostream& +variant::encode(ostream& str, const var::variant& data) +{ + const auto write_variant = [&str](auto&& v) { + using Tv = std::decay_t; + str << variant_map() << v; + }; + var::visit(write_variant, data); + return str; +} + +template +template +inline typename std::enable_if::type +variant::read_variant(istream&, Te, var::variant&) +{ + throw ReadError("Invalid variant type label"); +} + +template + template + inline + typename std::enable_if < I::type + variant::read_variant(istream& str, + Te target_type, + var::variant& v) +{ + using Tc = var::variant_alternative_t>; + if (variant_map() == target_type) { + str >> v.template emplace(); + return; + } + + read_variant(str, target_type, v); +} + +template +template +istream& +variant::decode(istream& str, var::variant& data) +{ + Ts target_type; + str >> target_type; + read_variant(str, target_type, data); + return str; +} + +// Struct writer without traits (enabled by macro) +template +inline typename std::enable_if::type +write_tuple(ostream&, const std::tuple&) +{ +} + +template + inline typename std::enable_if < + I::type + write_tuple(ostream& str, const std::tuple& t) +{ + str << std::get(t); + write_tuple(str, t); +} + +template +inline + typename std::enable_if::value && !has_traits::value, + ostream&>::type + operator<<(ostream& str, const T& obj) +{ + write_tuple(str, obj._tls_fields_w()); + return str; +} + +// Struct writer with traits (enabled by macro) +template +inline typename std::enable_if::type +write_tuple_traits(ostream&, const std::tuple&) +{ +} + +template + inline typename std::enable_if < + I::type + write_tuple_traits(ostream& str, const std::tuple& t) +{ + std::tuple_element_t::encode(str, std::get(t)); + write_tuple_traits(str, t); +} + +template +inline + typename std::enable_if::value && has_traits::value, + ostream&>::type + operator<<(ostream& str, const T& obj) +{ + write_tuple_traits(str, obj._tls_fields_w()); + return str; +} + +// Struct reader without traits (enabled by macro) +template +inline typename std::enable_if::type +read_tuple(istream&, const std::tuple&) +{ +} + +template + inline + typename std::enable_if < I::type + read_tuple(istream& str, const std::tuple& t) +{ + str >> std::get(t); + read_tuple(str, t); +} + +template +inline + typename std::enable_if::value && !has_traits::value, + istream&>::type + operator>>(istream& str, T& obj) +{ + read_tuple(str, obj._tls_fields_r()); + return str; +} + +// Struct reader with traits (enabled by macro) +template +inline typename std::enable_if::type +read_tuple_traits(istream&, const std::tuple&) +{ +} + +template + inline typename std::enable_if < + I::type + read_tuple_traits(istream& str, const std::tuple& t) +{ + std::tuple_element_t::decode(str, std::get(t)); + read_tuple_traits(str, t); +} + +template +inline + typename std::enable_if::value && has_traits::value, + istream&>::type + operator>>(istream& str, T& obj) +{ + read_tuple_traits(str, obj._tls_fields_r()); + return str; +} + +} // namespace mlspp::tls diff --git a/mlspp/lib/bytes/src/bytes.cpp b/mlspp/lib/bytes/src/bytes.cpp new file mode 100755 index 0000000000..634b9f9aad --- /dev/null +++ b/mlspp/lib/bytes/src/bytes.cpp @@ -0,0 +1,146 @@ +#include + +#include +#include +#include +#include + +namespace mlspp::bytes_ns { + +bool +bytes::operator==(const bytes& other) const +{ + return *this == other._data; +} + +bool +bytes::operator!=(const bytes& other) const +{ + return !(*this == other._data); +} + +bool +bytes::operator==(const std::vector& other) const +{ + const size_t size = other.size(); + if (_data.size() != size) { + return false; + } + + unsigned char diff = 0; + for (size_t i = 0; i < size; ++i) { + // Not sure why the linter thinks `diff` is signed + // NOLINTNEXTLINE(hicpp-signed-bitwise) + diff |= (_data.at(i) ^ other.at(i)); + } + return (diff == 0); +} + +bool +bytes::operator!=(const std::vector& other) const +{ + return !(*this == other); +} + +bytes& +bytes::operator+=(const bytes& other) +{ + // Not sure what the default argument is here + // NOLINTNEXTLINE(fuchsia-default-arguments) + _data.insert(end(), other.begin(), other.end()); + return *this; +} + +bytes +bytes::operator+(const bytes& rhs) const +{ + bytes out = *this; + out += rhs; + return out; +} + +bool +bytes::operator<(const bytes& rhs) const +{ + return _data < rhs._data; +} + +bytes +bytes::operator^(const bytes& rhs) const +{ + if (size() != rhs.size()) { + throw std::invalid_argument("XOR with unequal size"); + } + + bytes out = *this; + for (size_t i = 0; i < size(); ++i) { + out.at(i) ^= rhs.at(i); + } + return out; +} + +std::string +to_ascii(const bytes& data) +{ + return { data.begin(), data.end() }; +} + +bytes +from_ascii(const std::string& ascii) +{ + return std::vector(ascii.begin(), ascii.end()); +} + +std::string +to_hex(const bytes& data) +{ + std::stringstream hex(std::ios_base::out); + hex.flags(std::ios::hex); + for (const auto& byte : data) { + hex << std::setw(2) << std::setfill('0') << int(byte); + } + return hex.str(); +} + +bytes +from_hex(const std::string& hex) +{ + if (hex.length() % 2 == 1) { + throw std::invalid_argument("Odd-length hex string"); + } + + auto len = hex.length() / 2; + auto out = bytes(len); + for (size_t i = 0; i < len; i += 1) { + const std::string byte = hex.substr(2 * i, 2); + out.at(i) = static_cast(strtol(byte.c_str(), nullptr, 16)); + } + + return out; +} + +std::ostream& +operator<<(std::ostream& out, const bytes& data) +{ + // Adjust this threshold to make output more compact + const size_t threshold = 0xffff; + if (data.size() < threshold) { + return out << to_hex(data); + } + + return out << to_hex(data.slice(0, threshold)) << "..."; +} + +bool +operator==(const std::vector& lhs, const bytes_ns::bytes& rhs) +{ + return rhs == lhs; +} + +bool +operator!=(const std::vector& lhs, const bytes_ns::bytes& rhs) +{ + return rhs != lhs; +} + +} // namespace mlspp::bytes_ns diff --git a/mlspp/lib/hpke/CMakeLists.txt b/mlspp/lib/hpke/CMakeLists.txt new file mode 100755 index 0000000000..b8e5ac79e0 --- /dev/null +++ b/mlspp/lib/hpke/CMakeLists.txt @@ -0,0 +1,57 @@ +set(CURRENT_LIB_NAME hpke) + +### +### Library Config +### + +file(GLOB_RECURSE LIB_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h") +file(GLOB_RECURSE LIB_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") + +# -Werror=dangling-reference + +add_library(${CURRENT_LIB_NAME} STATIC ${LIB_HEADERS} ${LIB_SOURCES}) +add_dependencies(${CURRENT_LIB_NAME} bytes tls_syntax) +target_include_directories(${CURRENT_LIB_NAME} + PRIVATE + "${JSON_INCLUDE_INTERFACE}") + +target_link_libraries(${CURRENT_LIB_NAME} + PUBLIC + bytes tls_syntax +) + +target_include_directories(${CURRENT_LIB_NAME} + PUBLIC + $ + $ + $ + PRIVATE + ${OPENSSL_INCLUDE_DIR} +) + +# Private statically linked dependencies +target_link_libraries(${CURRENT_LIB_NAME} PRIVATE + mlspp + mls_vectors + bytes + tls_syntax +) + +target_compile_options( + "${CURRENT_LIB_NAME}" + PUBLIC + "$<$:/bigobj;/Zc:preprocessor>" + PRIVATE + "$<$:$<$:/sdl;/Od;/DEBUG;/MP;/DFD_SETSIZE=1024>>" + "$<$:$<$:/O2;/Oi;/Oy;/GL;/Gy;/sdl;/MP;/DFD_SETSIZE=1024>>" + "$<$:$<$:-Wall;-Wempty-body;-Wno-psabi;-Wunknown-pragmas;-Wignored-qualifiers;-Wimplicit-fallthrough;-Wmissing-field-initializers;-Wsign-compare;-Wtype-limits;-Wuninitialized;-Wshift-negative-value;-pthread;-g;-Og;-fPIC>>" + "$<$:$<$:-Wall;-Wempty-body;-Wno-psabi;-Wunknown-pragmas;-Wignored-qualifiers;-Wimplicit-fallthrough;-Wmissing-field-initializers;-Wsign-compare;-Wtype-limits;-Wuninitialized;-Wshift-negative-value;-pthread;-O3;-fPIC>>" + "${AVX_FLAG}" +) + +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../lib/bytes/include") +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../lib/hpke/include") +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../lib/mls_vectors/include") +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../lib/tls_syntax/include") +# For nlohmann/json.hpp +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../../include") diff --git a/mlspp/lib/hpke/include/hpke/base64.h b/mlspp/lib/hpke/include/hpke/base64.h new file mode 100755 index 0000000000..44ab7a5a09 --- /dev/null +++ b/mlspp/lib/hpke/include/hpke/base64.h @@ -0,0 +1,20 @@ +#pragma once + +#include +using namespace mlspp::bytes_ns; + +namespace mlspp::hpke { + +std::string +to_base64(const bytes& data); + +std::string +to_base64url(const bytes& data); + +bytes +from_base64(const std::string& enc); + +bytes +from_base64url(const std::string& enc); + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/include/hpke/certificate.h b/mlspp/lib/hpke/include/hpke/certificate.h new file mode 100755 index 0000000000..752c5f417b --- /dev/null +++ b/mlspp/lib/hpke/include/hpke/certificate.h @@ -0,0 +1,75 @@ +#pragma once +#include +#include + +#include +#include +#include +#include + +using namespace mlspp::bytes_ns; + +namespace mlspp::hpke { + +struct Certificate +{ +private: + struct ParsedCertificate; + std::unique_ptr parsed_cert; + +public: + struct NameType + { + static const int organization; + static const int common_name; + static const int organizational_unit; + static const int country; + static const int serial_number; + static const int state_or_province_name; + }; + + using ParsedName = std::map; + + // Certificate Expiration Status + enum struct ExpirationStatus + { + inactive, // now < notBefore + active, // notBefore < now < notAfter + expired, // notAfter < now + }; + + explicit Certificate(const bytes& der); + explicit Certificate(std::unique_ptr&& parsed_cert_in); + Certificate() = delete; + Certificate(const Certificate& other); + ~Certificate(); + + static std::vector parse_pem(const bytes& pem); + + bool valid_from(const Certificate& parent) const; + + // Accessors for parsed certificate elements + uint64_t issuer_hash() const; + uint64_t subject_hash() const; + ParsedName issuer() const; + ParsedName subject() const; + bool is_ca() const; + ExpirationStatus expiration_status() const; + std::optional subject_key_id() const; + std::optional authority_key_id() const; + std::vector email_addresses() const; + std::vector dns_names() const; + bytes hash() const; + std::chrono::system_clock::time_point not_before() const; + std::chrono::system_clock::time_point not_after() const; + Signature::ID public_key_algorithm() const; + Signature::ID signature_algorithm() const; + + const std::unique_ptr public_key; + const bytes raw; +}; + +bool +operator==(const Certificate& lhs, const Certificate& rhs); + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/include/hpke/digest.h b/mlspp/lib/hpke/include/hpke/digest.h new file mode 100755 index 0000000000..025669133e --- /dev/null +++ b/mlspp/lib/hpke/include/hpke/digest.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include + +using namespace mlspp::bytes_ns; + +namespace mlspp::hpke { + +struct Digest +{ + enum struct ID + { + SHA256, + SHA384, + SHA512, + }; + + template + static const Digest& get(); + + const ID id; + + bytes hash(const bytes& data) const; + bytes hmac(const bytes& key, const bytes& data) const; + + const size_t hash_size; + +private: + explicit Digest(ID id); + + bytes hmac_for_hkdf_extract(const bytes& key, const bytes& data) const; + friend struct HKDF; +}; + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/include/hpke/hpke.h b/mlspp/lib/hpke/include/hpke/hpke.h new file mode 100755 index 0000000000..4cf8c00a28 --- /dev/null +++ b/mlspp/lib/hpke/include/hpke/hpke.h @@ -0,0 +1,253 @@ +#pragma once + +#include +#include + +#include +using namespace mlspp::bytes_ns; + +namespace mlspp::hpke { + +struct KEM +{ + enum struct ID : uint16_t + { + DHKEM_P256_SHA256 = 0x0010, + DHKEM_P384_SHA384 = 0x0011, + DHKEM_P521_SHA512 = 0x0012, + DHKEM_X25519_SHA256 = 0x0020, +#if !defined(WITH_BORINGSSL) + DHKEM_X448_SHA512 = 0x0021, +#endif + }; + + template + static const KEM& get(); + + virtual ~KEM() = default; + + struct PublicKey + { + virtual ~PublicKey() = default; + }; + + struct PrivateKey + { + virtual ~PrivateKey() = default; + virtual std::unique_ptr public_key() const = 0; + }; + + const ID id; + const size_t secret_size; + const size_t enc_size; + const size_t pk_size; + const size_t sk_size; + + virtual std::unique_ptr generate_key_pair() const = 0; + virtual std::unique_ptr derive_key_pair( + const bytes& ikm) const = 0; + + virtual bytes serialize(const PublicKey& pk) const = 0; + virtual std::unique_ptr deserialize(const bytes& enc) const = 0; + + virtual bytes serialize_private(const PrivateKey& sk) const; + virtual std::unique_ptr deserialize_private( + const bytes& skm) const; + + // (shared_secret, enc) + virtual std::pair encap(const PublicKey& pkR) const = 0; + virtual bytes decap(const bytes& enc, const PrivateKey& skR) const = 0; + + // (shared_secret, enc) + virtual std::pair auth_encap(const PublicKey& pkR, + const PrivateKey& skS) const; + virtual bytes auth_decap(const bytes& enc, + const PublicKey& pkS, + const PrivateKey& skR) const; + +protected: + KEM(ID id_in, + size_t secret_size_in, + size_t enc_size_in, + size_t pk_size_in, + size_t sk_size_in); +}; + +struct KDF +{ + enum struct ID : uint16_t + { + HKDF_SHA256 = 0x0001, + HKDF_SHA384 = 0x0002, + HKDF_SHA512 = 0x0003, + }; + + template + static const KDF& get(); + + virtual ~KDF() = default; + + const ID id; + const size_t hash_size; + + virtual bytes extract(const bytes& salt, const bytes& ikm) const = 0; + virtual bytes expand(const bytes& prk, + const bytes& info, + size_t size) const = 0; + + bytes labeled_extract(const bytes& suite_id, + const bytes& salt, + const bytes& label, + const bytes& ikm) const; + bytes labeled_expand(const bytes& suite_id, + const bytes& prk, + const bytes& label, + const bytes& info, + size_t size) const; + +protected: + KDF(ID id_in, size_t hash_size_in); +}; + +struct AEAD +{ + enum struct ID : uint16_t + { + AES_128_GCM = 0x0001, + AES_256_GCM = 0x0002, + CHACHA20_POLY1305 = 0x0003, + + // Reserved identifier for pseudo-AEAD on contexts that only allow export + export_only = 0xffff, + }; + + template + static const AEAD& get(); + + virtual ~AEAD() = default; + + const ID id; + const size_t key_size; + const size_t nonce_size; + + virtual bytes seal(const bytes& key, + const bytes& nonce, + const bytes& aad, + const bytes& pt) const = 0; + virtual std::optional open(const bytes& key, + const bytes& nonce, + const bytes& aad, + const bytes& ct) const = 0; + +protected: + AEAD(ID id_in, size_t key_size_in, size_t nonce_size_in); +}; + +struct Context +{ + bytes do_export(const bytes& exporter_context, size_t size) const; + +protected: + bytes suite; + bytes key; + bytes nonce; + bytes exporter_secret; + const KDF& kdf; + const AEAD& aead; + + bytes current_nonce() const; + void increment_seq(); + +private: + uint64_t seq; + + Context(bytes suite_in, + bytes key_in, + bytes nonce_in, + bytes exporter_secret_in, + const KDF& kdf_in, + const AEAD& aead_in); + + friend struct HPKE; + friend struct HPKETest; + friend bool operator==(const Context& lhs, const Context& rhs); +}; + +struct SenderContext : public Context +{ + SenderContext(Context&& c); + bytes seal(const bytes& aad, const bytes& pt); +}; + +struct ReceiverContext : public Context +{ + ReceiverContext(Context&& c); + std::optional open(const bytes& aad, const bytes& ct); +}; + +struct HPKE +{ + enum struct Mode : uint8_t + { + base = 0, + psk = 1, + auth = 2, + auth_psk = 3, + }; + + HPKE(KEM::ID kem_id, KDF::ID kdf_id, AEAD::ID aead_id); + + using SenderInfo = std::pair; + + SenderInfo setup_base_s(const KEM::PublicKey& pkR, const bytes& info) const; + ReceiverContext setup_base_r(const bytes& enc, + const KEM::PrivateKey& skR, + const bytes& info) const; + + SenderInfo setup_psk_s(const KEM::PublicKey& pkR, + const bytes& info, + const bytes& psk, + const bytes& psk_id) const; + ReceiverContext setup_psk_r(const bytes& enc, + const KEM::PrivateKey& skR, + const bytes& info, + const bytes& psk, + const bytes& psk_id) const; + + SenderInfo setup_auth_s(const KEM::PublicKey& pkR, + const bytes& info, + const KEM::PrivateKey& skS) const; + ReceiverContext setup_auth_r(const bytes& enc, + const KEM::PrivateKey& skR, + const bytes& info, + const KEM::PublicKey& pkS) const; + + SenderInfo setup_auth_psk_s(const KEM::PublicKey& pkR, + const bytes& info, + const bytes& psk, + const bytes& psk_id, + const KEM::PrivateKey& skS) const; + ReceiverContext setup_auth_psk_r(const bytes& enc, + const KEM::PrivateKey& skR, + const bytes& info, + const bytes& psk, + const bytes& psk_id, + const KEM::PublicKey& pkS) const; + + bytes suite; + const KEM& kem; + const KDF& kdf; + const AEAD& aead; + +private: + static bool verify_psk_inputs(Mode mode, + const bytes& psk, + const bytes& psk_id); + Context key_schedule(Mode mode, + const bytes& shared_secret, + const bytes& info, + const bytes& psk, + const bytes& psk_id) const; +}; + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/include/hpke/random.h b/mlspp/lib/hpke/include/hpke/random.h new file mode 100755 index 0000000000..ad344e659c --- /dev/null +++ b/mlspp/lib/hpke/include/hpke/random.h @@ -0,0 +1,11 @@ +#pragma once + +#include +using namespace mlspp::bytes_ns; + +namespace mlspp::hpke { + +bytes +random_bytes(size_t size); + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/include/hpke/signature.h b/mlspp/lib/hpke/include/hpke/signature.h new file mode 100755 index 0000000000..8e5985a840 --- /dev/null +++ b/mlspp/lib/hpke/include/hpke/signature.h @@ -0,0 +1,89 @@ +#pragma once + +#include + +#include +using namespace mlspp::bytes_ns; + +namespace mlspp::hpke { + +struct Signature +{ + enum struct ID + { + P256_SHA256, + P384_SHA384, + P521_SHA512, + Ed25519, +#if !defined(WITH_BORINGSSL) + Ed448, +#endif + RSA_SHA256, + RSA_SHA384, + RSA_SHA512, + }; + + template + static const Signature& get(); + + virtual ~Signature() = default; + + struct PublicKey + { + virtual ~PublicKey() = default; + }; + + struct PrivateKey + { + virtual ~PrivateKey() = default; + virtual std::unique_ptr public_key() const = 0; + }; + + const ID id; + + virtual std::unique_ptr generate_key_pair() const = 0; + virtual std::unique_ptr derive_key_pair( + const bytes& ikm) const = 0; + + virtual bytes serialize(const PublicKey& pk) const = 0; + virtual std::unique_ptr deserialize(const bytes& enc) const = 0; + + virtual bytes serialize_private(const PrivateKey& sk) const = 0; + virtual std::unique_ptr deserialize_private( + const bytes& skm) const = 0; + + struct PrivateJWK + { + const Signature& sig; + std::optional key_id; + std::unique_ptr key; + }; + static PrivateJWK parse_jwk_private(const std::string& jwk_json); + + struct PublicJWK + { + const Signature& sig; + std::optional key_id; + std::unique_ptr key; + }; + static PublicJWK parse_jwk(const std::string& jwk_json); + + virtual std::unique_ptr import_jwk_private( + const std::string& jwk_json) const = 0; + virtual std::unique_ptr import_jwk( + const std::string& jwk_json) const = 0; + virtual std::string export_jwk_private(const PrivateKey& env) const = 0; + virtual std::string export_jwk(const PublicKey& env) const = 0; + + virtual bytes sign(const bytes& data, const PrivateKey& sk) const = 0; + virtual bool verify(const bytes& data, + const bytes& sig, + const PublicKey& pk) const = 0; + + static std::unique_ptr generate_rsa(size_t bits); + +protected: + Signature(ID id_in); +}; + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/include/hpke/userinfo_vc.h b/mlspp/lib/hpke/include/hpke/userinfo_vc.h new file mode 100755 index 0000000000..9f3cae27f4 --- /dev/null +++ b/mlspp/lib/hpke/include/hpke/userinfo_vc.h @@ -0,0 +1,82 @@ +#pragma once +#include +#include + +#include +#include +#include +#include + +using namespace mlspp::bytes_ns; + +namespace mlspp::hpke { + +struct UserInfoClaimsAddress +{ + std::optional formatted; + std::optional street_address; + std::optional locality; + std::optional region; + std::optional postal_code; + std::optional country; +}; + +struct UserInfoClaims +{ + + std::optional sub; + std::optional name; + std::optional given_name; + std::optional family_name; + std::optional middle_name; + std::optional nickname; + std::optional preferred_username; + std::optional profile; + std::optional picture; + std::optional website; + std::optional email; + std::optional email_verified; + std::optional gender; + std::optional birthdate; + std::optional zoneinfo; + std::optional locale; + std::optional phone_number; + std::optional phone_number_verified; + std::optional address; + std::optional updated_at; + + static UserInfoClaims from_json(const std::string& cred_subject); +}; + +struct UserInfoVC +{ +private: + struct ParsedCredential; + std::shared_ptr parsed_cred; + +public: + explicit UserInfoVC(std::string jwt); + UserInfoVC() = default; + UserInfoVC(const UserInfoVC& other) = default; + ~UserInfoVC() = default; + UserInfoVC& operator=(const UserInfoVC& other) = default; + UserInfoVC& operator=(UserInfoVC&& other) = default; + + const Signature& signature_algorithm() const; + std::string issuer() const; + std::optional key_id() const; + std::chrono::system_clock::time_point not_before() const; + std::chrono::system_clock::time_point not_after() const; + const std::string& raw_credential() const; + const UserInfoClaims& subject() const; + const Signature::PublicJWK& public_key() const; + + bool valid_from(const Signature::PublicKey& issuer_key) const; + + std::string raw; +}; + +bool +operator==(const UserInfoVC& lhs, const UserInfoVC& rhs); + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/src/aead_cipher.cpp b/mlspp/lib/hpke/src/aead_cipher.cpp new file mode 100755 index 0000000000..b200647789 --- /dev/null +++ b/mlspp/lib/hpke/src/aead_cipher.cpp @@ -0,0 +1,321 @@ +#include "aead_cipher.h" +#include "openssl_common.h" + +#include + +#if WITH_BORINGSSL +#include +#endif + +namespace mlspp::hpke { + +/// +/// ExportOnlyCipher +/// +bytes +ExportOnlyCipher::seal(const bytes& /* key */, + const bytes& /* nonce */, + const bytes& /* aad */, + const bytes& /* pt */) const +{ + throw std::runtime_error("seal() on export-only context"); +} + +std::optional +ExportOnlyCipher::open(const bytes& /* key */, + const bytes& /* nonce */, + const bytes& /* aad */, + const bytes& /* ct */) const +{ + throw std::runtime_error("open() on export-only context"); +} + +ExportOnlyCipher::ExportOnlyCipher() + : AEAD(AEAD::ID::export_only, 0, 0) +{ +} + +/// +/// AEADCipher +/// +AEADCipher +make_aead(AEAD::ID cipher_in) +{ + return { cipher_in }; +} + +template<> +const AEADCipher& +AEADCipher::get() +{ + static const auto instance = make_aead(AEAD::ID::AES_128_GCM); + return instance; +} + +template<> +const AEADCipher& +AEADCipher::get() +{ + static const auto instance = make_aead(AEAD::ID::AES_256_GCM); + return instance; +} + +template<> +const AEADCipher& +AEADCipher::get() +{ + static const auto instance = make_aead(AEAD::ID::CHACHA20_POLY1305); + return instance; +} + +static size_t +cipher_key_size(AEAD::ID cipher) +{ + switch (cipher) { + case AEAD::ID::AES_128_GCM: + return 16; + + case AEAD::ID::AES_256_GCM: + case AEAD::ID::CHACHA20_POLY1305: + return 32; + + default: + throw std::runtime_error("Unsupported algorithm"); + } +} + +static size_t +cipher_nonce_size(AEAD::ID cipher) +{ + switch (cipher) { + case AEAD::ID::AES_128_GCM: + case AEAD::ID::AES_256_GCM: + case AEAD::ID::CHACHA20_POLY1305: + return 12; + + default: + throw std::runtime_error("Unsupported algorithm"); + } +} + +static size_t +cipher_tag_size(AEAD::ID cipher) +{ + switch (cipher) { + case AEAD::ID::AES_128_GCM: + case AEAD::ID::AES_256_GCM: + case AEAD::ID::CHACHA20_POLY1305: + return 16; + + default: + throw std::runtime_error("Unsupported algorithm"); + } +} + +#if WITH_BORINGSSL +static const EVP_AEAD* +boringssl_cipher(AEAD::ID cipher) +{ + switch (cipher) { + case AEAD::ID::AES_128_GCM: + return EVP_aead_aes_128_gcm(); + + case AEAD::ID::AES_256_GCM: + return EVP_aead_aes_256_gcm(); + + case AEAD::ID::CHACHA20_POLY1305: + return EVP_aead_chacha20_poly1305(); + + default: + throw std::runtime_error("Unsupported algorithm"); + } +} +#else +static const EVP_CIPHER* +openssl_cipher(AEAD::ID cipher) +{ + switch (cipher) { + case AEAD::ID::AES_128_GCM: + return EVP_aes_128_gcm(); + + case AEAD::ID::AES_256_GCM: + return EVP_aes_256_gcm(); + + case AEAD::ID::CHACHA20_POLY1305: + return EVP_chacha20_poly1305(); + + default: + throw std::runtime_error("Unsupported algorithm"); + } +} +#endif // WITH_BORINGSSL + +AEADCipher::AEADCipher(AEAD::ID id_in) + : AEAD(id_in, cipher_key_size(id_in), cipher_nonce_size(id_in)) + , tag_size(cipher_tag_size(id)) +{ +} + +bytes +AEADCipher::seal(const bytes& key, + const bytes& nonce, + const bytes& aad, + const bytes& pt) const +{ +#if WITH_BORINGSSL + auto ctx = make_typed_unique( + EVP_AEAD_CTX_new(boringssl_cipher(id), key.data(), key.size(), tag_size)); + if (ctx == nullptr) { + throw openssl_error(); + } + + auto ct = bytes(pt.size() + tag_size); + auto out_len = ct.size(); + if (1 != EVP_AEAD_CTX_seal(ctx.get(), + ct.data(), + &out_len, + ct.size(), + nonce.data(), + nonce.size(), + pt.data(), + pt.size(), + aad.data(), + aad.size())) { + throw openssl_error(); + } + + return ct; +#else + auto ctx = make_typed_unique(EVP_CIPHER_CTX_new()); + if (ctx == nullptr) { + throw openssl_error(); + } + + const auto* cipher = openssl_cipher(id); + if (1 != EVP_EncryptInit(ctx.get(), cipher, key.data(), nonce.data())) { + throw openssl_error(); + } + + int outlen = 0; + if (!aad.empty()) { + if (1 != EVP_EncryptUpdate(ctx.get(), + nullptr, + &outlen, + aad.data(), + static_cast(aad.size()))) { + throw openssl_error(); + } + } + + bytes ct(pt.size()); + if (1 != EVP_EncryptUpdate(ctx.get(), + ct.data(), + &outlen, + pt.data(), + static_cast(pt.size()))) { + throw openssl_error(); + } + + // Providing nullptr as an argument is safe here because this + // function never writes with GCM; it only computes the tag + if (1 != EVP_EncryptFinal(ctx.get(), nullptr, &outlen)) { + throw openssl_error(); + } + + bytes tag(tag_size); + if (1 != EVP_CIPHER_CTX_ctrl(ctx.get(), + EVP_CTRL_GCM_GET_TAG, + static_cast(tag_size), + tag.data())) { + throw openssl_error(); + } + + ct += tag; + return ct; +#endif // WITH_BORINGSSL +} + +std::optional +AEADCipher::open(const bytes& key, + const bytes& nonce, + const bytes& aad, + const bytes& ct) const +{ + if (ct.size() < tag_size) { + throw std::runtime_error("AEAD ciphertext smaller than tag size"); + } + +#if WITH_BORINGSSL + auto ctx = make_typed_unique(EVP_AEAD_CTX_new( + boringssl_cipher(id), key.data(), key.size(), cipher_tag_size(id))); + if (ctx == nullptr) { + throw openssl_error(); + } + + auto pt = bytes(ct.size() - tag_size); + auto out_len = pt.size(); + if (1 != EVP_AEAD_CTX_open(ctx.get(), + pt.data(), + &out_len, + pt.size(), + nonce.data(), + nonce.size(), + ct.data(), + ct.size(), + aad.data(), + aad.size())) { + throw openssl_error(); + } + + return pt; +#else + auto ctx = make_typed_unique(EVP_CIPHER_CTX_new()); + if (ctx == nullptr) { + throw openssl_error(); + } + + const auto* cipher = openssl_cipher(id); + if (1 != EVP_DecryptInit(ctx.get(), cipher, key.data(), nonce.data())) { + throw openssl_error(); + } + + auto inner_ct_size = ct.size() - tag_size; + auto tag = ct.slice(inner_ct_size, ct.size()); + if (1 != EVP_CIPHER_CTX_ctrl(ctx.get(), + EVP_CTRL_GCM_SET_TAG, + static_cast(tag_size), + tag.data())) { + throw openssl_error(); + } + + int out_size = 0; + if (!aad.empty()) { + if (1 != EVP_DecryptUpdate(ctx.get(), + nullptr, + &out_size, + aad.data(), + static_cast(aad.size()))) { + throw openssl_error(); + } + } + + bytes pt(inner_ct_size); + if (1 != EVP_DecryptUpdate(ctx.get(), + pt.data(), + &out_size, + ct.data(), + static_cast(inner_ct_size))) { + throw openssl_error(); + } + + // Providing nullptr as an argument is safe here because this + // function never writes with GCM; it only verifies the tag + if (1 != EVP_DecryptFinal(ctx.get(), nullptr, &out_size)) { + throw std::runtime_error("AEAD authentication failure"); + } + + return pt; +#endif // WITH_BORINGSSL +} + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/src/aead_cipher.h b/mlspp/lib/hpke/src/aead_cipher.h new file mode 100755 index 0000000000..32a36964fc --- /dev/null +++ b/mlspp/lib/hpke/src/aead_cipher.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +namespace mlspp::hpke { + +struct ExportOnlyCipher : public AEAD +{ + ExportOnlyCipher(); + ~ExportOnlyCipher() override = default; + + bytes seal(const bytes& key, + const bytes& nonce, + const bytes& aad, + const bytes& pt) const override; + std::optional open(const bytes& key, + const bytes& nonce, + const bytes& aad, + const bytes& ct) const override; +}; + +struct AEADCipher : public AEAD +{ + template + static const AEADCipher& get(); + + ~AEADCipher() override = default; + + bytes seal(const bytes& key, + const bytes& nonce, + const bytes& aad, + const bytes& pt) const override; + std::optional open(const bytes& key, + const bytes& nonce, + const bytes& aad, + const bytes& ct) const override; + +private: + const size_t tag_size; + + AEADCipher(AEAD::ID id_in); + friend AEADCipher make_aead(AEAD::ID cipher_in); +}; + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/src/base64.cpp b/mlspp/lib/hpke/src/base64.cpp new file mode 100755 index 0000000000..7db48df75b --- /dev/null +++ b/mlspp/lib/hpke/src/base64.cpp @@ -0,0 +1,105 @@ +#include + +#include "openssl_common.h" + +#include +#include +#include + +namespace mlspp::hpke { + +std::string +to_base64(const bytes& data) +{ + if (data.empty()) { + return ""; + } + +#if WITH_BORINGSSL + const auto data_size = data.size(); +#else + const auto data_size = static_cast(data.size()); +#endif + + // base64 encoding produces 4 characters for every 3 input bytes (rounded up) + const auto out_size = (data_size + 2) / 3 * 4; + auto out = bytes(out_size + 1); // NUL terminator + + const auto result = EVP_EncodeBlock(out.data(), data.data(), data_size); + if (result != out_size) { + throw openssl_error(); + } + + out.resize(out.size() - 1); // strip NUL terminator + return to_ascii(out); +} + +std::string +to_base64url(const bytes& data) +{ + if (data.empty()) { + return ""; + } + + auto encoded = to_base64(data); + + auto pad_start = encoded.find_first_of('='); + if (pad_start != std::string::npos) { + encoded = encoded.substr(0, pad_start); + } + + std::replace(encoded.begin(), encoded.end(), '+', '-'); + std::replace(encoded.begin(), encoded.end(), '/', '_'); + + return encoded; +} + +bytes +from_base64(const std::string& enc) +{ + if (enc.length() == 0) { + return {}; + } + + if (enc.length() % 4 != 0) { + throw std::runtime_error("Base64 length is not divisible by 4"); + } + + const auto in = from_ascii(enc); + const auto in_size = static_cast(in.size()); + const auto out_size = in_size / 4 * 3; + auto out = bytes(out_size); + + const auto result = EVP_DecodeBlock(out.data(), in.data(), in_size); + if (result != out_size) { + throw openssl_error(); + } + + if (enc.substr(enc.length() - 2, enc.length()) == "==") { + out.resize(out.size() - 2); + } else if (enc.substr(enc.length() - 1, enc.length()) == "=") { + out.resize(out.size() - 1); + } + + return out; +} + +bytes +from_base64url(const std::string& enc) +{ + if (enc.empty()) { + return {}; + } + + auto enc_copy = enc; + std::replace(enc_copy.begin(), enc_copy.end(), '-', '+'); + std::replace(enc_copy.begin(), enc_copy.end(), '_', '/'); + + while (enc_copy.length() % 4 != 0) { + enc_copy += "="; + } + + return from_base64(enc_copy); +} + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/src/certificate.cpp b/mlspp/lib/hpke/src/certificate.cpp new file mode 100755 index 0000000000..f4b72dee37 --- /dev/null +++ b/mlspp/lib/hpke/src/certificate.cpp @@ -0,0 +1,539 @@ +#include "group.h" +#include "openssl_common.h" +#include "rsa.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mlspp::hpke { +/// +/// Utility functions +/// + +static std::optional +asn1_octet_string_to_bytes(const ASN1_OCTET_STRING* octets) +{ + if (octets == nullptr) { + return std::nullopt; + } + const auto* ptr = ASN1_STRING_get0_data(octets); + const auto len = ASN1_STRING_length(octets); + // NOLINTNEXTLINE (cppcoreguidelines-pro-bounds-pointer-arithmetic) + return std::vector(ptr, ptr + len); +} + +static std::string +asn1_string_to_std_string(const ASN1_STRING* asn1_string) +{ + const auto* data = ASN1_STRING_get0_data(asn1_string); + const auto data_size = static_cast(ASN1_STRING_length(asn1_string)); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + auto str = std::string(reinterpret_cast(data)); + if (str.size() != data_size) { + throw std::runtime_error("Malformed ASN.1 string"); + } + return str; +} + +static std::chrono::system_clock::time_point +asn1_time_to_chrono(const ASN1_TIME* asn1_time) +{ + auto epoch_chrono = std::chrono::system_clock::time_point(); + auto epoch_time_t = std::chrono::system_clock::to_time_t(epoch_chrono); + auto epoch_asn1 = make_typed_unique(ASN1_TIME_set(nullptr, epoch_time_t)); + if (!epoch_asn1) { + throw openssl_error(); + } + + auto secs = int(0); + auto days = int(0); + if (ASN1_TIME_diff(&days, &secs, epoch_asn1.get(), asn1_time) != 1) { + throw openssl_error(); + } + + auto delta = std::chrono::seconds(secs) + std::chrono::hours(24 * days); + return std::chrono::system_clock::time_point(delta); +} + +/// +/// ParsedCertificate +/// + +const int Certificate::NameType::organization = NID_organizationName; +const int Certificate::NameType::common_name = NID_commonName; +const int Certificate::NameType::organizational_unit = + NID_organizationalUnitName; +const int Certificate::NameType::country = NID_countryName; +const int Certificate::NameType::serial_number = NID_serialNumber; +const int Certificate::NameType::state_or_province_name = + NID_stateOrProvinceName; + +struct RFC822Name +{ + std::string value; +}; + +struct DNSName +{ + std::string value; +}; + +using GeneralName = tls::var::variant; + +struct Certificate::ParsedCertificate +{ + + static std::unique_ptr parse(const bytes& der) + { + const auto* buf = der.data(); + auto cert = + make_typed_unique(d2i_X509(nullptr, &buf, static_cast(der.size()))); + if (cert == nullptr) { + throw openssl_error(); + } + + return std::make_unique(cert.release()); + } + + static bytes compute_digest(const X509* cert) + { + const auto* md = EVP_sha256(); + auto digest = bytes(EVP_MD_size(md)); + unsigned int out_size = 0; + if (1 != X509_digest(cert, md, digest.data(), &out_size)) { + throw openssl_error(); + } + return digest; + } + + // Note: This method does not implement total general name parsing. + // Duplicate entries are not supported; if they are present, the last one + // presented by OpenSSL is chosen. + static ParsedName parse_names(const X509_NAME* x509_name) + { + if (x509_name == nullptr) { + throw openssl_error(); + } + + ParsedName parsed_name; + + for (int i = X509_NAME_entry_count(x509_name) - 1; i >= 0; i--) { + auto* entry = X509_NAME_get_entry(x509_name, i); + if (entry == nullptr) { + continue; + } + + auto* oid = X509_NAME_ENTRY_get_object(entry); + auto* asn_str = X509_NAME_ENTRY_get_data(entry); + if (oid == nullptr || asn_str == nullptr) { + continue; + } + + const int nid = OBJ_obj2nid(oid); + const std::string parsed_value = asn1_string_to_std_string(asn_str); + parsed_name[nid] = parsed_value; + } + + return parsed_name; + } + + // Parse Subject Key Identifier Extension + static std::optional parse_skid(X509* cert) + { + return asn1_octet_string_to_bytes(X509_get0_subject_key_id(cert)); + } + + // Parse Authority Key Identifier + static std::optional parse_akid(X509* cert) + { + return asn1_octet_string_to_bytes(X509_get0_authority_key_id(cert)); + } + + static std::vector parse_san(X509* cert) + { + std::vector names; + +#ifdef WITH_BORINGSSL + using san_names_nb_t = size_t; +#else + using san_names_nb_t = int; +#endif + + san_names_nb_t san_names_nb = 0; + + auto* ext_ptr = + X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + auto* san_ptr = reinterpret_cast(ext_ptr); + const auto san_names = make_typed_unique(san_ptr); + san_names_nb = sk_GENERAL_NAME_num(san_names.get()); + + // Check each name within the extension + for (san_names_nb_t i = 0; i < san_names_nb; i++) { + auto* current_name = sk_GENERAL_NAME_value(san_names.get(), i); + if (current_name->type == GEN_DNS) { + const auto dns_name = + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) + asn1_string_to_std_string(current_name->d.dNSName); + names.emplace_back(DNSName{ dns_name }); + } else if (current_name->type == GEN_EMAIL) { + const auto email = + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access + asn1_string_to_std_string(current_name->d.rfc822Name); + names.emplace_back(RFC822Name{ email }); + } + } + + return names; + } + + explicit ParsedCertificate(X509* x509_in) + : x509(x509_in, typed_delete) + , pub_key_id(public_key_algorithm(x509.get())) + , sig_algo(signature_algorithm(x509.get())) + , issuer_hash(X509_issuer_name_hash(x509.get())) + , subject_hash(X509_subject_name_hash(x509.get())) + , issuer(parse_names(X509_get_issuer_name(x509.get()))) + , subject(parse_names(X509_get_subject_name(x509.get()))) + , subject_key_id(parse_skid(x509.get())) + , authority_key_id(parse_akid(x509.get())) + , sub_alt_names(parse_san(x509.get())) + , is_ca(X509_check_ca(x509.get()) != 0) + , hash(compute_digest(x509.get())) + , not_before(asn1_time_to_chrono(X509_get0_notBefore(x509.get()))) + , not_after(asn1_time_to_chrono(X509_get0_notAfter(x509.get()))) + { + } + + ParsedCertificate(const ParsedCertificate& other) + : x509(nullptr, typed_delete) + , pub_key_id(public_key_algorithm(other.x509.get())) + , sig_algo(signature_algorithm(other.x509.get())) + , issuer_hash(other.issuer_hash) + , subject_hash(other.subject_hash) + , issuer(other.issuer) + , subject(other.subject) + , subject_key_id(other.subject_key_id) + , authority_key_id(other.authority_key_id) + , sub_alt_names(other.sub_alt_names) + , is_ca(other.is_ca) + , hash(other.hash) + , not_before(other.not_before) + , not_after(other.not_after) + { + if (1 != X509_up_ref(other.x509.get())) { + throw openssl_error(); + } + x509.reset(other.x509.get()); + } + + static Signature::ID public_key_algorithm(X509* x509) + { +#if WITH_BORINGSSL + const auto pub = make_typed_unique(X509_get_pubkey(x509)); + const auto* pub_ptr = pub.get(); +#else + const auto* pub_ptr = X509_get0_pubkey(x509); +#endif + + switch (EVP_PKEY_base_id(pub_ptr)) { + case EVP_PKEY_ED25519: + return Signature::ID::Ed25519; +#if !defined(WITH_BORINGSSL) + case EVP_PKEY_ED448: + return Signature::ID::Ed448; +#endif + case EVP_PKEY_EC: { + auto key_size = EVP_PKEY_bits(pub_ptr); + switch (key_size) { + case 256: + return Signature::ID::P256_SHA256; + case 384: + return Signature::ID::P384_SHA384; + case 521: + return Signature::ID::P521_SHA512; + default: + throw std::runtime_error("Unknown curve"); + } + } + case EVP_PKEY_RSA: + // RSA public keys are not specific to an algorithm + return Signature::ID::RSA_SHA256; + default: + break; + } + throw std::runtime_error("Unsupported public key algorithm"); + } + + static Signature::ID signature_algorithm(X509* cert) + { + auto nid = X509_get_signature_nid(cert); + switch (nid) { + case EVP_PKEY_ED25519: + return Signature::ID::Ed25519; +#if !defined(WITH_BORINGSSL) + case EVP_PKEY_ED448: + return Signature::ID::Ed448; +#endif + case NID_ecdsa_with_SHA256: + return Signature::ID::P256_SHA256; + case NID_ecdsa_with_SHA384: + return Signature::ID::P384_SHA384; + case NID_ecdsa_with_SHA512: + return Signature::ID::P521_SHA512; + case NID_sha1WithRSAEncryption: + // We fall through to SHA256 for SHA1 because we do not implement SHA-1. + case NID_sha256WithRSAEncryption: + return Signature::ID::RSA_SHA256; + case NID_sha384WithRSAEncryption: + return Signature::ID::RSA_SHA384; + case NID_sha512WithRSAEncryption: + return Signature::ID::RSA_SHA512; + default: + break; + } + + throw std::runtime_error("Unsupported signature algorithm"); + } + + typed_unique_ptr public_key() const + { + return make_typed_unique(X509_get_pubkey(x509.get())); + } + + Certificate::ExpirationStatus expiration_status() const + { + auto now = std::chrono::system_clock::now(); + + if (now < not_before) { + return Certificate::ExpirationStatus::inactive; + } + + if (now > not_after) { + return Certificate::ExpirationStatus::expired; + } + + return Certificate::ExpirationStatus::active; + } + + bytes raw() const + { + auto out = bytes(i2d_X509(x509.get(), nullptr)); + auto* ptr = out.data(); + i2d_X509(x509.get(), &ptr); + return out; + } + + typed_unique_ptr x509; + const Signature::ID pub_key_id; + const Signature::ID sig_algo; + const uint64_t issuer_hash; + const uint64_t subject_hash; + const ParsedName issuer; + const ParsedName subject; + const std::optional subject_key_id; + const std::optional authority_key_id; + const std::vector sub_alt_names; + const bool is_ca; + const bytes hash; + const std::chrono::system_clock::time_point not_before; + const std::chrono::system_clock::time_point not_after; +}; + +/// +/// Certificate +/// + +static std::unique_ptr +signature_key(EVP_PKEY* pkey) +{ + switch (EVP_PKEY_base_id(pkey)) { + case EVP_PKEY_RSA: + return std::make_unique(pkey); + + case EVP_PKEY_ED448: + case EVP_PKEY_ED25519: + case EVP_PKEY_EC: + return std::make_unique(pkey); + + default: + throw std::runtime_error("Unsupported algorithm"); + } +} + +Certificate::Certificate(std::unique_ptr&& parsed_cert_in) + : parsed_cert(std::move(parsed_cert_in)) + , public_key(signature_key(parsed_cert->public_key().release())) + , raw(parsed_cert->raw()) +{ +} + +Certificate::Certificate(const bytes& der) + : parsed_cert(ParsedCertificate::parse(der)) + , public_key(signature_key(parsed_cert->public_key().release())) + , raw(der) +{ +} + +Certificate::Certificate(const Certificate& other) + : parsed_cert(std::make_unique(*other.parsed_cert)) + , public_key(signature_key(parsed_cert->public_key().release())) + , raw(other.raw) +{ +} + +Certificate::~Certificate() = default; + +std::vector +Certificate::parse_pem(const bytes& pem) +{ + auto size_int = static_cast(pem.size()); + auto bio = make_typed_unique(BIO_new_mem_buf(pem.data(), size_int)); + if (!bio) { + throw openssl_error(); + } + + auto certs = std::vector(); + while (true) { + auto x509 = make_typed_unique( + PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr)); + if (!x509) { + // NOLINTNEXTLINE(hicpp-signed-bitwise) + auto err = ERR_GET_REASON(ERR_peek_last_error()); + if (err == PEM_R_NO_START_LINE) { + // No more objects to read + break; + } + + throw openssl_error(); + } + + auto parsed = std::make_unique(x509.release()); + certs.emplace_back(std::move(parsed)); + } + + return certs; +} + +bool +Certificate::valid_from(const Certificate& parent) const +{ + auto pub = parent.parsed_cert->public_key(); + return (1 == X509_verify(parsed_cert->x509.get(), pub.get())); +} + +uint64_t +Certificate::issuer_hash() const +{ + return parsed_cert->issuer_hash; +} + +uint64_t +Certificate::subject_hash() const +{ + return parsed_cert->subject_hash; +} + +Certificate::ParsedName +Certificate::subject() const +{ + return parsed_cert->subject; +} + +Certificate::ParsedName +Certificate::issuer() const +{ + return parsed_cert->issuer; +} + +bool +Certificate::is_ca() const +{ + return parsed_cert->is_ca; +} + +Certificate::ExpirationStatus +Certificate::expiration_status() const +{ + return parsed_cert->expiration_status(); +} + +std::optional +Certificate::subject_key_id() const +{ + return parsed_cert->subject_key_id; +} + +std::optional +Certificate::authority_key_id() const +{ + return parsed_cert->authority_key_id; +} + +std::vector +Certificate::email_addresses() const +{ + std::vector emails; + for (const auto& name : parsed_cert->sub_alt_names) { + if (tls::var::holds_alternative(name)) { + emails.emplace_back(tls::var::get(name).value); + } + } + return emails; +} + +std::vector +Certificate::dns_names() const +{ + std::vector domains; + for (const auto& name : parsed_cert->sub_alt_names) { + if (tls::var::holds_alternative(name)) { + domains.emplace_back(tls::var::get(name).value); + } + } + + return domains; +} + +bytes +Certificate::hash() const +{ + return parsed_cert->hash; +} + +std::chrono::system_clock::time_point +Certificate::not_before() const +{ + return parsed_cert->not_before; +} + +std::chrono::system_clock::time_point +Certificate::not_after() const +{ + return parsed_cert->not_after; +} + +Signature::ID +Certificate::public_key_algorithm() const +{ + return parsed_cert->pub_key_id; +} + +Signature::ID +Certificate::signature_algorithm() const +{ + return parsed_cert->sig_algo; +} + +bool +operator==(const Certificate& lhs, const Certificate& rhs) +{ + return lhs.raw == rhs.raw; +} + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/src/common.cpp b/mlspp/lib/hpke/src/common.cpp new file mode 100755 index 0000000000..dabbfd6286 --- /dev/null +++ b/mlspp/lib/hpke/src/common.cpp @@ -0,0 +1,20 @@ +#include "common.h" + +namespace mlspp::hpke { + +bytes +i2osp(uint64_t val, size_t size) +{ + auto out = bytes(size, 0); + auto max = size; + if (size > 8) { + max = 8; + } + + for (size_t i = 0; i < max; i++) { + out.at(size - i - 1) = static_cast(val >> (8 * i)); + } + return out; +} + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/src/common.h b/mlspp/lib/hpke/src/common.h new file mode 100755 index 0000000000..d6938f6a1f --- /dev/null +++ b/mlspp/lib/hpke/src/common.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace mlspp::hpke { + +bytes +i2osp(uint64_t val, size_t size); + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/src/dhkem.cpp b/mlspp/lib/hpke/src/dhkem.cpp new file mode 100755 index 0000000000..fb23c568cc --- /dev/null +++ b/mlspp/lib/hpke/src/dhkem.cpp @@ -0,0 +1,216 @@ +#include "dhkem.h" + +#include "common.h" + +namespace mlspp::hpke { + +DHKEM::PrivateKey::PrivateKey(Group::PrivateKey* group_priv_in) + : group_priv(group_priv_in) +{ +} + +std::unique_ptr +DHKEM::PrivateKey::public_key() const +{ + return group_priv->public_key(); +} + +DHKEM +make_dhkem(KEM::ID kem_id_in, const Group& group_in, const KDF& kdf_in) +{ + return { kem_id_in, group_in, kdf_in }; +} + +template<> +const DHKEM& +DHKEM::get() +{ + static const auto instance = make_dhkem(KEM::ID::DHKEM_P256_SHA256, + Group::get(), + KDF::get()); + return instance; +} + +template<> +const DHKEM& +DHKEM::get() +{ + static const auto instance = make_dhkem(KEM::ID::DHKEM_P384_SHA384, + Group::get(), + KDF::get()); + return instance; +} + +template<> +const DHKEM& +DHKEM::get() +{ + static const auto instance = make_dhkem(KEM::ID::DHKEM_P521_SHA512, + Group::get(), + KDF::get()); + return instance; +} + +template<> +const DHKEM& +DHKEM::get() +{ + static const auto instance = make_dhkem(KEM::ID::DHKEM_X25519_SHA256, + Group::get(), + KDF::get()); + return instance; +} + +#if !defined(WITH_BORINGSSL) +template<> +const DHKEM& +DHKEM::get() +{ + static const auto instance = make_dhkem(KEM::ID::DHKEM_X448_SHA512, + Group::get(), + KDF::get()); + return instance; +} +#endif + +DHKEM::DHKEM(KEM::ID kem_id_in, const Group& group_in, const KDF& kdf_in) + : KEM(kem_id_in, + kdf_in.hash_size, + group_in.pk_size, + group_in.pk_size, + group_in.sk_size) + , group(group_in) + , kdf(kdf_in) +{ + static const auto label_kem = from_ascii("KEM"); + suite_id = label_kem + i2osp(uint16_t(kem_id_in), 2); +} + +std::unique_ptr +DHKEM::generate_key_pair() const +{ + return std::make_unique( + group.generate_key_pair().release()); +} + +std::unique_ptr +DHKEM::derive_key_pair(const bytes& ikm) const +{ + return std::make_unique( + group.derive_key_pair(suite_id, ikm).release()); +} + +bytes +DHKEM::serialize(const KEM::PublicKey& pk) const +{ + const auto& gpk = dynamic_cast(pk); + return group.serialize(gpk); +} + +std::unique_ptr +DHKEM::deserialize(const bytes& enc) const +{ + return group.deserialize(enc); +} + +bytes +DHKEM::serialize_private(const KEM::PrivateKey& sk) const +{ + const auto& gsk = dynamic_cast(sk); + return group.serialize_private(*gsk.group_priv); +} + +std::unique_ptr +DHKEM::deserialize_private(const bytes& skm) const +{ + return std::make_unique(group.deserialize_private(skm).release()); +} + +std::pair +DHKEM::encap(const KEM::PublicKey& pkR) const +{ + const auto& gpkR = dynamic_cast(pkR); + + auto skE = group.generate_key_pair(); + auto pkE = skE->public_key(); + + auto zz = group.dh(*skE, gpkR); + auto enc = group.serialize(*pkE); + + auto pkRm = group.serialize(gpkR); + auto kem_context = enc + pkRm; + + auto shared_secret = extract_and_expand(zz, kem_context); + return std::make_pair(shared_secret, enc); +} + +bytes +DHKEM::decap(const bytes& enc, const KEM::PrivateKey& skR) const +{ + const auto& gskR = dynamic_cast(skR); + auto pkR = gskR.group_priv->public_key(); + auto pkE = group.deserialize(enc); + auto zz = group.dh(*gskR.group_priv, *pkE); + + auto pkRm = group.serialize(*pkR); + auto kem_context = enc + pkRm; + return extract_and_expand(zz, kem_context); +} + +std::pair +DHKEM::auth_encap(const KEM::PublicKey& pkR, const KEM::PrivateKey& skS) const +{ + const auto& gpkR = dynamic_cast(pkR); + const auto& gskS = dynamic_cast(skS); + + auto skE = group.generate_key_pair(); + auto pkE = skE->public_key(); + auto pkS = gskS.group_priv->public_key(); + + auto zzER = group.dh(*skE, gpkR); + auto zzSR = group.dh(*gskS.group_priv, gpkR); + auto zz = zzER + zzSR; + auto enc = group.serialize(*pkE); + + auto pkRm = group.serialize(gpkR); + auto pkSm = group.serialize(*pkS); + auto kem_context = enc + pkRm + pkSm; + + auto shared_secret = extract_and_expand(zz, kem_context); + return std::make_pair(shared_secret, enc); +} + +bytes +DHKEM::auth_decap(const bytes& enc, + const KEM::PublicKey& pkS, + const KEM::PrivateKey& skR) const +{ + const auto& gpkS = dynamic_cast(pkS); + const auto& gskR = dynamic_cast(skR); + + auto pkE = group.deserialize(enc); + auto pkR = gskR.group_priv->public_key(); + + auto zzER = group.dh(*gskR.group_priv, *pkE); + auto zzSR = group.dh(*gskR.group_priv, gpkS); + auto zz = zzER + zzSR; + + auto pkRm = group.serialize(*pkR); + auto pkSm = group.serialize(gpkS); + auto kem_context = enc + pkRm + pkSm; + + return extract_and_expand(zz, kem_context); +} + +bytes +DHKEM::extract_and_expand(const bytes& dh, const bytes& kem_context) const +{ + static const auto label_eae_prk = from_ascii("eae_prk"); + static const auto label_shared_secret = from_ascii("shared_secret"); + + auto eae_prk = kdf.labeled_extract(suite_id, {}, label_eae_prk, dh); + return kdf.labeled_expand( + suite_id, eae_prk, label_shared_secret, kem_context, secret_size); +} + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/src/dhkem.h b/mlspp/lib/hpke/src/dhkem.h new file mode 100755 index 0000000000..5a0feb9fca --- /dev/null +++ b/mlspp/lib/hpke/src/dhkem.h @@ -0,0 +1,57 @@ +#pragma once + +#include + +#include "group.h" + +namespace mlspp::hpke { + +struct DHKEM : public KEM +{ + struct PrivateKey : public KEM::PrivateKey + { + PrivateKey(Group::PrivateKey* group_priv_in); + std::unique_ptr public_key() const override; + + std::unique_ptr group_priv; + }; + + template + static const DHKEM& get(); + + ~DHKEM() override = default; + + std::unique_ptr generate_key_pair() const override; + std::unique_ptr derive_key_pair( + const bytes& ikm) const override; + + bytes serialize(const KEM::PublicKey& pk) const override; + std::unique_ptr deserialize(const bytes& enc) const override; + + bytes serialize_private(const KEM::PrivateKey& sk) const override; + std::unique_ptr deserialize_private( + const bytes& skm) const override; + + std::pair encap(const KEM::PublicKey& pk) const override; + bytes decap(const bytes& enc, const KEM::PrivateKey& sk) const override; + + std::pair auth_encap(const KEM::PublicKey& pkR, + const KEM::PrivateKey& skS) const override; + bytes auth_decap(const bytes& enc, + const KEM::PublicKey& pkS, + const KEM::PrivateKey& skR) const override; + +private: + const Group& group; + const KDF& kdf; + bytes suite_id; + + bytes extract_and_expand(const bytes& dh, const bytes& kem_context) const; + + DHKEM(KEM::ID kem_id_in, const Group& group_in, const KDF& kdf_in); + friend DHKEM make_dhkem(KEM::ID kem_id_in, + const Group& group_in, + const KDF& kdf_in); +}; + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/src/digest.cpp b/mlspp/lib/hpke/src/digest.cpp new file mode 100755 index 0000000000..28b301bc1d --- /dev/null +++ b/mlspp/lib/hpke/src/digest.cpp @@ -0,0 +1,187 @@ +#include + +#include +#include +#if defined(WITH_OPENSSL3) +#include +#endif + +#include "openssl_common.h" + +namespace mlspp::hpke { + +static const EVP_MD* +openssl_digest_type(Digest::ID digest) +{ + switch (digest) { + case Digest::ID::SHA256: + return EVP_sha256(); + + case Digest::ID::SHA384: + return EVP_sha384(); + + case Digest::ID::SHA512: + return EVP_sha512(); + + default: + throw std::runtime_error("Unsupported ciphersuite"); + } +} + +#if defined(WITH_OPENSSL3) +static std::string +openssl_digest_name(Digest::ID digest) +{ + switch (digest) { + case Digest::ID::SHA256: + return OSSL_DIGEST_NAME_SHA2_256; + + case Digest::ID::SHA384: + return OSSL_DIGEST_NAME_SHA2_384; + + case Digest::ID::SHA512: + return OSSL_DIGEST_NAME_SHA2_512; + + default: + throw std::runtime_error("Unsupported digest algorithm"); + } +} +#endif + +template<> +const Digest& +Digest::get() +{ + static const Digest instance(Digest::ID::SHA256); + return instance; +} + +template<> +const Digest& +Digest::get() +{ + static const Digest instance(Digest::ID::SHA384); + return instance; +} + +template<> +const Digest& +Digest::get() +{ + static const Digest instance(Digest::ID::SHA512); + return instance; +} + +Digest::Digest(Digest::ID id_in) + : id(id_in) + , hash_size(EVP_MD_size(openssl_digest_type(id_in))) +{ +} + +bytes +Digest::hash(const bytes& data) const +{ + auto md = bytes(hash_size); + unsigned int size = 0; + const auto* type = openssl_digest_type(id); + if (1 != + EVP_Digest(data.data(), data.size(), md.data(), &size, type, nullptr)) { + throw openssl_error(); + } + + return md; +} + +bytes +Digest::hmac(const bytes& key, const bytes& data) const +{ + auto md = bytes(hash_size); + unsigned int size = 0; + const auto* type = openssl_digest_type(id); + if (nullptr == HMAC(type, + key.data(), + static_cast(key.size()), + data.data(), + static_cast(data.size()), + md.data(), + &size)) { + throw openssl_error(); + } + + return md; +} + +bytes +Digest::hmac_for_hkdf_extract(const bytes& key, const bytes& data) const +{ +#if defined(WITH_OPENSSL3) + auto digest_name = openssl_digest_name(id); + std::array params = { + OSSL_PARAM_construct_utf8_string( + OSSL_ALG_PARAM_DIGEST, digest_name.data(), 0), + OSSL_PARAM_construct_end() + }; + const auto mac = + make_typed_unique(EVP_MAC_fetch(nullptr, OSSL_MAC_NAME_HMAC, nullptr)); + const auto ctx = make_typed_unique(EVP_MAC_CTX_new(mac.get())); +#else + const auto* type = openssl_digest_type(id); + auto ctx = make_typed_unique(HMAC_CTX_new()); +#endif + if (ctx == nullptr) { + throw openssl_error(); + } + + // Some FIPS-enabled libraries are overly conservative in their interpretation + // of NIST SP 800-131A, which requires HMAC keys to be at least 112 bits long. + // That document does not impose that requirement on HKDF, so we disable FIPS + // enforcement for purposes of HKDF. + // + // https://doi.org/10.6028/NIST.SP.800-131Ar2 + auto key_size = static_cast(key.size()); + // OpenSSL 3 does not support the flag EVP_MD_CTX_FLAG_NON_FIPS_ALLOW anymore. + // However, OpenSSL 3 in FIPS mode doesn't seem to check the HMAC key size + // constraint. +#if !defined(WITH_OPENSSL3) && !defined(WITH_BORINGSSL) + static const auto fips_min_hmac_key_len = 14; + if (FIPS_mode() != 0 && key_size < fips_min_hmac_key_len) { + HMAC_CTX_set_flags(ctx.get(), EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); + } +#endif + + // Guard against sending nullptr to HMAC_Init_ex + const auto* key_data = key.data(); + const auto non_null_zero_length_key = uint8_t(0); + if (key_data == nullptr) { + key_data = &non_null_zero_length_key; + } + + auto md = bytes(hash_size); +#if defined(WITH_OPENSSL3) + if (1 != EVP_MAC_init(ctx.get(), key_data, key_size, params.data())) { + throw openssl_error(); + } + if (1 != EVP_MAC_update(ctx.get(), data.data(), data.size())) { + throw openssl_error(); + } + size_t size = 0; + if (1 != EVP_MAC_final(ctx.get(), md.data(), &size, hash_size)) { + throw openssl_error(); + } +#else + if (1 != HMAC_Init_ex(ctx.get(), key_data, key_size, type, nullptr)) { + throw openssl_error(); + } + if (1 != HMAC_Update(ctx.get(), data.data(), data.size())) { + throw openssl_error(); + } + unsigned int size = 0; + if (1 != HMAC_Final(ctx.get(), md.data(), &size)) { + throw openssl_error(); + } +#endif + + return md; +} + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/src/group.cpp b/mlspp/lib/hpke/src/group.cpp new file mode 100755 index 0000000000..f9f9d7744c --- /dev/null +++ b/mlspp/lib/hpke/src/group.cpp @@ -0,0 +1,1077 @@ +#include "group.h" + +#include + +#include "common.h" +#include "openssl_common.h" + +#include "openssl/bn.h" +#include "openssl/ec.h" +#include "openssl/evp.h" +#include "openssl/obj_mac.h" +#if defined(WITH_OPENSSL3) +#include "openssl/core_names.h" +#include "openssl/param_build.h" +#endif + +namespace mlspp::hpke { + +static inline size_t +group_dh_size(Group::ID group_id); + +static inline const EVP_MD* +group_sig_digest(Group::ID group_id) +{ + switch (group_id) { + case Group::ID::P256: + return EVP_sha256(); + case Group::ID::P384: + return EVP_sha384(); + case Group::ID::P521: + return EVP_sha512(); + + // EdDSA does its own hashing internally + case Group::ID::Ed25519: + case Group::ID::Ed448: + return nullptr; + + // Groups not used for signature + case Group::ID::X25519: + case Group::ID::X448: + throw std::runtime_error("Signature not supported for group"); + + default: + throw std::runtime_error("Unknown group"); + } +} + +/// +/// General implementation with OpenSSL EVP_PKEY +/// + +EVPGroup::EVPGroup(Group::ID group_id, const KDF& kdf) + : Group(group_id, kdf) +{ +} + +EVPGroup::PublicKey::PublicKey(EVP_PKEY* pkey_in) + : pkey(pkey_in, typed_delete) +{ +} + +EVPGroup::PrivateKey::PrivateKey(EVP_PKEY* pkey_in) + : pkey(pkey_in, typed_delete) +{ +} + +std::unique_ptr +EVPGroup::PrivateKey::public_key() const +{ + if (1 != EVP_PKEY_up_ref(pkey.get())) { + throw openssl_error(); + } + return std::make_unique(pkey.get()); +} + +std::unique_ptr +EVPGroup::generate_key_pair() const +{ + return derive_key_pair({}, random_bytes(sk_size)); +} + +bytes +EVPGroup::dh(const Group::PrivateKey& sk, const Group::PublicKey& pk) const +{ + const auto& rsk = dynamic_cast(sk); + const auto& rpk = dynamic_cast(pk); + + // This and the next line are acceptable because the OpenSSL + // functions fail to mark the required EVP_PKEYs as const, even + // though they are not modified. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + auto* priv_pkey = const_cast(rsk.pkey.get()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + auto* pub_pkey = const_cast(rpk.pkey.get()); + + auto ctx = make_typed_unique(EVP_PKEY_CTX_new(priv_pkey, nullptr)); + if (ctx == nullptr) { + throw openssl_error(); + } + + if (1 != EVP_PKEY_derive_init(ctx.get())) { + throw openssl_error(); + } + + if (1 != EVP_PKEY_derive_set_peer(ctx.get(), pub_pkey)) { + throw openssl_error(); + } + + size_t out_len = 0; + if (1 != EVP_PKEY_derive(ctx.get(), nullptr, &out_len)) { + throw openssl_error(); + } + + bytes out(out_len); + uint8_t* ptr = out.data(); + if (1 != (EVP_PKEY_derive(ctx.get(), ptr, &out_len))) { + throw openssl_error(); + } + + return out; +} + +bytes +EVPGroup::sign(const bytes& data, const Group::PrivateKey& sk) const +{ + const auto& rsk = dynamic_cast(sk); + + auto ctx = make_typed_unique(EVP_MD_CTX_create()); + if (ctx == nullptr) { + throw openssl_error(); + } + + const auto* digest = group_sig_digest(id); + if (1 != + EVP_DigestSignInit(ctx.get(), nullptr, digest, nullptr, rsk.pkey.get())) { + throw openssl_error(); + } + + size_t siglen = EVP_PKEY_size(rsk.pkey.get()); + bytes sig(siglen); + if (1 != EVP_DigestSign( + ctx.get(), sig.data(), &siglen, data.data(), data.size())) { + throw openssl_error(); + } + + sig.resize(siglen); + return sig; +} + +bool +EVPGroup::verify(const bytes& data, + const bytes& sig, + const Group::PublicKey& pk) const +{ + const auto& rpk = dynamic_cast(pk); + + auto ctx = make_typed_unique(EVP_MD_CTX_create()); + if (ctx == nullptr) { + throw openssl_error(); + } + + const auto* digest = group_sig_digest(id); + if (1 != EVP_DigestVerifyInit( + ctx.get(), nullptr, digest, nullptr, rpk.pkey.get())) { + throw openssl_error(); + } + + auto rv = EVP_DigestVerify( + ctx.get(), sig.data(), sig.size(), data.data(), data.size()); + + return rv == 1; +} + +/// +/// DH over "normal" curves +/// + +struct ECKeyGroup : public EVPGroup +{ + ECKeyGroup(Group::ID group_id, const KDF& kdf) + : EVPGroup(group_id, kdf) + , curve_nid(group_to_nid(group_id)) + { + } + +#if defined(WITH_OPENSSL3) + typed_unique_ptr keypair_evp_key( + const typed_unique_ptr& priv) const + { + const auto* name = OBJ_nid2sn(curve_nid); + if (name == nullptr) { + throw std::runtime_error("Unsupported algorithm"); + } + + auto group = make_typed_unique( + EC_GROUP_new_by_curve_name_ex(nullptr, nullptr, curve_nid)); + if (group == nullptr) { + throw openssl_error(); + } + + auto pt = make_typed_unique(EC_POINT_new(group.get())); + if (pt == nullptr) { + throw openssl_error(); + } + + if (1 != EC_POINT_mul( + group.get(), pt.get(), priv.get(), nullptr, nullptr, nullptr)) { + throw openssl_error(); + } + + const auto pt_size = EC_POINT_point2oct(group.get(), + pt.get(), + POINT_CONVERSION_UNCOMPRESSED, + nullptr, + 0, + nullptr); + if (0 == pt_size) { + throw openssl_error(); + } + + bytes pub(pt_size); + if (EC_POINT_point2oct(group.get(), + pt.get(), + POINT_CONVERSION_UNCOMPRESSED, + pub.data(), + pt_size, + nullptr) != pt_size) { + throw openssl_error(); + } + + auto builder = make_typed_unique(OSSL_PARAM_BLD_new()); + if (builder == nullptr || + 1 != OSSL_PARAM_BLD_push_utf8_string( + builder.get(), OSSL_PKEY_PARAM_GROUP_NAME, name, 0) || + 1 != OSSL_PARAM_BLD_push_BN( + builder.get(), OSSL_PKEY_PARAM_PRIV_KEY, priv.get()) || + 1 != + OSSL_PARAM_BLD_push_octet_string( + builder.get(), OSSL_PKEY_PARAM_PUB_KEY, pub.data(), pub.size())) { + throw openssl_error(); + } + + auto params = make_typed_unique(OSSL_PARAM_BLD_to_param(builder.get())); + auto ctx = + make_typed_unique(EVP_PKEY_CTX_new_from_name(nullptr, "EC", nullptr)); + auto key = make_typed_unique(EVP_PKEY_new()); + auto* key_ptr = key.get(); + if (params == nullptr || ctx == nullptr || key == nullptr || + EVP_PKEY_fromdata_init(ctx.get()) <= 0 || + EVP_PKEY_fromdata( + ctx.get(), &key_ptr, EVP_PKEY_KEYPAIR, params.get()) <= 0) { + throw openssl_error(); + } + ctx.reset(); + + ctx = make_typed_unique( + EVP_PKEY_CTX_new_from_pkey(nullptr, key.get(), nullptr)); + if (EVP_PKEY_check(ctx.get()) <= 0) { + throw openssl_error(); + } + + return key; + } + + typed_unique_ptr public_evp_key(const bytes& pub) const + { + const auto* name = OBJ_nid2sn(curve_nid); + if (name == nullptr) { + throw std::runtime_error("Unsupported algorithm"); + } + + auto group = make_typed_unique( + EC_GROUP_new_by_curve_name_ex(nullptr, nullptr, curve_nid)); + if (group == nullptr) { + throw openssl_error(); + } + + auto builder = make_typed_unique(OSSL_PARAM_BLD_new()); + if (builder == nullptr || + 1 != OSSL_PARAM_BLD_push_utf8_string( + builder.get(), OSSL_PKEY_PARAM_GROUP_NAME, name, 0) || + 1 != + OSSL_PARAM_BLD_push_octet_string( + builder.get(), OSSL_PKEY_PARAM_PUB_KEY, pub.data(), pub.size())) { + throw openssl_error(); + } + + auto params = make_typed_unique(OSSL_PARAM_BLD_to_param(builder.get())); + auto ctx = + make_typed_unique(EVP_PKEY_CTX_new_from_name(nullptr, "EC", nullptr)); + auto key = make_typed_unique(EVP_PKEY_new()); + auto* key_ptr = key.get(); + if (params == nullptr || ctx == nullptr || key == nullptr || + EVP_PKEY_fromdata_init(ctx.get()) <= 0 || + EVP_PKEY_fromdata( + ctx.get(), &key_ptr, EVP_PKEY_KEYPAIR, params.get()) <= 0) { + throw openssl_error(); + } + ctx.reset(); + + ctx = make_typed_unique( + EVP_PKEY_CTX_new_from_pkey(nullptr, key.get(), nullptr)); + if (EVP_PKEY_public_check(ctx.get()) <= 0) { + throw openssl_error(); + } + + return key; + } +#endif + + std::unique_ptr derive_key_pair( + const bytes& suite_id, + const bytes& ikm) const override + { + static const int retry_limit = 255; + static const auto label_dkp_prk = from_ascii("dkp_prk"); + static const auto label_candidate = from_ascii("candidate"); + + auto dkp_prk = kdf.labeled_extract(suite_id, {}, label_dkp_prk, ikm); + +#if defined(WITH_OPENSSL3) + auto* group = EC_GROUP_new_by_curve_name_ex(nullptr, nullptr, curve_nid); + auto group_ptr = make_typed_unique(group); +#else + auto eckey = new_ec_key(); + const auto* group = EC_KEY_get0_group(eckey.get()); +#endif + + auto order = make_typed_unique(BN_new()); + if (1 != EC_GROUP_get_order(group, order.get(), nullptr)) { + throw openssl_error(); + } + + auto sk = make_typed_unique(BN_new()); + BN_zero(sk.get()); + + auto counter = int(0); + while (BN_is_zero(sk.get()) != 0 || BN_cmp(sk.get(), order.get()) != -1) { + auto ctr = i2osp(counter, 1); + auto candidate = + kdf.labeled_expand(suite_id, dkp_prk, label_candidate, ctr, sk_size); + candidate.at(0) &= bitmask(); + sk.reset(BN_bin2bn( + candidate.data(), static_cast(candidate.size()), nullptr)); + + counter += 1; + if (counter > retry_limit) { + throw std::runtime_error("DeriveKeyPair iteration limit exceeded"); + } + } + +#if defined(WITH_OPENSSL3) + auto key = keypair_evp_key(sk); + return std::make_unique(key.release()); +#else + auto pt = make_typed_unique(EC_POINT_new(group)); + EC_POINT_mul(group, pt.get(), sk.get(), nullptr, nullptr, nullptr); + + EC_KEY_set_private_key(eckey.get(), sk.get()); + EC_KEY_set_public_key(eckey.get(), pt.get()); + + auto pkey = to_pkey(eckey.release()); + return std::make_unique(pkey.release()); +#endif + } + + bytes serialize(const Group::PublicKey& pk) const override + { + const auto& rpk = dynamic_cast(pk); +#if defined(WITH_OPENSSL3) + OSSL_PARAM* param = nullptr; + if (1 != EVP_PKEY_todata(rpk.pkey.get(), EVP_PKEY_PUBLIC_KEY, ¶m)) { + throw openssl_error(); + } + auto param_ptr = make_typed_unique(param); + + const OSSL_PARAM* pk_param = + OSSL_PARAM_locate_const(param_ptr.get(), OSSL_PKEY_PARAM_PUB_KEY); + if (pk_param == nullptr) { + return bytes({}, 0); + } + + size_t len = 0; + if (1 != OSSL_PARAM_get_octet_string(pk_param, nullptr, 0, &len)) { + return bytes({}, 0); + } + + bytes buf(len); + void* data_ptr = buf.data(); + if (1 != OSSL_PARAM_get_octet_string(pk_param, &data_ptr, len, nullptr)) { + return bytes({}, 0); + } + + // Prior to OpenSSL 3.0.8, we will always get compressed point from + // OSSL_PKEY_PARAM_PUB_KEY, so we will have to do the following conversion. + // From OpenSSL 3.0.8, we can obtain the uncompressed point value by setting + // OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT appropriately. + auto group = make_typed_unique( + EC_GROUP_new_by_curve_name_ex(nullptr, nullptr, curve_nid)); + if (group == nullptr) { + return bytes({}, 0); + } + auto point = make_typed_unique(EC_POINT_new(group.get())); + const auto* oct_ptr = static_cast(data_ptr); + if (1 != + EC_POINT_oct2point(group.get(), point.get(), oct_ptr, len, nullptr)) { + return bytes({}, 0); + } + len = EC_POINT_point2oct(group.get(), + point.get(), + POINT_CONVERSION_UNCOMPRESSED, + nullptr, + 0, + nullptr); + if (0 == len) { + return bytes({}, 0); + } + bytes out(len); + auto* data = out.data(); + if (EC_POINT_point2oct(group.get(), + point.get(), + POINT_CONVERSION_UNCOMPRESSED, + data, + len, + nullptr) != len) { + return bytes({}, 0); + } +#else + auto* pub = EVP_PKEY_get0_EC_KEY(rpk.pkey.get()); + + auto len = i2o_ECPublicKey(pub, nullptr); + if (len != static_cast(pk_size)) { + throw openssl_error(); + } + + bytes out(len); + auto* data = out.data(); + if (i2o_ECPublicKey(pub, &data) == 0) { + throw openssl_error(); + } +#endif + return out; + } + + std::unique_ptr deserialize(const bytes& enc) const override + { +#if defined(WITH_OPENSSL3) + auto key = public_evp_key(enc); + if (key == nullptr) { + throw std::runtime_error("Unable to deserialize the public key"); + } + return std::make_unique(key.release()); +#else + auto eckey = new_ec_key(); + auto* eckey_ptr = eckey.get(); + const auto* data_ptr = enc.data(); + if (nullptr == + o2i_ECPublicKey(&eckey_ptr, + &data_ptr, + static_cast( // NOLINT(google-runtime-int) + enc.size()))) { + throw openssl_error(); + } + + auto pkey = to_pkey(eckey.release()); + return std::make_unique(pkey.release()); +#endif + } + + bytes serialize_private(const Group::PrivateKey& sk) const override + { + const auto& rsk = dynamic_cast(sk); +#if defined(WITH_OPENSSL3) + OSSL_PARAM* param = nullptr; + if (1 != EVP_PKEY_todata(rsk.pkey.get(), EVP_PKEY_KEYPAIR, ¶m)) { + throw openssl_error(); + } + auto param_ptr = make_typed_unique(param); + + const OSSL_PARAM* sk_param = + OSSL_PARAM_locate_const(param_ptr.get(), OSSL_PKEY_PARAM_PRIV_KEY); + if (sk_param == nullptr) { + return bytes({}, 0); + } + + BIGNUM* d = nullptr; + if (1 != OSSL_PARAM_get_BN(sk_param, &d)) { + return bytes({}, 0); + } + auto d_ptr = make_typed_unique(d); +#else + auto* eckey = EVP_PKEY_get0_EC_KEY(rsk.pkey.get()); + const auto* d = EC_KEY_get0_private_key(eckey); +#endif + + auto out = bytes(BN_num_bytes(d)); +#if WITH_BORINGSSL + // In BoringSSL, BN_bn2bin returns size_t + const auto out_size = out.size(); +#else + // In OpenSSL, BN_bn2bin returns int + const auto out_size = static_cast(out.size()); +#endif + if (BN_bn2bin(d, out.data()) != out_size) { + throw openssl_error(); + } + + const auto zeros_needed = group_dh_size(id) - out.size(); + auto leading_zeros = bytes(zeros_needed, 0); + return leading_zeros + out; + } + + std::unique_ptr deserialize_private( + const bytes& skm) const override + { +#if defined(WITH_OPENSSL3) + auto priv = make_typed_unique( + BN_bin2bn(skm.data(), static_cast(skm.size()), nullptr)); + if (priv == nullptr) { + throw std::runtime_error("Unable to deserialize the private key"); + } + auto key = keypair_evp_key(priv); + return std::make_unique(key.release()); +#else + auto eckey = new_ec_key(); + const auto* group = EC_KEY_get0_group(eckey.get()); + const auto d = make_typed_unique( + BN_bin2bn(skm.data(), static_cast(skm.size()), nullptr)); + auto pt = make_typed_unique(EC_POINT_new(group)); + + EC_POINT_mul(group, pt.get(), d.get(), nullptr, nullptr, nullptr); + EC_KEY_set_private_key(eckey.get(), d.get()); + EC_KEY_set_public_key(eckey.get(), pt.get()); + + auto pkey = to_pkey(eckey.release()); + return std::make_unique(pkey.release()); +#endif + } + + // EC Key + std::tuple coordinates( + const Group::PublicKey& pk) const override + { + auto bn_x = make_typed_unique(BN_new()); + auto bn_y = make_typed_unique(BN_new()); + const auto& rpk = dynamic_cast(pk); + +#if defined(WITH_OPENSSL3) + // Raw pointer OK here because it becomes managed as soon as possible + OSSL_PARAM* param_ptr = nullptr; + if (1 != EVP_PKEY_todata(rpk.pkey.get(), EVP_PKEY_PUBLIC_KEY, ¶m_ptr)) { + throw openssl_error(); + } + + auto param = make_typed_unique(param_ptr); + + // Raw pointer OK here because it is non-owning + const auto* pk_param = + OSSL_PARAM_locate_const(param.get(), OSSL_PKEY_PARAM_PUB_KEY); + if (pk_param == nullptr) { + throw std::runtime_error("Failed to locate OSSL_PKEY_PARAM_PUB_KEY"); + } + + // Copy the octet string representation of the key into a buffer + auto len = size_t(0); + if (1 != OSSL_PARAM_get_octet_string(pk_param, nullptr, 0, &len)) { + throw std::runtime_error("Failed to get OSSL_PKEY_PARAM_PUB_KEY len"); + } + + auto buf = bytes(len); + auto* buf_ptr = buf.data(); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + auto* buf_ptr_void = reinterpret_cast(buf_ptr); + if (1 != + OSSL_PARAM_get_octet_string(pk_param, &buf_ptr_void, len, nullptr)) { + throw std::runtime_error("Failed to get OSSL_PKEY_PARAM_PUB_KEY data"); + } + + // Parse the octet string representation into an EC_POINT + auto group = make_typed_unique( + EC_GROUP_new_by_curve_name_ex(nullptr, nullptr, curve_nid)); + if (group == nullptr) { + throw openssl_error(); + } + + auto point = make_typed_unique(EC_POINT_new(group.get())); + if (point == nullptr) { + throw openssl_error(); + } + + if (1 != + EC_POINT_oct2point(group.get(), point.get(), buf_ptr, len, nullptr)) { + throw openssl_error(); + } + + // Retrieve the affine coordinates of the point + if (1 != EC_POINT_get_affine_coordinates( + group.get(), point.get(), bn_x.get(), bn_y.get(), nullptr)) { + throw openssl_error(); + } +#else + // Raw pointers are non-owning + auto* pub = EVP_PKEY_get0_EC_KEY(rpk.pkey.get()); + const auto* point = EC_KEY_get0_public_key(pub); + const auto* group = EC_KEY_get0_group(pub); + + if (1 != EC_POINT_get_affine_coordinates_GFp( + group, point, bn_x.get(), bn_y.get(), nullptr)) { + throw openssl_error(); + } +#endif + const auto x_size = BN_num_bytes(bn_x.get()); + auto x = bytes(x_size); + if (BN_bn2bin(bn_x.get(), x.data()) != x_size) { + throw openssl_error(); + } + + const auto y_size = BN_num_bytes(bn_y.get()); + auto y = bytes(y_size); + if (BN_bn2bin(bn_y.get(), y.data()) != y_size) { + throw openssl_error(); + } + + const auto zeros_needed_x = dh_size - x.size(); + const auto zeros_needed_y = dh_size - y.size(); + auto leading_zeros_x = bytes(zeros_needed_x, 0); + auto leading_zeros_y = bytes(zeros_needed_y, 0); + + return { leading_zeros_x + x, leading_zeros_y + y }; + } + + // EC Key + std::unique_ptr public_key_from_coordinates( + const bytes& x, + const bytes& y) const override + { + auto bn_x = make_typed_unique( + BN_bin2bn(x.data(), static_cast(x.size()), nullptr)); + auto bn_y = make_typed_unique( + BN_bin2bn(y.data(), static_cast(y.size()), nullptr)); + + if (bn_x == nullptr || bn_y == nullptr) { + throw std::runtime_error("Failed to convert bn_x or bn_y"); + } + +#if defined(WITH_OPENSSL3) + const auto group = make_typed_unique( + EC_GROUP_new_by_curve_name_ex(nullptr, nullptr, curve_nid)); + if (group == nullptr) { + throw std::runtime_error("Failed to create EC_GROUP"); + } + + // Construct a point with the given coordinates + auto point = make_typed_unique(EC_POINT_new(group.get())); + if (group == nullptr) { + throw std::runtime_error("Failed to create EC_POINT"); + } + + if (1 != EC_POINT_set_affine_coordinates( + group.get(), point.get(), bn_x.get(), bn_y.get(), nullptr)) { + throw openssl_error(); + } + + // Serialize the point + const auto point_size = EC_POINT_point2oct(group.get(), + point.get(), + POINT_CONVERSION_UNCOMPRESSED, + nullptr, + 0, + nullptr); + if (0 == point_size) { + throw openssl_error(); + } + + auto pub = bytes(point_size); + if (EC_POINT_point2oct(group.get(), + point.get(), + POINT_CONVERSION_UNCOMPRESSED, + pub.data(), + point_size, + nullptr) != point_size) { + throw openssl_error(); + } + + // Initialize a public key from the serialized point + auto key = public_evp_key(pub); + return std::make_unique(key.release()); +#else + auto eckey = new_ec_key(); + if (eckey == nullptr) { + throw std::runtime_error("Failed to create EC_KEY"); + } + + // Group pointer is non-owning + const auto* group = EC_KEY_get0_group(eckey.get()); + auto point = make_typed_unique(EC_POINT_new(group)); + + if (1 != EC_POINT_set_affine_coordinates_GFp( + group, point.get(), bn_x.get(), bn_y.get(), nullptr)) { + throw openssl_error(); + } + + if (1 != EC_KEY_set_public_key(eckey.get(), point.get())) { + throw openssl_error(); + } + + auto pkey = to_pkey(eckey.release()); + return std::make_unique(pkey.release()); +#endif + } + +private: + int curve_nid; + +#if !defined(WITH_OPENSSL3) + typed_unique_ptr new_ec_key() const + { + return make_typed_unique(EC_KEY_new_by_curve_name(curve_nid)); + } + + static typed_unique_ptr to_pkey(EC_KEY* eckey) + { + auto* pkey = EVP_PKEY_new(); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) + EVP_PKEY_assign_EC_KEY(pkey, eckey); + return make_typed_unique(pkey); + } +#endif + + static inline int group_to_nid(Group::ID group_id) + { + switch (group_id) { + case Group::ID::P256: + return NID_X9_62_prime256v1; + case Group::ID::P384: + return NID_secp384r1; + case Group::ID::P521: + return NID_secp521r1; + default: + throw std::runtime_error("Unsupported algorithm"); + } + } + + uint8_t bitmask() const + { + switch (id) { + case Group::ID::P256: + case Group::ID::P384: + return 0xff; + + case Group::ID::P521: + return 0x01; + + default: + throw std::runtime_error("Unsupported algorithm"); + } + } +}; + +/// +/// DH over "raw" curves +/// + +struct RawKeyGroup : public EVPGroup +{ + RawKeyGroup(Group::ID group_id, const KDF& kdf) + : EVPGroup(group_id, kdf) + , evp_type(group_to_evp(group_id)) + { + } + + template + static const RawKeyGroup instance; + + std::unique_ptr derive_key_pair( + const bytes& suite_id, + const bytes& ikm) const override + { + static const auto label_dkp_prk = from_ascii("dkp_prk"); + static const auto label_sk = from_ascii("sk"); + + auto dkp_prk = kdf.labeled_extract(suite_id, {}, label_dkp_prk, ikm); + auto skm = kdf.labeled_expand(suite_id, dkp_prk, label_sk, {}, sk_size); + return deserialize_private(skm); + } + + bytes serialize(const Group::PublicKey& pk) const override + { + const auto& rpk = dynamic_cast(pk); + auto raw = bytes(pk_size); + auto* data_ptr = raw.data(); + auto data_len = raw.size(); + if (1 != EVP_PKEY_get_raw_public_key(rpk.pkey.get(), data_ptr, &data_len)) { + throw openssl_error(); + } + + return raw; + } + + std::unique_ptr deserialize(const bytes& enc) const override + { + auto* pkey = + EVP_PKEY_new_raw_public_key(evp_type, nullptr, enc.data(), enc.size()); + if (pkey == nullptr) { + throw openssl_error(); + } + + return std::make_unique(pkey); + } + + bytes serialize_private(const Group::PrivateKey& sk) const override + { + const auto& rsk = dynamic_cast(sk); + auto raw = bytes(sk_size); + auto* data_ptr = raw.data(); + auto data_len = raw.size(); + if (1 != + EVP_PKEY_get_raw_private_key(rsk.pkey.get(), data_ptr, &data_len)) { + throw openssl_error(); + } + + return raw; + } + + std::unique_ptr deserialize_private( + const bytes& skm) const override + { + auto* pkey = + EVP_PKEY_new_raw_private_key(evp_type, nullptr, skm.data(), skm.size()); + if (pkey == nullptr) { + throw openssl_error(); + } + + return std::make_unique(pkey); + } + + // Raw Key + std::tuple coordinates( + const Group::PublicKey& pk) const override + { + const auto& rpk = dynamic_cast(pk); + auto raw = bytes(pk_size); + auto* data_ptr = raw.data(); + auto data_len = raw.size(); + + if (1 != EVP_PKEY_get_raw_public_key(rpk.pkey.get(), data_ptr, &data_len)) { + throw openssl_error(); + } + + return { raw, {} }; + } + + // Raw Key + std::unique_ptr public_key_from_coordinates( + const bytes& x, + const bytes& /* y */) const override + { + return deserialize(x); + } + +private: + const int evp_type; + + static inline int group_to_evp(Group::ID group_id) + { + switch (group_id) { + case Group::ID::X25519: + return EVP_PKEY_X25519; + case Group::ID::X448: + return EVP_PKEY_X448; + case Group::ID::Ed25519: + return EVP_PKEY_ED25519; + case Group::ID::Ed448: + return EVP_PKEY_ED448; + default: + throw std::runtime_error("Unsupported algorithm"); + } + } +}; + +/// +/// General DH group +/// + +template<> +const Group& +Group::get() +{ + static const ECKeyGroup instance(Group::ID::P256, + KDF::get()); + + return instance; +} + +template<> +const Group& +Group::get() +{ + static const ECKeyGroup instance(Group::ID::P384, + KDF::get()); + + return instance; +} + +template<> +const Group& +Group::get() +{ + static const ECKeyGroup instance(Group::ID::P521, + KDF::get()); + + return instance; +} + +template<> +const Group& +Group::get() +{ + static const RawKeyGroup instance(Group::ID::X25519, + KDF::get()); + return instance; +} + +template<> +const Group& +Group::get() +{ + static const RawKeyGroup instance(Group::ID::Ed25519, + KDF::get()); + return instance; +} + +// BoringSSL doesn't support X448 / Ed448 +#if !defined(WITH_BORINGSSL) +template<> +const Group& +Group::get() +{ + static const RawKeyGroup instance(Group::ID::X448, + KDF::get()); + return instance; +} +#endif + +template<> +const Group& +Group::get() +{ + static const RawKeyGroup instance(Group::ID::Ed448, + KDF::get()); + return instance; +} + +static inline size_t +group_dh_size(Group::ID group_id) +{ + switch (group_id) { + case Group::ID::P256: + return 32; + case Group::ID::P384: + return 48; + case Group::ID::P521: + return 66; + case Group::ID::X25519: + return 32; + case Group::ID::X448: + return 56; + + // Non-DH groups + case Group::ID::Ed25519: + case Group::ID::Ed448: + return 0; + + default: + throw std::runtime_error("Unknown group"); + } +} + +static inline size_t +group_pk_size(Group::ID group_id) +{ + switch (group_id) { + case Group::ID::P256: + return 65; + case Group::ID::P384: + return 97; + case Group::ID::P521: + return 133; + case Group::ID::X25519: + case Group::ID::Ed25519: + return 32; + case Group::ID::X448: + return 56; + case Group::ID::Ed448: + return 57; + + default: + throw std::runtime_error("Unknown group"); + } +} + +static inline size_t +group_sk_size(Group::ID group_id) +{ + switch (group_id) { + case Group::ID::P256: + return 32; + case Group::ID::P384: + return 48; + case Group::ID::P521: + return 66; + case Group::ID::X25519: + case Group::ID::Ed25519: + return 32; + case Group::ID::X448: + return 56; + case Group::ID::Ed448: + return 57; + + default: + throw std::runtime_error("Unknown group"); + } +} + +static inline std::string +group_jwk_curve_name(Group::ID group_id) +{ + switch (group_id) { + case Group::ID::P256: + return "P-256"; + case Group::ID::P384: + return "P-384"; + case Group::ID::P521: + return "P-521"; + case Group::ID::Ed25519: + return "Ed25519"; + case Group::ID::Ed448: + return "Ed448"; + case Group::ID::X25519: + return "X25519"; + case Group::ID::X448: + return "X448"; + default: + throw std::runtime_error("Unknown group"); + } +} + +static inline std::string +group_jwk_key_type(Group::ID group_id) +{ + switch (group_id) { + case Group::ID::P256: + case Group::ID::P384: + case Group::ID::P521: + return "EC"; + case Group::ID::Ed25519: + case Group::ID::Ed448: + case Group::ID::X25519: + case Group::ID::X448: + return "OKP"; + default: + throw std::runtime_error("Unknown group"); + } +} + +Group::Group(ID group_id_in, const KDF& kdf_in) + : id(group_id_in) + , dh_size(group_dh_size(group_id_in)) + , pk_size(group_pk_size(group_id_in)) + , sk_size(group_sk_size(group_id_in)) + , jwk_key_type(group_jwk_key_type(group_id_in)) + , jwk_curve_name(group_jwk_curve_name(group_id_in)) + , kdf(kdf_in) +{ +} + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/src/group.h b/mlspp/lib/hpke/src/group.h new file mode 100755 index 0000000000..73b52c715f --- /dev/null +++ b/mlspp/lib/hpke/src/group.h @@ -0,0 +1,116 @@ +#pragma once + +#include +#include + +#include "openssl_common.h" +#include + +namespace mlspp::hpke { + +struct Group +{ + enum struct ID : uint8_t + { + P256, + P384, + P521, + X25519, + X448, + Ed25519, + Ed448, + }; + + struct PublicKey + : public KEM::PublicKey + , public Signature::PublicKey + { + virtual ~PublicKey() = default; + }; + + struct PrivateKey + { + virtual ~PrivateKey() = default; + virtual std::unique_ptr public_key() const = 0; + }; + + template + static const Group& get(); + + virtual ~Group() = default; + + const ID id; + const size_t dh_size; + const size_t pk_size; + const size_t sk_size; + const std::string jwk_key_type; + const std::string jwk_curve_name; + + virtual std::unique_ptr generate_key_pair() const = 0; + virtual std::unique_ptr derive_key_pair( + const bytes& suite_id, + const bytes& ikm) const = 0; + + virtual bytes serialize(const PublicKey& pk) const = 0; + virtual std::unique_ptr deserialize(const bytes& enc) const = 0; + + virtual bytes serialize_private(const PrivateKey& sk) const = 0; + virtual std::unique_ptr deserialize_private( + const bytes& skm) const = 0; + + virtual bytes dh(const PrivateKey& sk, const PublicKey& pk) const = 0; + + virtual bytes sign(const bytes& data, const PrivateKey& sk) const = 0; + virtual bool verify(const bytes& data, + const bytes& sig, + const PublicKey& pk) const = 0; + + virtual std::tuple coordinates(const PublicKey& pk) const = 0; + virtual std::unique_ptr public_key_from_coordinates( + const bytes& x, + const bytes& y) const = 0; + +protected: + const KDF& kdf; + + friend struct DHKEM; + + Group(ID group_id_in, const KDF& kdf_in); +}; + +struct EVPGroup : public Group +{ + EVPGroup(Group::ID group_id, const KDF& kdf); + + struct PublicKey : public Group::PublicKey + { + explicit PublicKey(EVP_PKEY* pkey_in); + ~PublicKey() override = default; + + // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes) + typed_unique_ptr pkey; + }; + + struct PrivateKey : public Group::PrivateKey + { + explicit PrivateKey(EVP_PKEY* pkey_in); + ~PrivateKey() override = default; + + std::unique_ptr public_key() const override; + + // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes) + typed_unique_ptr pkey; + }; + + std::unique_ptr generate_key_pair() const override; + + bytes dh(const Group::PrivateKey& sk, + const Group::PublicKey& pk) const override; + + bytes sign(const bytes& data, const Group::PrivateKey& sk) const override; + bool verify(const bytes& data, + const bytes& sig, + const Group::PublicKey& pk) const override; +}; + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/src/hkdf.cpp b/mlspp/lib/hpke/src/hkdf.cpp new file mode 100755 index 0000000000..274b4ad25a --- /dev/null +++ b/mlspp/lib/hpke/src/hkdf.cpp @@ -0,0 +1,79 @@ +#include "hkdf.h" +#include "openssl_common.h" + +#include +#include +#include + +namespace mlspp::hpke { + +template<> +const HKDF& +HKDF::get() +{ + static const HKDF instance(Digest::get()); + return instance; +} + +template<> +const HKDF& +HKDF::get() +{ + static const HKDF instance(Digest::get()); + return instance; +} + +template<> +const HKDF& +HKDF::get() +{ + static const HKDF instance(Digest::get()); + return instance; +} + +static KDF::ID +digest_to_kdf(Digest::ID digest_id) +{ + switch (digest_id) { + case Digest::ID::SHA256: + return KDF::ID::HKDF_SHA256; + case Digest::ID::SHA384: + return KDF::ID::HKDF_SHA384; + case Digest::ID::SHA512: + return KDF::ID::HKDF_SHA512; + } + + throw std::runtime_error("Unsupported algorithm"); +} + +HKDF::HKDF(const Digest& digest_in) + : KDF(digest_to_kdf(digest_in.id), digest_in.hash_size) + , digest(digest_in) +{ +} + +bytes +HKDF::extract(const bytes& salt, const bytes& ikm) const +{ + return digest.hmac_for_hkdf_extract(salt, ikm); +} + +bytes +HKDF::expand(const bytes& prk, const bytes& info, size_t size) const +{ + auto okm = bytes{}; + auto i = uint8_t(0x00); + auto Ti = bytes{}; + while (okm.size() < size) { + i += 1; + auto block = Ti + info + bytes{ i }; + + Ti = digest.hmac(prk, block); + okm += Ti; + } + + okm.resize(size); + return okm; +} + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/src/hkdf.h b/mlspp/lib/hpke/src/hkdf.h new file mode 100755 index 0000000000..e6951be6b5 --- /dev/null +++ b/mlspp/lib/hpke/src/hkdf.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +namespace mlspp::hpke { + +struct HKDF : public KDF +{ + template + static const HKDF& get(); + + ~HKDF() override = default; + + bytes extract(const bytes& salt, const bytes& ikm) const override; + bytes expand(const bytes& prk, const bytes& info, size_t size) const override; + +private: + const Digest& digest; + + explicit HKDF(const Digest& digest_in); +}; + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/src/hpke.cpp b/mlspp/lib/hpke/src/hpke.cpp new file mode 100755 index 0000000000..7ee2faf670 --- /dev/null +++ b/mlspp/lib/hpke/src/hpke.cpp @@ -0,0 +1,540 @@ +#include +#include + +#include "aead_cipher.h" +#include "common.h" +#include "dhkem.h" +#include "hkdf.h" + +#include +#include +#include + +namespace mlspp::hpke { + +/// +/// Helper functions and constants +/// + +static const bytes& +label_exp() +{ + static const bytes val = from_ascii("exp"); + return val; +} + +static const bytes& +label_hpke() +{ + static const bytes val = from_ascii("HPKE"); + return val; +} + +static const bytes& +label_hpke_version() +{ + static const bytes val = from_ascii("HPKE-v1"); + return val; +} + +static const bytes& +label_info_hash() +{ + static const bytes val = from_ascii("info_hash"); + return val; +} + +static const bytes& +label_key() +{ + static const bytes val = from_ascii("key"); + return val; +} + +static const bytes& +label_base_nonce() +{ + static const bytes val = from_ascii("base_nonce"); + return val; +} + +static const bytes& +label_psk_id_hash() +{ + static const bytes val = from_ascii("psk_id_hash"); + return val; +} + +static const bytes& +label_sec() +{ + static const bytes val = from_ascii("sec"); + return val; +} + +static const bytes& +label_secret() +{ + static const bytes val = from_ascii("secret"); + return val; +} + +/// +/// Factory methods for primitives +/// + +KEM::KEM(ID id_in, + size_t secret_size_in, + size_t enc_size_in, + size_t pk_size_in, + size_t sk_size_in) + : id(id_in) + , secret_size(secret_size_in) + , enc_size(enc_size_in) + , pk_size(pk_size_in) + , sk_size(sk_size_in) +{ +} + +template<> +const KEM& +KEM::get() +{ + return DHKEM::get(); +} + +template<> +const KEM& +KEM::get() +{ + return DHKEM::get(); +} + +template<> +const KEM& +KEM::get() +{ + return DHKEM::get(); +} + +template<> +const KEM& +KEM::get() +{ + return DHKEM::get(); +} + +#if !defined(WITH_BORINGSSL) +template<> +const KEM& +KEM::get() +{ + return DHKEM::get(); +} +#endif + +bytes +KEM::serialize_private(const KEM::PrivateKey& /* unused */) const +{ + throw std::runtime_error("Not implemented"); +} + +std::unique_ptr +KEM::deserialize_private(const bytes& /* unused */) const +{ + throw std::runtime_error("Not implemented"); +} + +std::pair +KEM::auth_encap(const PublicKey& /* unused */, + const PrivateKey& /* unused */) const +{ + throw std::runtime_error("Not implemented"); +} + +bytes +KEM::auth_decap(const bytes& /* unused */, + const PublicKey& /* unused */, + const PrivateKey& /* unused */) const +{ + throw std::runtime_error("Not implemented"); +} + +template<> +const KDF& +KDF::get() +{ + return HKDF::get(); +} + +template<> +const KDF& +KDF::get() +{ + return HKDF::get(); +} + +template<> +const KDF& +KDF::get() +{ + return HKDF::get(); +} + +KDF::KDF(ID id_in, size_t hash_size_in) + : id(id_in) + , hash_size(hash_size_in) +{ +} + +bytes +KDF::labeled_extract(const bytes& suite_id, + const bytes& salt, + const bytes& label, + const bytes& ikm) const +{ + auto labeled_ikm = label_hpke_version() + suite_id + label + ikm; + return extract(salt, labeled_ikm); +} + +bytes +KDF::labeled_expand(const bytes& suite_id, + const bytes& prk, + const bytes& label, + const bytes& info, + size_t size) const +{ + auto labeled_info = + i2osp(size, 2) + label_hpke_version() + suite_id + label + info; + return expand(prk, labeled_info, size); +} + +template<> +const AEAD& +AEAD::get() +{ + return AEADCipher::get(); +} + +template<> +const AEAD& +AEAD::get() +{ + return AEADCipher::get(); +} + +template<> +const AEAD& +AEAD::get() +{ + return AEADCipher::get(); +} + +template<> +const AEAD& +AEAD::get() +{ + static const auto export_only = ExportOnlyCipher{}; + return export_only; +} + +AEAD::AEAD(ID id_in, size_t key_size_in, size_t nonce_size_in) + : id(id_in) + , key_size(key_size_in) + , nonce_size(nonce_size_in) +{ +} + +/// +/// Encryption Contexts +/// + +bytes +Context::do_export(const bytes& exporter_context, size_t size) const +{ + return kdf.labeled_expand( + suite, exporter_secret, label_sec(), exporter_context, size); +} + +bytes +Context::current_nonce() const +{ + auto curr = i2osp(seq, aead.nonce_size); + return curr ^ nonce; +} + +void +Context::increment_seq() +{ + if (seq == std::numeric_limits::max()) { + throw std::runtime_error("Sequence number overflow"); + } + + seq += 1; +} + +Context::Context(bytes suite_in, + bytes key_in, + bytes nonce_in, + bytes exporter_secret_in, + const KDF& kdf_in, + const AEAD& aead_in) + : suite(std::move(suite_in)) + , key(std::move(key_in)) + , nonce(std::move(nonce_in)) + , exporter_secret(std::move(exporter_secret_in)) + , kdf(kdf_in) + , aead(aead_in) + , seq(0) +{ +} + +bool +operator==(const Context& lhs, const Context& rhs) +{ + // TODO(RLB) Compare KDF and AEAD algorithms + auto suite = (lhs.suite == rhs.suite); + auto key = (lhs.key == rhs.key); + auto nonce = (lhs.nonce == rhs.nonce); + auto exporter_secret = (lhs.exporter_secret == rhs.exporter_secret); + auto seq = (lhs.seq == rhs.seq); + return suite && key && nonce && exporter_secret && seq; +} + +SenderContext::SenderContext(Context&& c) + : Context(std::move(c)) +{ +} + +bytes +SenderContext::seal(const bytes& aad, const bytes& pt) +{ + auto ct = aead.seal(key, current_nonce(), aad, pt); + increment_seq(); + return ct; +} + +ReceiverContext::ReceiverContext(Context&& c) + : Context(std::move(c)) +{ +} + +std::optional +ReceiverContext::open(const bytes& aad, const bytes& ct) +{ + auto maybe_pt = aead.open(key, current_nonce(), aad, ct); + increment_seq(); + return maybe_pt; +} + +/// +/// HPKE +/// + +static const bytes default_psk = {}; +static const bytes default_psk_id = {}; + +static bytes +suite_id(KEM::ID kem_id, KDF::ID kdf_id, AEAD::ID aead_id) +{ + return label_hpke() + i2osp(static_cast(kem_id), 2) + + i2osp(static_cast(kdf_id), 2) + + i2osp(static_cast(aead_id), 2); +} + +static const KEM& +select_kem(KEM::ID id) +{ + switch (id) { + case KEM::ID::DHKEM_P256_SHA256: + return KEM::get(); + case KEM::ID::DHKEM_P384_SHA384: + return KEM::get(); + case KEM::ID::DHKEM_P521_SHA512: + return KEM::get(); + case KEM::ID::DHKEM_X25519_SHA256: + return KEM::get(); +#if !defined(WITH_BORINGSSL) + case KEM::ID::DHKEM_X448_SHA512: + return KEM::get(); +#endif + default: + throw std::runtime_error("Unsupported algorithm"); + } +} + +static const KDF& +select_kdf(KDF::ID id) +{ + switch (id) { + case KDF::ID::HKDF_SHA256: + return KDF::get(); + case KDF::ID::HKDF_SHA384: + return KDF::get(); + case KDF::ID::HKDF_SHA512: + return KDF::get(); + default: + throw std::runtime_error("Unsupported algorithm"); + } +} + +static const AEAD& +select_aead(AEAD::ID id) +{ + switch (id) { + case AEAD::ID::AES_128_GCM: + return AEAD::get(); + case AEAD::ID::AES_256_GCM: + return AEAD::get(); + case AEAD::ID::CHACHA20_POLY1305: + return AEAD::get(); + case AEAD::ID::export_only: + return AEAD::get(); + default: + throw std::runtime_error("Unsupported algorithm"); + } +} + +HPKE::HPKE(KEM::ID kem_id, KDF::ID kdf_id, AEAD::ID aead_id) + : suite(suite_id(kem_id, kdf_id, aead_id)) + , kem(select_kem(kem_id)) + , kdf(select_kdf(kdf_id)) + , aead(select_aead(aead_id)) +{ +} + +HPKE::SenderInfo +HPKE::setup_base_s(const KEM::PublicKey& pkR, const bytes& info) const +{ + auto [shared_secret, enc] = kem.encap(pkR); + auto ctx = + key_schedule(Mode::base, shared_secret, info, default_psk, default_psk_id); + return std::make_pair(enc, SenderContext(std::move(ctx))); +} + +ReceiverContext +HPKE::setup_base_r(const bytes& enc, + const KEM::PrivateKey& skR, + const bytes& info) const +{ + auto pkRm = kem.serialize(*skR.public_key()); + auto shared_secret = kem.decap(enc, skR); + auto ctx = + key_schedule(Mode::base, shared_secret, info, default_psk, default_psk_id); + return { std::move(ctx) }; +} + +HPKE::SenderInfo +HPKE::setup_psk_s(const KEM::PublicKey& pkR, + const bytes& info, + const bytes& psk, + const bytes& psk_id) const +{ + auto [shared_secret, enc] = kem.encap(pkR); + auto ctx = key_schedule(Mode::psk, shared_secret, info, psk, psk_id); + return std::make_pair(enc, SenderContext(std::move(ctx))); +} + +ReceiverContext +HPKE::setup_psk_r(const bytes& enc, + const KEM::PrivateKey& skR, + const bytes& info, + const bytes& psk, + const bytes& psk_id) const +{ + auto shared_secret = kem.decap(enc, skR); + auto ctx = key_schedule(Mode::psk, shared_secret, info, psk, psk_id); + return { std::move(ctx) }; +} + +HPKE::SenderInfo +HPKE::setup_auth_s(const KEM::PublicKey& pkR, + const bytes& info, + const KEM::PrivateKey& skS) const +{ + auto [shared_secret, enc] = kem.auth_encap(pkR, skS); + auto ctx = + key_schedule(Mode::auth, shared_secret, info, default_psk, default_psk_id); + return std::make_pair(enc, SenderContext(std::move(ctx))); +} + +ReceiverContext +HPKE::setup_auth_r(const bytes& enc, + const KEM::PrivateKey& skR, + const bytes& info, + const KEM::PublicKey& pkS) const +{ + auto shared_secret = kem.auth_decap(enc, pkS, skR); + auto ctx = + key_schedule(Mode::auth, shared_secret, info, default_psk, default_psk_id); + return { std::move(ctx) }; +} + +HPKE::SenderInfo +HPKE::setup_auth_psk_s(const KEM::PublicKey& pkR, + const bytes& info, + const bytes& psk, + const bytes& psk_id, + const KEM::PrivateKey& skS) const +{ + auto [shared_secret, enc] = kem.auth_encap(pkR, skS); + auto ctx = key_schedule(Mode::auth_psk, shared_secret, info, psk, psk_id); + return std::make_pair(enc, SenderContext(std::move(ctx))); +} + +ReceiverContext +HPKE::setup_auth_psk_r(const bytes& enc, + const KEM::PrivateKey& skR, + const bytes& info, + const bytes& psk, + const bytes& psk_id, + const KEM::PublicKey& pkS) const +{ + auto shared_secret = kem.auth_decap(enc, pkS, skR); + auto ctx = key_schedule(Mode::auth_psk, shared_secret, info, psk, psk_id); + return { std::move(ctx) }; +} + +bool +HPKE::verify_psk_inputs(Mode mode, const bytes& psk, const bytes& psk_id) +{ + auto got_psk = (psk != default_psk); + auto got_psk_id = (psk_id != default_psk_id); + if (got_psk != got_psk_id) { + return false; + } + + return (!got_psk && (mode == Mode::base || mode == Mode::auth)) || + (got_psk && (mode == Mode::psk || mode == Mode::auth_psk)); +} + +Context +HPKE::key_schedule(Mode mode, + const bytes& shared_secret, + const bytes& info, + const bytes& psk, + const bytes& psk_id) const +{ + if (!verify_psk_inputs(mode, psk, psk_id)) { + throw std::runtime_error("Invalid PSK inputs"); + } + + auto psk_id_hash = + kdf.labeled_extract(suite, {}, label_psk_id_hash(), psk_id); + auto info_hash = kdf.labeled_extract(suite, {}, label_info_hash(), info); + auto mode_bytes = bytes{ uint8_t(mode) }; + auto key_schedule_context = mode_bytes + psk_id_hash + info_hash; + + auto secret = kdf.labeled_extract(suite, shared_secret, label_secret(), psk); + + auto key = kdf.labeled_expand( + suite, secret, label_key(), key_schedule_context, aead.key_size); + auto nonce = kdf.labeled_expand( + suite, secret, label_base_nonce(), key_schedule_context, aead.nonce_size); + auto exporter_secret = kdf.labeled_expand( + suite, secret, label_exp(), key_schedule_context, kdf.hash_size); + + return { suite, key, nonce, exporter_secret, kdf, aead }; +} + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/src/openssl_common.cpp b/mlspp/lib/hpke/src/openssl_common.cpp new file mode 100755 index 0000000000..e520059d1c --- /dev/null +++ b/mlspp/lib/hpke/src/openssl_common.cpp @@ -0,0 +1,160 @@ +#include "openssl_common.h" + +#include +#include +#include +#include +#include +#include +#if defined(WITH_OPENSSL3) +#include +#endif + +namespace mlspp::hpke { + +template<> +void +typed_delete(EVP_CIPHER_CTX* ptr) +{ + EVP_CIPHER_CTX_free(ptr); +} + +#if WITH_BORINGSSL +template<> +void +typed_delete(EVP_AEAD_CTX* ptr) +{ + EVP_AEAD_CTX_free(ptr); +} +#endif + +template<> +void +typed_delete(EVP_PKEY_CTX* ptr) +{ + EVP_PKEY_CTX_free(ptr); +} + +template<> +void +typed_delete(EVP_MD_CTX* ptr) +{ + EVP_MD_CTX_free(ptr); +} + +#if !defined(WITH_OPENSSL3) +template<> +void +typed_delete(HMAC_CTX* ptr) +{ + HMAC_CTX_free(ptr); +} +#endif + +template<> +void +typed_delete(EVP_PKEY* ptr) +{ + EVP_PKEY_free(ptr); +} + +template<> +void +typed_delete(BIGNUM* ptr) +{ + BN_free(ptr); +} + +template<> +void +typed_delete(EC_POINT* ptr) +{ + EC_POINT_free(ptr); +} + +#if !defined(WITH_OPENSSL3) +template<> +void +typed_delete(EC_KEY* ptr) +{ + EC_KEY_free(ptr); +} +#endif + +#if defined(WITH_OPENSSL3) +template<> +void +typed_delete(EVP_MAC* ptr) +{ + EVP_MAC_free(ptr); +} + +template<> +void +typed_delete(EVP_MAC_CTX* ptr) +{ + EVP_MAC_CTX_free(ptr); +} + +template<> +void +typed_delete(EC_GROUP* ptr) +{ + EC_GROUP_free(ptr); +} + +template<> +void +typed_delete(OSSL_PARAM_BLD* ptr) +{ + OSSL_PARAM_BLD_free(ptr); +} + +template<> +void +typed_delete(OSSL_PARAM* ptr) +{ + OSSL_PARAM_free(ptr); +} +#endif + +template<> +void +typed_delete(X509* ptr) +{ + X509_free(ptr); +} + +template<> +void +typed_delete(STACK_OF(GENERAL_NAME) * ptr) +{ + sk_GENERAL_NAME_pop_free(ptr, GENERAL_NAME_free); +} + +template<> +void +typed_delete(BIO* ptr) +{ + BIO_vfree(ptr); +} + +template<> +void +typed_delete(ASN1_TIME* ptr) +{ + ASN1_TIME_free(ptr); +} + +/// +/// Map OpenSSL errors to C++ exceptions +/// + +std::runtime_error +openssl_error() +{ + auto code = ERR_get_error(); + return std::runtime_error(ERR_error_string(code, nullptr)); +} + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/src/openssl_common.h b/mlspp/lib/hpke/src/openssl_common.h new file mode 100755 index 0000000000..d9282bd83b --- /dev/null +++ b/mlspp/lib/hpke/src/openssl_common.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +namespace mlspp::hpke { + +template +void +typed_delete(T* ptr); + +template +using typed_unique_ptr = std::unique_ptr)>; + +template +typed_unique_ptr +make_typed_unique(T* ptr) +{ + return typed_unique_ptr(ptr, typed_delete); +} + +std::runtime_error +openssl_error(); + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/src/random.cpp b/mlspp/lib/hpke/src/random.cpp new file mode 100755 index 0000000000..6f00c6bae5 --- /dev/null +++ b/mlspp/lib/hpke/src/random.cpp @@ -0,0 +1,19 @@ +#include + +#include "openssl_common.h" + +#include + +namespace mlspp::hpke { + +bytes +random_bytes(size_t size) +{ + auto rand = bytes(size); + if (1 != RAND_bytes(rand.data(), static_cast(size))) { + throw openssl_error(); + } + return rand; +} + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/src/rsa.cpp b/mlspp/lib/hpke/src/rsa.cpp new file mode 100755 index 0000000000..de6aba172f --- /dev/null +++ b/mlspp/lib/hpke/src/rsa.cpp @@ -0,0 +1,207 @@ +#include "rsa.h" + +#include "common.h" +#include "openssl/rsa.h" +#include "openssl_common.h" + +namespace mlspp::hpke { + +std::unique_ptr +RSASignature::generate_key_pair() const +{ + throw std::runtime_error("Not implemented"); +} + +std::unique_ptr +RSASignature::derive_key_pair(const bytes& /*ikm*/) const +{ + throw std::runtime_error("Not implemented"); +} + +std::unique_ptr +RSASignature::generate_key_pair(size_t bits) +{ + auto ctx = make_typed_unique(EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr)); + if (ctx == nullptr) { + throw openssl_error(); + } + + if (EVP_PKEY_keygen_init(ctx.get()) <= 0) { + throw openssl_error(); + } + + // NOLINTNEXTLINE(hicpp-signed-bitwise) + if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx.get(), static_cast(bits)) <= + 0) { + throw openssl_error(); + } + + auto* pkey = static_cast(nullptr); + if (EVP_PKEY_keygen(ctx.get(), &pkey) <= 0) { + throw openssl_error(); + } + + return std::make_unique(pkey); +} + +// TODO(rlb): Implement derive() with sizes + +bytes +RSASignature::serialize(const Signature::PublicKey& pk) const +{ + const auto& rpk = dynamic_cast(pk); + const int len = i2d_PublicKey(rpk.pkey.get(), nullptr); + auto raw = bytes(len); + auto* data_ptr = raw.data(); + if (len != i2d_PublicKey(rpk.pkey.get(), &data_ptr)) { + throw openssl_error(); + } + return raw; +} + +std::unique_ptr +RSASignature::deserialize(const bytes& enc) const +{ + const auto* data_ptr = enc.data(); + auto* pkey = d2i_PublicKey( + EVP_PKEY_RSA, nullptr, &data_ptr, static_cast(enc.size())); + if (pkey == nullptr) { + throw openssl_error(); + } + return std::make_unique(pkey); +} + +bytes +RSASignature::serialize_private(const Signature::PrivateKey& sk) const +{ + const auto& rsk = dynamic_cast(sk); + const int len = i2d_PrivateKey(rsk.pkey.get(), nullptr); + auto raw = bytes(len); + auto* data_ptr = raw.data(); + if (len != i2d_PrivateKey(rsk.pkey.get(), &data_ptr)) { + throw openssl_error(); + } + + return raw; +} + +std::unique_ptr +RSASignature::deserialize_private(const bytes& skm) const +{ + const auto* data_ptr = skm.data(); + auto* pkey = d2i_PrivateKey( + EVP_PKEY_RSA, nullptr, &data_ptr, static_cast(skm.size())); + if (pkey == nullptr) { + throw openssl_error(); + } + return std::make_unique(pkey); +} + +bytes +RSASignature::sign(const bytes& data, const Signature::PrivateKey& sk) const +{ + const auto& rsk = dynamic_cast(sk); + + auto ctx = make_typed_unique(EVP_MD_CTX_create()); + if (ctx == nullptr) { + throw openssl_error(); + } + + if (1 != + EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, rsk.pkey.get())) { + throw openssl_error(); + } + + size_t siglen = EVP_PKEY_size(rsk.pkey.get()); + bytes sig(siglen); + if (1 != EVP_DigestSign( + ctx.get(), sig.data(), &siglen, data.data(), data.size())) { + throw openssl_error(); + } + + sig.resize(siglen); + return sig; +} + +bool +RSASignature::verify(const bytes& data, + const bytes& sig, + const Signature::PublicKey& pk) const +{ + const auto& rpk = dynamic_cast(pk); + + auto ctx = make_typed_unique(EVP_MD_CTX_create()); + if (ctx == nullptr) { + throw openssl_error(); + } + + if (1 != + EVP_DigestVerifyInit(ctx.get(), nullptr, md, nullptr, rpk.pkey.get())) { + throw openssl_error(); + } + + auto rv = EVP_DigestVerify( + ctx.get(), sig.data(), sig.size(), data.data(), data.size()); + + return rv == 1; +} + +// TODO(RLB) Implement these methods. No concrete need, but might be nice for +// completeness. +std::unique_ptr +RSASignature::import_jwk_private(const std::string& /* json_str */) const +{ + throw std::runtime_error("not implemented"); +} + +std::unique_ptr +RSASignature::import_jwk(const std::string& /* json_str */) const +{ + throw std::runtime_error("not implemented"); +} + +std::string +RSASignature::export_jwk_private(const Signature::PrivateKey& /* sk */) const +{ + throw std::runtime_error("not implemented"); +} + +std::string +RSASignature::export_jwk(const Signature::PublicKey& /* pk */) const +{ + throw std::runtime_error("not implemented"); +} + +const EVP_MD* +RSASignature::digest_to_md(Digest::ID digest) +{ + // NOLINTNEXTLINE(hicpp-multiway-paths-covered) + switch (digest) { + case Digest::ID::SHA256: + return EVP_sha256(); + case Digest::ID::SHA384: + return EVP_sha384(); + case Digest::ID::SHA512: + return EVP_sha512(); + default: + throw std::runtime_error("Unsupported digest"); + } +} + +Signature::ID +RSASignature::digest_to_sig(Digest::ID digest) +{ + // NOLINTNEXTLINE(hicpp-multiway-paths-covered) + switch (digest) { + case Digest::ID::SHA256: + return Signature::ID::RSA_SHA256; + case Digest::ID::SHA384: + return Signature::ID::RSA_SHA384; + case Digest::ID::SHA512: + return Signature::ID::RSA_SHA512; + default: + throw std::runtime_error("Unsupported digest"); + } +} + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/src/rsa.h b/mlspp/lib/hpke/src/rsa.h new file mode 100755 index 0000000000..dde38bde7e --- /dev/null +++ b/mlspp/lib/hpke/src/rsa.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include + +#include "openssl_common.h" +#include +#include + +namespace mlspp::hpke { + +// XXX(RLB): There is a lot of code in RSASignature that is duplicated in +// EVPGroup. I have allowed this duplication rather than factoring it out +// because I would like to be able to cleanly remove RSA later. +struct RSASignature : public Signature +{ + struct PublicKey : public Signature::PublicKey + { + explicit PublicKey(EVP_PKEY* pkey_in) + : pkey(pkey_in, typed_delete) + { + } + + ~PublicKey() override = default; + + typed_unique_ptr pkey; + }; + + struct PrivateKey : public Signature::PrivateKey + { + explicit PrivateKey(EVP_PKEY* pkey_in) + : pkey(pkey_in, typed_delete) + { + } + + ~PrivateKey() override = default; + + std::unique_ptr public_key() const override + { + if (1 != EVP_PKEY_up_ref(pkey.get())) { + throw openssl_error(); + } + return std::make_unique(pkey.get()); + } + + typed_unique_ptr pkey; + }; + + explicit RSASignature(Digest::ID digest) + : Signature(digest_to_sig(digest)) + , md(digest_to_md(digest)) + { + } + + std::unique_ptr generate_key_pair() const override; + + std::unique_ptr derive_key_pair( + const bytes& /*ikm*/) const override; + + static std::unique_ptr generate_key_pair(size_t bits); + + // TODO(rlb): Implement derive() with sizes + + bytes serialize(const Signature::PublicKey& pk) const override; + + std::unique_ptr deserialize( + const bytes& enc) const override; + + bytes serialize_private(const Signature::PrivateKey& sk) const override; + + std::unique_ptr deserialize_private( + const bytes& skm) const override; + + bytes sign(const bytes& data, const Signature::PrivateKey& sk) const override; + + bool verify(const bytes& data, + const bytes& sig, + const Signature::PublicKey& pk) const override; + + std::unique_ptr import_jwk_private( + const std::string& json_str) const override; + std::unique_ptr import_jwk( + const std::string& json_str) const override; + std::string export_jwk_private( + const Signature::PrivateKey& sk) const override; + std::string export_jwk(const Signature::PublicKey& pk) const override; + +private: + const EVP_MD* md; + + static const EVP_MD* digest_to_md(Digest::ID digest); + + static Signature::ID digest_to_sig(Digest::ID digest); +}; + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/src/signature.cpp b/mlspp/lib/hpke/src/signature.cpp new file mode 100755 index 0000000000..97d7b9181c --- /dev/null +++ b/mlspp/lib/hpke/src/signature.cpp @@ -0,0 +1,344 @@ +#include +#include +#include +#include + +#include "dhkem.h" +#include "rsa.h" + +#include +#include +#include +#include +#include +#include + +using nlohmann::json; + +namespace mlspp::hpke { + +struct GroupSignature : public Signature +{ + struct PrivateKey : public Signature::PrivateKey + { + explicit PrivateKey(Group::PrivateKey* group_priv_in) + : group_priv(group_priv_in) + { + } + + std::unique_ptr public_key() const override + { + return group_priv->public_key(); + } + + std::unique_ptr group_priv; + }; + + static Signature::ID group_to_sig(Group::ID group_id) + { + switch (group_id) { + case Group::ID::P256: + return Signature::ID::P256_SHA256; + case Group::ID::P384: + return Signature::ID::P384_SHA384; + case Group::ID::P521: + return Signature::ID::P521_SHA512; + case Group::ID::Ed25519: + return Signature::ID::Ed25519; +#if !defined(WITH_BORINGSSL) + case Group::ID::Ed448: + return Signature::ID::Ed448; +#endif + default: + throw std::runtime_error("Unsupported group"); + } + } + + explicit GroupSignature(const Group& group_in) + : Signature(group_to_sig(group_in.id)) + , group(group_in) + { + } + + std::unique_ptr generate_key_pair() const override + { + return std::make_unique(group.generate_key_pair().release()); + } + + std::unique_ptr derive_key_pair( + const bytes& ikm) const override + { + return std::make_unique( + group.derive_key_pair({}, ikm).release()); + } + + bytes serialize(const Signature::PublicKey& pk) const override + { + const auto& rpk = dynamic_cast(pk); + return group.serialize(rpk); + } + + std::unique_ptr deserialize( + const bytes& enc) const override + { + return group.deserialize(enc); + } + + bytes serialize_private(const Signature::PrivateKey& sk) const override + { + const auto& rsk = dynamic_cast(sk); + return group.serialize_private(*rsk.group_priv); + } + + std::unique_ptr deserialize_private( + const bytes& skm) const override + { + return std::make_unique( + group.deserialize_private(skm).release()); + } + + bytes sign(const bytes& data, const Signature::PrivateKey& sk) const override + { + const auto& rsk = dynamic_cast(sk); + return group.sign(data, *rsk.group_priv); + } + + bool verify(const bytes& data, + const bytes& sig, + const Signature::PublicKey& pk) const override + { + const auto& rpk = dynamic_cast(pk); + return group.verify(data, sig, rpk); + } + + std::unique_ptr import_jwk_private( + const std::string& jwk_json) const override + { + const auto jwk = validate_jwk_json(jwk_json, true); + + const auto d = from_base64url(jwk.at("d")); + auto gsk = group.deserialize_private(d); + + return std::make_unique(gsk.release()); + } + + std::unique_ptr import_jwk( + const std::string& jwk_json) const override + { + const auto jwk = validate_jwk_json(jwk_json, false); + + const auto x = from_base64url(jwk.at("x")); + auto y = bytes{}; + if (jwk.contains("y")) { + y = from_base64url(jwk.at("y")); + } + + return group.public_key_from_coordinates(x, y); + } + + std::string export_jwk(const Signature::PublicKey& pk) const override + { + const auto& gpk = dynamic_cast(pk); + const auto jwk_json = export_jwk_json(gpk); + return jwk_json.dump(); + } + + std::string export_jwk_private(const Signature::PrivateKey& sk) const override + { + const auto& gssk = dynamic_cast(sk); + const auto& gsk = gssk.group_priv; + const auto gpk = gsk->public_key(); + + auto jwk_json = export_jwk_json(*gpk); + + // encode the private key + const auto enc = serialize_private(sk); + jwk_json.emplace("d", to_base64url(enc)); + + return jwk_json.dump(); + } + +private: + const Group& group; + + json validate_jwk_json(const std::string& jwk_json, bool private_key) const + { + json jwk = json::parse(jwk_json); + + if (jwk.empty() || !jwk.contains("kty") || !jwk.contains("crv") || + !jwk.contains("x") || (private_key && !jwk.contains("d"))) { + throw std::runtime_error("malformed JWK"); + } + + if (jwk.at("kty") != group.jwk_key_type) { + throw std::runtime_error("invalid JWK key type"); + } + + if (jwk.at("crv") != group.jwk_curve_name) { + throw std::runtime_error("invalid JWK curve"); + } + + return jwk; + } + + json export_jwk_json(const Group::PublicKey& pk) const + { + const auto [x, y] = group.coordinates(pk); + + json jwk = json::object({ + { "crv", group.jwk_curve_name }, + { "kty", group.jwk_key_type }, + }); + + if (group.jwk_key_type == "EC") { + jwk.emplace("x", to_base64url(x)); + jwk.emplace("y", to_base64url(y)); + } else if (group.jwk_key_type == "OKP") { + jwk.emplace("x", to_base64url(x)); + } else { + throw std::runtime_error("unknown key type"); + } + + return jwk; + } +}; + +template<> +const Signature& +Signature::get() +{ + static const auto instance = GroupSignature(Group::get()); + return instance; +} + +template<> +const Signature& +Signature::get() +{ + static const auto instance = GroupSignature(Group::get()); + return instance; +} + +template<> +const Signature& +Signature::get() +{ + static const auto instance = GroupSignature(Group::get()); + return instance; +} + +template<> +const Signature& +Signature::get() +{ + static const auto instance = GroupSignature(Group::get()); + return instance; +} + +#if !defined(WITH_BORINGSSL) +template<> +const Signature& +Signature::get() +{ + static const auto instance = GroupSignature(Group::get()); + return instance; +} +#endif + +template<> +const Signature& +Signature::get() +{ + static const auto instance = RSASignature(Digest::ID::SHA256); + return instance; +} + +template<> +const Signature& +Signature::get() +{ + static const auto instance = RSASignature(Digest::ID::SHA384); + return instance; +} + +template<> +const Signature& +Signature::get() +{ + static const auto instance = RSASignature(Digest::ID::SHA512); + return instance; +} + +Signature::Signature(Signature::ID id_in) + : id(id_in) +{ +} + +std::unique_ptr +Signature::generate_rsa(size_t bits) +{ + return RSASignature::generate_key_pair(bits); +} + +static const Signature& +sig_from_jwk(const std::string& jwk_json) +{ + using KeyTypeAndCurve = std::tuple; + static const auto alg_sig_map = std::map + { + { { "EC", "P-256" }, Signature::get() }, + { { "EC", "P-384" }, Signature::get() }, + { { "EC", "P-512" }, Signature::get() }, + { { "OKP", "Ed25519" }, Signature::get() }, +#if !defined(WITH_BORINGSSL) + { { "OKP", "Ed448" }, Signature::get() }, +#endif + // TODO(RLB): RSA + }; + + const auto jwk = json::parse(jwk_json); + const auto& kty = jwk.at("kty"); + + auto crv = std::string(""); + if (jwk.contains("crv")) { + crv = jwk.at("crv"); + } + + const auto key = KeyTypeAndCurve{ kty, crv }; + return alg_sig_map.at(key); +} + +Signature::PrivateJWK +Signature::parse_jwk_private(const std::string& jwk_json) +{ + // XXX(RLB): This JSON-parses the JWK twice. I'm assuming that this is a less + // bad cost than changing the import_jwk method signature to take `json`. + const auto& sig = sig_from_jwk(jwk_json); + const auto jwk = json::parse(jwk_json); + auto priv = sig.import_jwk_private(jwk_json); + + auto kid = std::optional{}; + if (jwk.contains("kid")) { + kid = jwk.at("kid").get(); + } + + return { sig, kid, std::move(priv) }; +} + +Signature::PublicJWK +Signature::parse_jwk(const std::string& jwk_json) +{ + // XXX(RLB): Same double-parsing comment as with `parse_jwk_private` + const auto& sig = sig_from_jwk(jwk_json); + const auto jwk = json::parse(jwk_json); + auto pub = sig.import_jwk(jwk_json); + + auto kid = std::optional{}; + if (jwk.contains("kid")) { + kid = jwk.at("kid").get(); + } + + return { sig, kid, std::move(pub) }; +} + +} // namespace mlspp::hpke diff --git a/mlspp/lib/hpke/src/userinfo_vc.cpp b/mlspp/lib/hpke/src/userinfo_vc.cpp new file mode 100755 index 0000000000..fea3d8ea31 --- /dev/null +++ b/mlspp/lib/hpke/src/userinfo_vc.cpp @@ -0,0 +1,401 @@ +#include +#include +#include +#include +#include + +using nlohmann::json; + +namespace mlspp::hpke { + +static const std::string name_attr = "name"; +static const std::string sub_attr = "sub"; +static const std::string given_name_attr = "given_name"; +static const std::string family_name_attr = "family_name"; +static const std::string middle_name_attr = "middle_name"; +static const std::string nickname_attr = "nickname"; +static const std::string preferred_username_attr = "preferred_username"; +static const std::string profile_attr = "profile"; +static const std::string picture_attr = "picture"; +static const std::string website_attr = "website"; +static const std::string email_attr = "email"; +static const std::string email_verified_attr = "email_verified"; +static const std::string gender_attr = "gender"; +static const std::string birthdate_attr = "birthdate"; +static const std::string zoneinfo_attr = "zoneinfo"; +static const std::string locale_attr = "locale"; +static const std::string phone_number_attr = "phone_number"; +static const std::string phone_number_verified_attr = "phone_number_verified"; +static const std::string address_attr = "address"; +static const std::string address_formatted_attr = "formatted"; +static const std::string address_street_address_attr = "street_address"; +static const std::string address_locality_attr = "locality"; +static const std::string address_region_attr = "region"; +static const std::string address_postal_code_attr = "postal_code"; +static const std::string address_country_attr = "country"; +static const std::string updated_at_attr = "updated_at"; + +template +static std::optional +get_optional(const json& json_object, const std::string& field_name) +{ + if (!json_object.contains(field_name)) { + return std::nullopt; + } + + return { json_object.at(field_name).get() }; +} + +/// +/// ParsedCredential +/// +static const Signature& +signature_from_alg(const std::string& alg) +{ + static const auto alg_sig_map = std::map + { + { "ES256", Signature::get() }, + { "ES384", Signature::get() }, + { "ES512", Signature::get() }, + { "Ed25519", Signature::get() }, +#if !defined(WITH_BORINGSSL) + { "Ed448", Signature::get() }, +#endif + { "RS256", Signature::get() }, + { "RS384", Signature::get() }, + { "RS512", Signature::get() }, + }; + + return alg_sig_map.at(alg); +} + +static std::chrono::system_clock::time_point +epoch_time(int64_t seconds_since_epoch) +{ + const auto delta = std::chrono::seconds(seconds_since_epoch); + return std::chrono::system_clock::time_point(delta); +} + +static bool +is_ecdsa(const Signature& sig) +{ + return sig.id == Signature::ID::P256_SHA256 || + sig.id == Signature::ID::P384_SHA384 || + sig.id == Signature::ID::P521_SHA512; +} + +// OpenSSL expects ECDSA signatures to be in DER form. JWS provides the +// signature in raw R||S form. So we need to do some manual DER encoding. +static bytes +jws_to_der_sig(const bytes& jws_sig) +{ + // Inputs that are too large will result in invalid DER encodings with this + // code. At this size, the combination of the DER integer headers and the + // integer data will overflow the one-byte DER struct length. + static const auto max_sig_size = size_t(250); + if (jws_sig.size() > max_sig_size) { + throw std::runtime_error("JWS signature too large"); + } + + if (jws_sig.size() % 2 != 0) { + throw std::runtime_error("Malformed JWS signature"); + } + + const auto int_size = jws_sig.size() / 2; + const auto jws_sig_cut = + jws_sig.begin() + static_cast(int_size); + + // Compute the encoded size of R and S integer data, adding a zero byte if + // needed to clear the sign bit + const auto r_big = (jws_sig.at(0) >= 0x80); + const auto s_big = (jws_sig.at(int_size) >= 0x80); + + const auto r_size = int_size + (r_big ? 1 : 0); + const auto s_size = int_size + (s_big ? 1 : 0); + + // Compute the size of the DER-encoded signature + static const auto int_header_size = 2; + const auto r_int_size = int_header_size + r_size; + const auto s_int_size = int_header_size + s_size; + + const auto content_size = r_int_size + s_int_size; + const auto content_big = (content_size > 0x80); + + auto der_header_size = 2 + (content_big ? 1 : 0); + const auto der_size = der_header_size + content_size; + + // Allocate the DER buffer + auto der = bytes(der_size, 0); + + // Write the header + der.at(0) = 0x30; + if (content_big) { + der.at(1) = 0x81; + der.at(2) = static_cast(content_size); + } else { + der.at(1) = static_cast(content_size); + } + + // Write R, virtually padding with a zero byte if needed + const auto r_start = der_header_size; + const auto r_data_start = r_start + int_header_size + (r_big ? 1 : 0); + const auto r_data_begin = + der.begin() + static_cast(r_data_start); + + der.at(r_start) = 0x02; + der.at(r_start + 1) = static_cast(r_size); + std::copy(jws_sig.begin(), jws_sig_cut, r_data_begin); + + // Write S, virtually padding with a zero byte if needed + const auto s_start = der_header_size + r_int_size; + const auto s_data_start = s_start + int_header_size + (s_big ? 1 : 0); + const auto s_data_begin = + der.begin() + static_cast(s_data_start); + + der.at(s_start) = 0x02; + der.at(s_start + 1) = static_cast(s_size); + std::copy(jws_sig_cut, jws_sig.end(), s_data_begin); + + return der; +} + +struct UserInfoVC::ParsedCredential +{ + // Header fields + const Signature& signature_algorithm; // `alg` + std::optional key_id; // `kid` + + // Top-level Payload fields + std::string issuer; // `iss` + std::chrono::system_clock::time_point not_before; // `nbf` + std::chrono::system_clock::time_point not_after; // `exp` + + // Credential subject fields + UserInfoClaims credential_subject; + Signature::PublicJWK public_key; + + // Signature verification information + bytes to_be_signed; + bytes signature; + + ParsedCredential(const Signature& signature_algorithm_in, + std::optional key_id_in, + std::string issuer_in, + std::chrono::system_clock::time_point not_before_in, + std::chrono::system_clock::time_point not_after_in, + UserInfoClaims credential_subject_in, + Signature::PublicJWK&& public_key_in, + bytes to_be_signed_in, + bytes signature_in) + : signature_algorithm(signature_algorithm_in) + , key_id(std::move(key_id_in)) + , issuer(std::move(issuer_in)) + , not_before(not_before_in) + , not_after(not_after_in) + , credential_subject(std::move(credential_subject_in)) + , public_key(std::move(public_key_in)) + , to_be_signed(std::move(to_be_signed_in)) + , signature(std::move(signature_in)) + { + } + + static std::shared_ptr parse(const std::string& jwt) + { + // Split the JWT into its header, payload, and signature + const auto first_dot = jwt.find_first_of('.'); + const auto last_dot = jwt.find_last_of('.'); + if (first_dot == std::string::npos || last_dot == std::string::npos || + first_dot == last_dot || last_dot > jwt.length() - 2) { + throw std::runtime_error("malformed JWT; not enough '.' characters"); + } + + const auto header_b64 = jwt.substr(0, first_dot); + const auto payload_b64 = + jwt.substr(first_dot + 1, last_dot - first_dot - 1); + const auto signature_b64 = jwt.substr(last_dot + 1); + + // Parse the components + const auto header = json::parse(to_ascii(from_base64url(header_b64))); + const auto payload = json::parse(to_ascii(from_base64url(payload_b64))); + + // Prepare the validation inputs + const auto hdr = header.at("alg"); + const auto& sig = signature_from_alg(hdr); + const auto to_be_signed = from_ascii(header_b64 + "." + payload_b64); + auto signature = from_base64url(signature_b64); + if (is_ecdsa(sig)) { + signature = jws_to_der_sig(signature); + } + + auto kid = std::optional{}; + if (header.contains("kid")) { + kid = header.at("kid").get(); + } + + // Verify the VC parts + const auto& vc = payload.at("vc"); + + static const auto context = + std::vector{ { "https://www.w3.org/2018/credentials/v1" } }; + const auto vc_context = vc.at("@context").get>(); + if (vc_context != context) { + throw std::runtime_error("malformed VC: incorrect context value"); + } + + static const auto type = std::vector{ + "VerifiableCredential", + "UserInfoCredential", + }; + if (vc.at("type") != type) { + throw std::runtime_error("malformed VC: incorrect type value"); + } + + // Parse the subject public key + static const std::string did_jwk_prefix = "did:jwk:"; + const auto id = vc.at("credentialSubject").at("id").get(); + if (id.find(did_jwk_prefix) != 0) { + throw std::runtime_error("malformed UserInfo VC: ID is not did:jwk"); + } + + const auto jwk = to_ascii(from_base64url(id.substr(did_jwk_prefix.size()))); + auto public_key = Signature::parse_jwk(jwk); + + // Extract the salient parts + return std::make_shared( + sig, + kid, + + payload.at("iss"), + epoch_time(payload.at("nbf").get()), + epoch_time(payload.at("exp").get()), + + UserInfoClaims::from_json(vc.at("credentialSubject").dump()), + std::move(public_key), + + to_be_signed, + signature); + } + + bool verify(const Signature::PublicKey& issuer_key) + { + return signature_algorithm.verify(to_be_signed, signature, issuer_key); + } +}; + +/// +/// UserInfoClaims +/// +UserInfoClaims +UserInfoClaims::from_json(const std::string& cred_subject) +{ + const auto& cred_subject_json = nlohmann::json::parse(cred_subject); + + std::optional address_opt = {}; + + if (cred_subject_json.contains(address_attr)) { + auto address_json = cred_subject_json.at(address_attr); + address_opt = { + get_optional(address_json, address_formatted_attr), + get_optional(address_json, address_street_address_attr), + get_optional(address_json, address_locality_attr), + get_optional(address_json, address_region_attr), + get_optional(address_json, address_postal_code_attr), + get_optional(address_json, address_country_attr) + }; + } + + return { + get_optional(cred_subject_json, sub_attr), + get_optional(cred_subject_json, name_attr), + get_optional(cred_subject_json, given_name_attr), + get_optional(cred_subject_json, family_name_attr), + get_optional(cred_subject_json, middle_name_attr), + get_optional(cred_subject_json, nickname_attr), + get_optional(cred_subject_json, preferred_username_attr), + get_optional(cred_subject_json, profile_attr), + get_optional(cred_subject_json, picture_attr), + get_optional(cred_subject_json, website_attr), + get_optional(cred_subject_json, email_attr), + get_optional(cred_subject_json, email_verified_attr), + get_optional(cred_subject_json, gender_attr), + get_optional(cred_subject_json, birthdate_attr), + get_optional(cred_subject_json, zoneinfo_attr), + get_optional(cred_subject_json, locale_attr), + get_optional(cred_subject_json, phone_number_attr), + get_optional(cred_subject_json, phone_number_verified_attr), + address_opt, + get_optional(cred_subject_json, updated_at_attr), + }; +} + +/// +/// UserInfoVC +/// + +UserInfoVC::UserInfoVC(std::string jwt) + : parsed_cred(ParsedCredential::parse(jwt)) + , raw(std::move(jwt)) +{ +} + +const Signature& +UserInfoVC::signature_algorithm() const +{ + return parsed_cred->signature_algorithm; +} + +std::string +UserInfoVC::issuer() const +{ + return parsed_cred->issuer; +} + +std::optional +UserInfoVC::key_id() const +{ + return parsed_cred->key_id; +} + +bool +UserInfoVC::valid_from(const Signature::PublicKey& issuer_key) const +{ + return parsed_cred->verify(issuer_key); +} + +const std::string& +UserInfoVC::raw_credential() const +{ + return raw; +} + +const UserInfoClaims& +UserInfoVC::subject() const +{ + return parsed_cred->credential_subject; +} + +std::chrono::system_clock::time_point +UserInfoVC::not_before() const +{ + return parsed_cred->not_before; +} + +std::chrono::system_clock::time_point +UserInfoVC::not_after() const +{ + return parsed_cred->not_after; +} + +const Signature::PublicJWK& +UserInfoVC::public_key() const +{ + return parsed_cred->public_key; +} + +bool +operator==(const UserInfoVC& lhs, const UserInfoVC& rhs) +{ + return lhs.raw == rhs.raw; +} + +} // namespace mlspp::hpke diff --git a/mlspp/lib/mls_vectors/CMakeLists.txt b/mlspp/lib/mls_vectors/CMakeLists.txt new file mode 100755 index 0000000000..e5700b2d00 --- /dev/null +++ b/mlspp/lib/mls_vectors/CMakeLists.txt @@ -0,0 +1,24 @@ +set(CURRENT_LIB_NAME mls_vectors) + +### +### Library Config +### + +file(GLOB_RECURSE LIB_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h") +file(GLOB_RECURSE LIB_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") + +add_library(${CURRENT_LIB_NAME} STATIC ${LIB_HEADERS} ${LIB_SOURCES}) +add_dependencies(${CURRENT_LIB_NAME} mlspp) +target_link_libraries(${CURRENT_LIB_NAME} mlspp bytes tls_syntax) +target_include_directories(${CURRENT_LIB_NAME} + PUBLIC + $ + $ + $ +) + +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../lib/bytes/include") +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../lib/hpke/include") +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../lib/mls_vectors/include") +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../lib/tls_syntax/include") + diff --git a/mlspp/lib/mls_vectors/include/mls_vectors/mls_vectors.h b/mlspp/lib/mls_vectors/include/mls_vectors/mls_vectors.h new file mode 100755 index 0000000000..25c148a94d --- /dev/null +++ b/mlspp/lib/mls_vectors/include/mls_vectors/mls_vectors.h @@ -0,0 +1,577 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mls_vectors { + +struct PseudoRandom +{ + struct Generator + { + Generator() = default; + Generator(mlspp::CipherSuite suite_in, const std::string& label); + Generator sub(const std::string& label) const; + + bytes secret(const std::string& label) const; + bytes generate(const std::string& label, size_t size) const; + + uint16_t uint16(const std::string& label) const; + uint32_t uint32(const std::string& label) const; + uint64_t uint64(const std::string& label) const; + + mlspp::SignaturePrivateKey signature_key( + const std::string& label) const; + mlspp::HPKEPrivateKey hpke_key(const std::string& label) const; + + size_t output_length() const; + + private: + mlspp::CipherSuite suite; + bytes seed; + + Generator(mlspp::CipherSuite suite_in, bytes seed_in); + }; + + PseudoRandom() = default; + PseudoRandom(mlspp::CipherSuite suite, const std::string& label); + + Generator prg; +}; + +struct TreeMathTestVector +{ + using OptionalNode = std::optional; + + mlspp::LeafCount n_leaves; + mlspp::NodeCount n_nodes; + mlspp::NodeIndex root; + std::vector left; + std::vector right; + std::vector parent; + std::vector sibling; + + std::optional null_if_invalid( + mlspp::NodeIndex input, + mlspp::NodeIndex answer) const; + + TreeMathTestVector() = default; + TreeMathTestVector(uint32_t n_leaves); + std::optional verify() const; +}; + +struct CryptoBasicsTestVector : PseudoRandom +{ + struct RefHash + { + std::string label; + bytes value; + bytes out; + + RefHash() = default; + RefHash(mlspp::CipherSuite suite, + const PseudoRandom::Generator& prg); + std::optional verify(mlspp::CipherSuite suite) const; + }; + + struct ExpandWithLabel + { + bytes secret; + std::string label; + bytes context; + uint16_t length; + bytes out; + + ExpandWithLabel() = default; + ExpandWithLabel(mlspp::CipherSuite suite, + const PseudoRandom::Generator& prg); + std::optional verify(mlspp::CipherSuite suite) const; + }; + + struct DeriveSecret + { + bytes secret; + std::string label; + bytes out; + + DeriveSecret() = default; + DeriveSecret(mlspp::CipherSuite suite, + const PseudoRandom::Generator& prg); + std::optional verify(mlspp::CipherSuite suite) const; + }; + + struct DeriveTreeSecret + { + bytes secret; + std::string label; + uint32_t generation; + uint16_t length; + bytes out; + + DeriveTreeSecret() = default; + DeriveTreeSecret(mlspp::CipherSuite suite, + const PseudoRandom::Generator& prg); + std::optional verify(mlspp::CipherSuite suite) const; + }; + + struct SignWithLabel + { + mlspp::SignaturePrivateKey priv; + mlspp::SignaturePublicKey pub; + bytes content; + std::string label; + bytes signature; + + SignWithLabel() = default; + SignWithLabel(mlspp::CipherSuite suite, + const PseudoRandom::Generator& prg); + std::optional verify(mlspp::CipherSuite suite) const; + }; + + struct EncryptWithLabel + { + mlspp::HPKEPrivateKey priv; + mlspp::HPKEPublicKey pub; + std::string label; + bytes context; + bytes plaintext; + bytes kem_output; + bytes ciphertext; + + EncryptWithLabel() = default; + EncryptWithLabel(mlspp::CipherSuite suite, + const PseudoRandom::Generator& prg); + std::optional verify(mlspp::CipherSuite suite) const; + }; + + mlspp::CipherSuite cipher_suite; + + RefHash ref_hash; + ExpandWithLabel expand_with_label; + DeriveSecret derive_secret; + DeriveTreeSecret derive_tree_secret; + SignWithLabel sign_with_label; + EncryptWithLabel encrypt_with_label; + + CryptoBasicsTestVector() = default; + CryptoBasicsTestVector(mlspp::CipherSuite suite); + std::optional verify() const; +}; + +struct SecretTreeTestVector : PseudoRandom +{ + struct SenderData + { + bytes sender_data_secret; + bytes ciphertext; + bytes key; + bytes nonce; + + SenderData() = default; + SenderData(mlspp::CipherSuite suite, + const PseudoRandom::Generator& prg); + std::optional verify(mlspp::CipherSuite suite) const; + }; + + struct RatchetStep + { + uint32_t generation; + bytes handshake_key; + bytes handshake_nonce; + bytes application_key; + bytes application_nonce; + }; + + mlspp::CipherSuite cipher_suite; + + SenderData sender_data; + + bytes encryption_secret; + std::vector> leaves; + + SecretTreeTestVector() = default; + SecretTreeTestVector(mlspp::CipherSuite suite, + uint32_t n_leaves, + const std::vector& generations); + std::optional verify() const; +}; + +struct KeyScheduleTestVector : PseudoRandom +{ + struct Export + { + std::string label; + bytes context; + size_t length; + bytes secret; + }; + + struct Epoch + { + // Chosen by the generator + bytes tree_hash; + bytes commit_secret; + bytes psk_secret; + bytes confirmed_transcript_hash; + + // Computed values + bytes group_context; + + bytes joiner_secret; + bytes welcome_secret; + bytes init_secret; + + bytes sender_data_secret; + bytes encryption_secret; + bytes exporter_secret; + bytes epoch_authenticator; + bytes external_secret; + bytes confirmation_key; + bytes membership_key; + bytes resumption_psk; + + mlspp::HPKEPublicKey external_pub; + Export exporter; + }; + + mlspp::CipherSuite cipher_suite; + + bytes group_id; + bytes initial_init_secret; + + std::vector epochs; + + KeyScheduleTestVector() = default; + KeyScheduleTestVector(mlspp::CipherSuite suite, uint32_t n_epochs); + std::optional verify() const; +}; + +struct MessageProtectionTestVector : PseudoRandom +{ + mlspp::CipherSuite cipher_suite; + + bytes group_id; + mlspp::epoch_t epoch; + bytes tree_hash; + bytes confirmed_transcript_hash; + + mlspp::SignaturePrivateKey signature_priv; + mlspp::SignaturePublicKey signature_pub; + + bytes encryption_secret; + bytes sender_data_secret; + bytes membership_key; + + mlspp::Proposal proposal; + mlspp::MLSMessage proposal_pub; + mlspp::MLSMessage proposal_priv; + + mlspp::Commit commit; + mlspp::MLSMessage commit_pub; + mlspp::MLSMessage commit_priv; + + bytes application; + mlspp::MLSMessage application_priv; + + MessageProtectionTestVector() = default; + MessageProtectionTestVector(mlspp::CipherSuite suite); + std::optional verify(); + +private: + mlspp::GroupKeySource group_keys() const; + mlspp::GroupContext group_context() const; + + mlspp::MLSMessage protect_pub( + const mlspp::GroupContent::RawContent& raw_content) const; + mlspp::MLSMessage protect_priv( + const mlspp::GroupContent::RawContent& raw_content); + std::optional unprotect( + const mlspp::MLSMessage& message); +}; + +struct PSKSecretTestVector : PseudoRandom +{ + struct PSK + { + bytes psk_id; + bytes psk_nonce; + bytes psk; + }; + + mlspp::CipherSuite cipher_suite; + std::vector psks; + bytes psk_secret; + + PSKSecretTestVector() = default; + PSKSecretTestVector(mlspp::CipherSuite suite, size_t n_psks); + std::optional verify() const; +}; + +struct TranscriptTestVector : PseudoRandom +{ + mlspp::CipherSuite cipher_suite; + + bytes confirmation_key; + bytes interim_transcript_hash_before; + + mlspp::AuthenticatedContent authenticated_content; + + bytes confirmed_transcript_hash_after; + bytes interim_transcript_hash_after; + + TranscriptTestVector() = default; + TranscriptTestVector(mlspp::CipherSuite suite); + std::optional verify() const; +}; + +struct WelcomeTestVector : PseudoRandom +{ + mlspp::CipherSuite cipher_suite; + + mlspp::HPKEPrivateKey init_priv; + mlspp::SignaturePublicKey signer_pub; + + mlspp::MLSMessage key_package; + mlspp::MLSMessage welcome; + + WelcomeTestVector() = default; + WelcomeTestVector(mlspp::CipherSuite suite); + std::optional verify() const; +}; + +// XXX(RLB): The |structure| of the example trees below is to avoid compile +// errors from gcc's -Werror=comment when there is a '\' character at the end of +// a line. Inspired by a similar bug in Chromium: +// https://codereview.chromium.org/874663003/patch/1/10001 +enum struct TreeStructure +{ + // Full trees on N leaves, created by member k adding member k+1 + full_tree_2, + full_tree_3, + full_tree_4, + full_tree_5, + full_tree_6, + full_tree_7, + full_tree_8, + full_tree_32, + full_tree_33, + full_tree_34, + + // | W | + // | ______|______ | + // | / \ | + // | U Y | + // | __|__ __|__ | + // | / \ / \ | + // | T _ X Z | + // | / \ / \ / \ / \ | + // | A B C _ E F G H | + // + // * Start with full tree on 8 members + // * 0 commits removeing 2 and 3, and adding a new member + internal_blanks_no_skipping, + + // | W | + // | ______|______ | + // | / \ | + // | _ Y | + // | __|__ __|__ | + // | / \ / \ | + // | _ _ X Z | + // | / \ / \ / \ / \ | + // | A _ _ _ E F G H | + // + // * Start with full tree on 8 members + // * 0 commitsremoveing 1, 2, and 3 + internal_blanks_with_skipping, + + // | W[H] | + // | ______|______ | + // | / \ | + // | U Y[H] | + // | __|__ __|__ | + // | / \ / \ | + // | T V X _ | + // | / \ / \ / \ / \ | + // | A B C D E F G H | + // + // * Start with full tree on 7 members + // * 0 commits adding a member in a partial Commit (no path) + unmerged_leaves_no_skipping, + + // | W [F] | + // | ______|______ | + // | / \ | + // | U Y [F] | + // | __|__ __|__ | + // | / \ / \ | + // | T _ _ _ | + // | / \ / \ / \ / \ | + // | A B C D E F G _ | + // + // == Fig. 20 / {{parent-hash-tree}} + // * 0 creates group + // * 0 adds 1, ..., 6 in a partial Commit + // * O commits removing 5 + // * 4 commits without any proposals + // * 0 commits adding a new member in a partial Commit + unmerged_leaves_with_skipping, +}; + +extern std::array all_tree_structures; +extern std::array treekem_test_tree_structures; + +struct TreeHashTestVector : PseudoRandom +{ + mlspp::CipherSuite cipher_suite; + bytes group_id; + + mlspp::TreeKEMPublicKey tree; + std::vector tree_hashes; + std::vector> resolutions; + + TreeHashTestVector() = default; + TreeHashTestVector(mlspp::CipherSuite suite, + TreeStructure tree_structure); + std::optional verify(); +}; + +struct TreeOperationsTestVector : PseudoRandom +{ + enum struct Scenario + { + add_right_edge, + add_internal, + update, + remove_right_edge, + remove_internal, + }; + + static const std::vector all_scenarios; + + mlspp::CipherSuite cipher_suite; + + mlspp::TreeKEMPublicKey tree_before; + bytes tree_hash_before; + + mlspp::Proposal proposal; + mlspp::LeafIndex proposal_sender; + + mlspp::TreeKEMPublicKey tree_after; + bytes tree_hash_after; + + TreeOperationsTestVector() = default; + TreeOperationsTestVector(mlspp::CipherSuite suite, Scenario scenario); + std::optional verify(); +}; + +struct TreeKEMTestVector : PseudoRandom +{ + struct PathSecret + { + mlspp::NodeIndex node; + bytes path_secret; + }; + + struct LeafPrivateInfo + { + mlspp::LeafIndex index; + mlspp::HPKEPrivateKey encryption_priv; + mlspp::SignaturePrivateKey signature_priv; + std::vector path_secrets; + }; + + struct UpdatePathInfo + { + mlspp::LeafIndex sender; + mlspp::UpdatePath update_path; + std::vector> path_secrets; + bytes commit_secret; + bytes tree_hash_after; + }; + + mlspp::CipherSuite cipher_suite; + + bytes group_id; + mlspp::epoch_t epoch; + bytes confirmed_transcript_hash; + + mlspp::TreeKEMPublicKey ratchet_tree; + + std::vector leaves_private; + std::vector update_paths; + + TreeKEMTestVector() = default; + TreeKEMTestVector(mlspp::CipherSuite suite, + TreeStructure tree_structure); + std::optional verify(); +}; + +struct MessagesTestVector : PseudoRandom +{ + bytes mls_welcome; + bytes mls_group_info; + bytes mls_key_package; + + bytes ratchet_tree; + bytes group_secrets; + + bytes add_proposal; + bytes update_proposal; + bytes remove_proposal; + bytes pre_shared_key_proposal; + bytes re_init_proposal; + bytes external_init_proposal; + bytes group_context_extensions_proposal; + + bytes commit; + + bytes public_message_proposal; + bytes public_message_commit; + bytes private_message; + + MessagesTestVector(); + std::optional verify() const; +}; + +struct PassiveClientTestVector : PseudoRandom +{ + struct PSK + { + bytes psk_id; + bytes psk; + }; + + struct Epoch + { + std::vector proposals; + mlspp::MLSMessage commit; + bytes epoch_authenticator; + }; + + mlspp::CipherSuite cipher_suite; + + mlspp::MLSMessage key_package; + mlspp::SignaturePrivateKey signature_priv; + mlspp::HPKEPrivateKey encryption_priv; + mlspp::HPKEPrivateKey init_priv; + + std::vector external_psks; + + mlspp::MLSMessage welcome; + std::optional ratchet_tree; + bytes initial_epoch_authenticator; + + std::vector epochs; + + PassiveClientTestVector() = default; + std::optional verify(); +}; + +} // namespace mls_vectors diff --git a/mlspp/lib/mls_vectors/src/mls_vectors.cpp b/mlspp/lib/mls_vectors/src/mls_vectors.cpp new file mode 100755 index 0000000000..a6176efa55 --- /dev/null +++ b/mlspp/lib/mls_vectors/src/mls_vectors.cpp @@ -0,0 +1,2052 @@ +#include +#include +#include +#include + +#include // XXX + +namespace mls_vectors { + +using namespace mlspp; + +/// +/// Assertions for verifying test vectors +/// + +template, int> = 0> +std::ostream& +operator<<(std::ostream& str, const T& obj) +{ + auto u = static_cast>(obj); + return str << u; +} + +static std::ostream& +operator<<(std::ostream& str, const NodeIndex& obj) +{ + return str << obj.val; +} + +static std::ostream& +operator<<(std::ostream& str, const NodeCount& obj) +{ + return str << obj.val; +} + +template +static std::ostream& +operator<<(std::ostream& str, const std::optional& obj) +{ + if (!obj) { + return str << "(nullopt)"; + } + + return str << opt::get(obj); +} + +static std::ostream& +operator<<(std::ostream& str, const std::vector& obj) +{ + return str << to_hex(obj); +} + +template +static std::ostream& +operator<<(std::ostream& str, const std::vector& obj) +{ + for (const auto& val : obj) { + str << val << " "; + } + return str; +} + +static std::ostream& +operator<<(std::ostream& str, const GroupContent::RawContent& obj) +{ + return var::visit( + overloaded{ + [&](const Proposal&) -> std::ostream& { return str << "[Proposal]"; }, + [&](const Commit&) -> std::ostream& { return str << "[Commit]"; }, + [&](const ApplicationData&) -> std::ostream& { + return str << "[ApplicationData]"; + }, + }, + obj); +} + +template +inline std::enable_if_t +operator<<(std::ostream& str, const T& obj) +{ + return str << to_hex(tls::marshal(obj)); +} + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define VERIFY(label, test) \ + if (auto err = verify_bool(label, test)) { \ + return err; \ + } + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define VERIFY_EQUAL(label, actual, expected) \ + if (auto err = verify_equal(label, actual, expected)) { \ + return err; \ + } + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define VERIFY_TLS_RTT(label, Type, expected) \ + if (auto err = verify_round_trip(label, expected)) { \ + return err; \ + } + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define VERIFY_TLS_RTT_VAL(label, Type, expected, val) \ + if (auto err = verify_round_trip(label, expected, val)) { \ + return err; \ + } + +template +static std::optional +verify_bool(const std::string& label, const T& test) +{ + if (test) { + return std::nullopt; + } + + return label; +} + +template +static std::optional +verify_equal(const std::string& label, const T& actual, const U& expected) +{ + if (actual == expected) { + return std::nullopt; + } + + auto ss = std::stringstream(); + ss << "Error: " << label << " " << actual << " != " << expected; + return ss.str(); +} + +template +static std::optional +verify_round_trip(const std::string& label, const bytes& expected) +{ + auto noop = [](const auto& /* unused */) { return true; }; + return verify_round_trip(label, expected, noop); +} + +template +static std::optional +verify_round_trip(const std::string& label, const bytes& expected, const F& val) +{ + auto obj = T{}; + try { + obj = tls::get(expected); + } catch (const std::exception& e) { + auto ss = std::stringstream(); + ss << "Decode error: " << label << " " << e.what(); + return ss.str(); + } + + if (!val(obj)) { + auto ss = std::stringstream(); + ss << "Validation error: " << label; + return ss.str(); + } + + auto actual = tls::marshal(obj); + VERIFY_EQUAL(label, actual, expected); + return std::nullopt; +} + +/// +/// PseudoRandom +/// + +PseudoRandom::Generator::Generator(CipherSuite suite_in, + const std::string& label) + : suite(suite_in) + , seed(suite.hpke().kdf.extract({}, from_ascii(label))) +{ +} + +PseudoRandom::Generator::Generator(CipherSuite suite_in, bytes seed_in) + : suite(suite_in) + , seed(std::move(seed_in)) +{ +} + +PseudoRandom::Generator +PseudoRandom::Generator::sub(const std::string& label) const +{ + return { suite, suite.derive_secret(seed, label) }; +} + +bytes +PseudoRandom::Generator::secret(const std::string& label) const +{ + return suite.derive_secret(seed, label); +} + +bytes +PseudoRandom::Generator::generate(const std::string& label, size_t size) const +{ + return suite.expand_with_label(seed, label, {}, size); +} + +uint16_t +PseudoRandom::Generator::uint16(const std::string& label) const +{ + auto data = generate(label, 2); + return tls::get(data); +} + +uint32_t +PseudoRandom::Generator::uint32(const std::string& label) const +{ + auto data = generate(label, 4); + return tls::get(data); +} + +uint64_t +PseudoRandom::Generator::uint64(const std::string& label) const +{ + auto data = generate(label, 8); + return tls::get(data); +} + +SignaturePrivateKey +PseudoRandom::Generator::signature_key(const std::string& label) const +{ + auto data = generate(label, suite.secret_size()); + return SignaturePrivateKey::derive(suite, data); +} + +HPKEPrivateKey +PseudoRandom::Generator::hpke_key(const std::string& label) const +{ + auto data = generate(label, suite.secret_size()); + return HPKEPrivateKey::derive(suite, data); +} + +size_t +PseudoRandom::Generator::output_length() const +{ + return suite.secret_size(); +} + +PseudoRandom::PseudoRandom(CipherSuite suite, const std::string& label) + : prg(suite, label) +{ +} + +/// +/// TreeMathTestVector +/// + +// XXX(RLB): This is a hack to get the tests working in the right format. In +// reality, the tree math functions should be updated to be fallible. +std::optional +TreeMathTestVector::null_if_invalid(NodeIndex input, NodeIndex answer) const +{ + // For some invalid cases (e.g., leaf.left()), we currently return the node + // itself instead of null + if (input == answer) { + return std::nullopt; + } + + // NodeIndex::parent is irrespective of tree size, so we might step out of the + // tree under consideration. + if (answer.val >= n_nodes.val) { + return std::nullopt; + } + + return answer; +} + +TreeMathTestVector::TreeMathTestVector(uint32_t n_leaves_in) + : n_leaves(n_leaves_in) + , n_nodes(n_leaves) + , root(NodeIndex::root(n_leaves)) + , left(n_nodes.val) + , right(n_nodes.val) + , parent(n_nodes.val) + , sibling(n_nodes.val) +{ + for (NodeIndex x{ 0 }; x.val < n_nodes.val; x.val++) { + left[x.val] = null_if_invalid(x, x.left()); + right[x.val] = null_if_invalid(x, x.right()); + parent[x.val] = null_if_invalid(x, x.parent()); + sibling[x.val] = null_if_invalid(x, x.sibling()); + } +} + +std::optional +TreeMathTestVector::verify() const +{ + VERIFY_EQUAL("n_nodes", n_nodes, NodeCount(n_leaves)); + VERIFY_EQUAL("root", root, NodeIndex::root(n_leaves)); + + for (NodeIndex x{ 0 }; x.val < n_nodes.val; x.val++) { + VERIFY_EQUAL("left", null_if_invalid(x, x.left()), left[x.val]); + VERIFY_EQUAL("right", null_if_invalid(x, x.right()), right[x.val]); + VERIFY_EQUAL("parent", null_if_invalid(x, x.parent()), parent[x.val]); + VERIFY_EQUAL("sibling", null_if_invalid(x, x.sibling()), sibling[x.val]); + } + + return std::nullopt; +} + +/// +/// TreeMathTestVector +/// + +CryptoBasicsTestVector::RefHash::RefHash(CipherSuite suite, + const PseudoRandom::Generator& prg) + : label("RefHash") + , value(prg.secret("value")) + , out(suite.raw_ref(from_ascii(label), value)) +{ +} + +std::optional +CryptoBasicsTestVector::RefHash::verify(CipherSuite suite) const +{ + VERIFY_EQUAL("ref hash", out, suite.raw_ref(from_ascii(label), value)); + return std::nullopt; +} + +CryptoBasicsTestVector::ExpandWithLabel::ExpandWithLabel( + CipherSuite suite, + const PseudoRandom::Generator& prg) + : secret(prg.secret("secret")) + , label("ExpandWithLabel") + , context(prg.secret("context")) + , length(static_cast(prg.output_length())) + , out(suite.expand_with_label(secret, label, context, length)) +{ +} + +std::optional +CryptoBasicsTestVector::ExpandWithLabel::verify(CipherSuite suite) const +{ + VERIFY_EQUAL("expand with label", + out, + suite.expand_with_label(secret, label, context, length)); + return std::nullopt; +} + +CryptoBasicsTestVector::DeriveSecret::DeriveSecret( + CipherSuite suite, + const PseudoRandom::Generator& prg) + : secret(prg.secret("secret")) + , label("DeriveSecret") + , out(suite.derive_secret(secret, label)) +{ +} + +std::optional +CryptoBasicsTestVector::DeriveSecret::verify(CipherSuite suite) const +{ + VERIFY_EQUAL("derive secret", out, suite.derive_secret(secret, label)); + return std::nullopt; +} + +CryptoBasicsTestVector::DeriveTreeSecret::DeriveTreeSecret( + CipherSuite suite, + const PseudoRandom::Generator& prg) + : secret(prg.secret("secret")) + , label("DeriveTreeSecret") + , generation(prg.uint32("generation")) + , length(static_cast(prg.output_length())) + , out(suite.derive_tree_secret(secret, label, generation, length)) +{ +} + +std::optional +CryptoBasicsTestVector::DeriveTreeSecret::verify(CipherSuite suite) const +{ + VERIFY_EQUAL("derive tree secret", + out, + suite.derive_tree_secret(secret, label, generation, length)); + return std::nullopt; +} + +CryptoBasicsTestVector::SignWithLabel::SignWithLabel( + CipherSuite suite, + const PseudoRandom::Generator& prg) + : priv(prg.signature_key("priv")) + , pub(priv.public_key) + , content(prg.secret("content")) + , label("SignWithLabel") + , signature(priv.sign(suite, label, content)) +{ +} + +std::optional +CryptoBasicsTestVector::SignWithLabel::verify(CipherSuite suite) const +{ + VERIFY("verify with label", pub.verify(suite, label, content, signature)); + + auto new_signature = priv.sign(suite, label, content); + VERIFY("sign with label", pub.verify(suite, label, content, new_signature)); + + return std::nullopt; +} + +CryptoBasicsTestVector::EncryptWithLabel::EncryptWithLabel( + CipherSuite suite, + const PseudoRandom::Generator& prg) + : priv(prg.hpke_key("priv")) + , pub(priv.public_key) + , label("EncryptWithLabel") + , context(prg.secret("context")) + , plaintext(prg.secret("plaintext")) +{ + auto ct = pub.encrypt(suite, label, context, plaintext); + kem_output = ct.kem_output; + ciphertext = ct.ciphertext; +} + +std::optional +CryptoBasicsTestVector::EncryptWithLabel::verify(CipherSuite suite) const +{ + auto ct = HPKECiphertext{ kem_output, ciphertext }; + auto pt = priv.decrypt(suite, label, context, ct); + VERIFY_EQUAL("decrypt with label", pt, plaintext); + + auto new_ct = pub.encrypt(suite, label, context, plaintext); + auto new_pt = priv.decrypt(suite, label, context, new_ct); + VERIFY_EQUAL("encrypt with label", new_pt, plaintext); + + return std::nullopt; +} + +CryptoBasicsTestVector::CryptoBasicsTestVector(CipherSuite suite) + : PseudoRandom(suite, "crypto-basics") + , cipher_suite(suite) + , ref_hash(suite, prg.sub("ref_hash")) + , expand_with_label(suite, prg.sub("expand_with_label")) + , derive_secret(suite, prg.sub("derive_secret")) + , derive_tree_secret(suite, prg.sub("derive_tree_secret")) + , sign_with_label(suite, prg.sub("sign_with_label")) + , encrypt_with_label(suite, prg.sub("encrypt_with_label")) +{ +} + +std::optional +CryptoBasicsTestVector::verify() const +{ + auto result = ref_hash.verify(cipher_suite); + if (result) { + return result; + } + + result = expand_with_label.verify(cipher_suite); + if (result) { + return result; + } + + result = derive_secret.verify(cipher_suite); + if (result) { + return result; + } + + result = derive_tree_secret.verify(cipher_suite); + if (result) { + return result; + } + + result = sign_with_label.verify(cipher_suite); + if (result) { + return result; + } + + result = encrypt_with_label.verify(cipher_suite); + if (result) { + return result; + } + + return std::nullopt; +} + +/// +/// SecretTreeTestVector +/// + +SecretTreeTestVector::SenderData::SenderData(mlspp::CipherSuite suite, + const PseudoRandom::Generator& prg) + : sender_data_secret(prg.secret("sender_data_secret")) + , ciphertext(prg.secret("ciphertext")) +{ + auto key_and_nonce = + KeyScheduleEpoch::sender_data_keys(suite, sender_data_secret, ciphertext); + key = key_and_nonce.key; + nonce = key_and_nonce.nonce; +} + +std::optional +SecretTreeTestVector::SenderData::verify(mlspp::CipherSuite suite) const +{ + auto key_and_nonce = + KeyScheduleEpoch::sender_data_keys(suite, sender_data_secret, ciphertext); + VERIFY_EQUAL("sender data key", key, key_and_nonce.key); + VERIFY_EQUAL("sender data nonce", nonce, key_and_nonce.nonce); + return std::nullopt; +} + +SecretTreeTestVector::SecretTreeTestVector( + mlspp::CipherSuite suite, + uint32_t n_leaves, + const std::vector& generations) + : PseudoRandom(suite, "secret-tree") + , cipher_suite(suite) + , sender_data(suite, prg.sub("sender_data")) + , encryption_secret(prg.secret("encryption_secret")) +{ + auto src = + GroupKeySource(cipher_suite, LeafCount{ n_leaves }, encryption_secret); + leaves.resize(n_leaves); + auto zero_reuse_guard = ReuseGuard{ 0, 0, 0, 0 }; + for (uint32_t i = 0; i < n_leaves; i++) { + auto leaf = LeafIndex{ i }; + + for (const auto generation : generations) { + auto hs = + src.get(ContentType::proposal, leaf, generation, zero_reuse_guard); + auto app = + src.get(ContentType::application, leaf, generation, zero_reuse_guard); + + leaves.at(i).push_back( + RatchetStep{ generation, hs.key, hs.nonce, app.key, app.nonce }); + + src.erase(ContentType::proposal, leaf, generation); + src.erase(ContentType::application, leaf, generation); + } + } +} + +std::optional +SecretTreeTestVector::verify() const +{ + auto sender_data_error = sender_data.verify(cipher_suite); + if (sender_data_error) { + return sender_data_error; + } + + auto n_leaves = static_cast(leaves.size()); + auto src = + GroupKeySource(cipher_suite, LeafCount{ n_leaves }, encryption_secret); + auto zero_reuse_guard = ReuseGuard{ 0, 0, 0, 0 }; + for (uint32_t i = 0; i < n_leaves; i++) { + auto leaf = LeafIndex{ i }; + + for (const auto& step : leaves[i]) { + auto generation = step.generation; + + auto hs = + src.get(ContentType::proposal, leaf, generation, zero_reuse_guard); + VERIFY_EQUAL("hs key", hs.key, step.handshake_key); + VERIFY_EQUAL("hs nonce", hs.nonce, step.handshake_nonce); + + auto app = + src.get(ContentType::application, leaf, generation, zero_reuse_guard); + VERIFY_EQUAL("app key", app.key, step.application_key); + VERIFY_EQUAL("app nonce", app.nonce, step.application_nonce); + } + } + + return std::nullopt; +} + +/// +/// KeyScheduleTestVector +/// + +KeyScheduleTestVector::KeyScheduleTestVector(CipherSuite suite, + uint32_t n_epochs) + : PseudoRandom(suite, "key-schedule") + , cipher_suite(suite) + , group_id(prg.secret("group_id")) + , initial_init_secret(prg.secret("group_id")) +{ + auto group_context = GroupContext{ suite, group_id, 0, {}, {}, {} }; + auto epoch = KeyScheduleEpoch(cipher_suite); + epoch.init_secret = initial_init_secret; + + for (uint64_t i = 0; i < n_epochs; i++) { + auto epoch_prg = prg.sub(to_hex(tls::marshal(i))); + + group_context.tree_hash = epoch_prg.secret("tree_hash"); + group_context.confirmed_transcript_hash = + epoch_prg.secret("confirmed_transcript_hash"); + auto ctx = tls::marshal(group_context); + + // TODO(RLB) Add Test case for externally-driven epoch change + auto commit_secret = epoch_prg.secret("commit_secret"); + auto psk_secret = epoch_prg.secret("psk_secret"); + epoch = epoch.next_raw(commit_secret, psk_secret, std::nullopt, ctx); + + auto welcome_secret = KeyScheduleEpoch::welcome_secret_raw( + cipher_suite, epoch.joiner_secret, psk_secret); + + auto exporter_prg = epoch_prg.sub("exporter"); + auto exporter_label = to_hex(exporter_prg.secret("label")); + auto exporter_context = exporter_prg.secret("context"); + auto exporter_length = cipher_suite.secret_size(); + auto exported = + epoch.do_export(exporter_label, exporter_context, exporter_length); + + epochs.push_back({ group_context.tree_hash, + commit_secret, + psk_secret, + group_context.confirmed_transcript_hash, + + ctx, + + epoch.joiner_secret, + welcome_secret, + epoch.init_secret, + + epoch.sender_data_secret, + epoch.encryption_secret, + epoch.exporter_secret, + epoch.epoch_authenticator, + epoch.external_secret, + epoch.confirmation_key, + epoch.membership_key, + epoch.resumption_psk, + + epoch.external_priv.public_key, + + { + exporter_label, + exporter_context, + exporter_length, + exported, + } }); + + group_context.epoch += 1; + } +} + +std::optional +KeyScheduleTestVector::verify() const +{ + auto group_context = GroupContext{ cipher_suite, group_id, 0, {}, {}, {} }; + auto epoch = KeyScheduleEpoch(cipher_suite); + epoch.init_secret = initial_init_secret; + + for (const auto& tve : epochs) { + group_context.tree_hash = tve.tree_hash; + group_context.confirmed_transcript_hash = tve.confirmed_transcript_hash; + auto ctx = tls::marshal(group_context); + VERIFY_EQUAL("group context", ctx, tve.group_context); + + epoch = + epoch.next_raw(tve.commit_secret, tve.psk_secret, std::nullopt, ctx); + + // Verify the rest of the epoch + VERIFY_EQUAL("joiner secret", epoch.joiner_secret, tve.joiner_secret); + + auto welcome_secret = KeyScheduleEpoch::welcome_secret_raw( + cipher_suite, tve.joiner_secret, tve.psk_secret); + VERIFY_EQUAL("welcome secret", welcome_secret, tve.welcome_secret); + + VERIFY_EQUAL( + "sender data secret", epoch.sender_data_secret, tve.sender_data_secret); + VERIFY_EQUAL( + "encryption secret", epoch.encryption_secret, tve.encryption_secret); + VERIFY_EQUAL("exporter secret", epoch.exporter_secret, tve.exporter_secret); + VERIFY_EQUAL("epoch authenticator", + epoch.epoch_authenticator, + tve.epoch_authenticator); + VERIFY_EQUAL("external secret", epoch.external_secret, tve.external_secret); + VERIFY_EQUAL( + "confirmation key", epoch.confirmation_key, tve.confirmation_key); + VERIFY_EQUAL("membership key", epoch.membership_key, tve.membership_key); + VERIFY_EQUAL("resumption psk", epoch.resumption_psk, tve.resumption_psk); + VERIFY_EQUAL("init secret", epoch.init_secret, tve.init_secret); + + VERIFY_EQUAL( + "external pub", epoch.external_priv.public_key, tve.external_pub); + + auto exported = epoch.do_export( + tve.exporter.label, tve.exporter.context, tve.exporter.length); + VERIFY_EQUAL("exported", exported, tve.exporter.secret); + + group_context.epoch += 1; + } + + return std::nullopt; +} + +/// +/// MessageProtectionTestVector +/// + +MessageProtectionTestVector::MessageProtectionTestVector(CipherSuite suite) + : PseudoRandom(suite, "message-protection") + , cipher_suite(suite) + , group_id(prg.secret("group_id")) + , epoch(prg.uint64("epoch")) + , tree_hash(prg.secret("tree_hash")) + , confirmed_transcript_hash(prg.secret("confirmed_transcript_hash")) + , signature_priv(prg.signature_key("signature_priv")) + , signature_pub(signature_priv.public_key) + , encryption_secret(prg.secret("encryption_secret")) + , sender_data_secret(prg.secret("sender_data_secret")) + , membership_key(prg.secret("membership_key")) + , proposal{ GroupContextExtensions{} } + , commit{ /* XXX(RLB) this is technically invalid, empty w/o path */ } + , application{ prg.secret("application") } +{ + proposal_pub = protect_pub(proposal); + proposal_priv = protect_priv(proposal); + + commit_pub = protect_pub(commit); + commit_priv = protect_priv(commit); + + application_priv = protect_priv(ApplicationData{ application }); +} + +std::optional +MessageProtectionTestVector::verify() +{ + // Initialize fields that don't get set from JSON + prg = PseudoRandom::Generator(cipher_suite, "message-protection"); + signature_priv.set_public_key(cipher_suite); + + // Sanity check the key pairs + VERIFY_EQUAL("sig kp", signature_priv.public_key, signature_pub); + + // Verify proposal unprotect as PublicMessage + auto proposal_pub_unprotected = unprotect(proposal_pub); + VERIFY("proposal pub unprotect auth", proposal_pub_unprotected); + VERIFY_EQUAL("proposal pub unprotect", + opt::get(proposal_pub_unprotected).content, + proposal); + + // Verify proposal unprotect as PrivateMessage + auto proposal_priv_unprotected = unprotect(proposal_priv); + VERIFY("proposal priv unprotect auth", proposal_priv_unprotected); + VERIFY_EQUAL("proposal priv unprotect", + opt::get(proposal_priv_unprotected).content, + proposal); + + // Verify commit unprotect as PublicMessage + auto commit_pub_unprotected = unprotect(commit_pub); + VERIFY("commit pub unprotect auth", commit_pub_unprotected); + VERIFY_EQUAL( + "commit pub unprotect", opt::get(commit_pub_unprotected).content, commit); + + // Verify commit unprotect as PrivateMessage + auto commit_priv_unprotected = unprotect(commit_priv); + VERIFY("commit priv unprotect auth", commit_priv_unprotected); + VERIFY_EQUAL( + "commit priv unprotect", opt::get(commit_priv_unprotected).content, commit); + + // Verify application data unprotect as PrivateMessage + auto app_unprotected = unprotect(application_priv); + VERIFY("app priv unprotect auth", app_unprotected); + VERIFY_EQUAL("app priv unprotect", + opt::get(app_unprotected).content, + ApplicationData{ application }); + + // Verify protect/unprotect round-trips + // XXX(RLB): Note that because (a) unprotect() deletes keys from the ratchet + // and (b) we are using the same ratchet to send and receive, we need to do + // these round-trip tests after all the unprotect tests are done. Otherwise + // the protect() calls here will re-use generations used the test vector, and + // then unprotect() will delete the keys, then when you go to decrypt the test + // vector object, you'll get "expired key". It might be good to have better + // safeguards around such reuse. + auto proposal_pub_protected = protect_pub(proposal); + auto proposal_pub_protected_unprotected = unprotect(proposal_pub_protected); + VERIFY("proposal pub protect/unprotect auth", + proposal_pub_protected_unprotected); + VERIFY_EQUAL("proposal pub protect/unprotect", + opt::get(proposal_pub_protected_unprotected).content, + proposal); + + auto proposal_priv_protected = protect_priv(proposal); + auto proposal_priv_protected_unprotected = unprotect(proposal_priv_protected); + VERIFY("proposal priv protect/unprotect auth", + proposal_priv_protected_unprotected); + VERIFY_EQUAL("proposal priv protect/unprotect", + opt::get(proposal_priv_protected_unprotected).content, + proposal); + + auto commit_pub_protected = protect_pub(commit); + auto commit_pub_protected_unprotected = unprotect(commit_pub_protected); + VERIFY("commit pub protect/unprotect auth", commit_pub_protected_unprotected); + VERIFY_EQUAL("commit pub protect/unprotect", + opt::get(commit_pub_protected_unprotected).content, + commit); + + auto commit_priv_protected = protect_priv(commit); + auto commit_priv_protected_unprotected = unprotect(commit_priv_protected); + VERIFY("commit priv protect/unprotect auth", + commit_priv_protected_unprotected); + VERIFY_EQUAL("commit priv protect/unprotect", + opt::get(commit_priv_protected_unprotected).content, + commit); + + auto app_protected = protect_priv(ApplicationData{ application }); + auto app_protected_unprotected = unprotect(app_protected); + VERIFY("app priv protect/unprotect auth", app_protected_unprotected); + VERIFY_EQUAL("app priv protect/unprotect", + opt::get(app_protected_unprotected).content, + ApplicationData{ application }); + + return std::nullopt; +} + +GroupKeySource +MessageProtectionTestVector::group_keys() const +{ + return { cipher_suite, LeafCount{ 2 }, encryption_secret }; +} + +GroupContext +MessageProtectionTestVector::group_context() const +{ + return GroupContext{ + cipher_suite, group_id, epoch, tree_hash, confirmed_transcript_hash, {} + }; +} + +MLSMessage +MessageProtectionTestVector::protect_pub( + const mlspp::GroupContent::RawContent& raw_content) const +{ + auto sender = Sender{ MemberSender{ LeafIndex{ 1 } } }; + auto authenticated_data = bytes{}; + + auto content = + GroupContent{ group_id, epoch, sender, authenticated_data, raw_content }; + + auto auth_content = AuthenticatedContent::sign(WireFormat::mls_public_message, + content, + cipher_suite, + signature_priv, + group_context()); + if (content.content_type() == ContentType::commit) { + auto confirmation_tag = prg.secret("confirmation_tag"); + auth_content.set_confirmation_tag(confirmation_tag); + } + + return PublicMessage::protect( + auth_content, cipher_suite, membership_key, group_context()); +} + +MLSMessage +MessageProtectionTestVector::protect_priv( + const mlspp::GroupContent::RawContent& raw_content) +{ + auto sender = Sender{ MemberSender{ LeafIndex{ 1 } } }; + auto authenticated_data = bytes{}; + auto padding_size = size_t(0); + + auto content = + GroupContent{ group_id, epoch, sender, authenticated_data, raw_content }; + + auto auth_content = + AuthenticatedContent::sign(WireFormat::mls_private_message, + content, + cipher_suite, + signature_priv, + group_context()); + if (content.content_type() == ContentType::commit) { + auto confirmation_tag = prg.secret("confirmation_tag"); + auth_content.set_confirmation_tag(confirmation_tag); + } + + auto keys = group_keys(); + return PrivateMessage::protect( + auth_content, cipher_suite, keys, sender_data_secret, padding_size); +} + +std::optional +MessageProtectionTestVector::unprotect(const MLSMessage& message) +{ + auto do_unprotect = + overloaded{ [&](const PublicMessage& pt) { + return pt.unprotect( + cipher_suite, membership_key, group_context()); + }, + [&](const PrivateMessage& ct) { + auto keys = group_keys(); + return ct.unprotect(cipher_suite, keys, sender_data_secret); + }, + [](const auto& /* other */) -> std::optional { + return std::nullopt; + } }; + + auto maybe_auth_content = var::visit(do_unprotect, message.message); + if (!maybe_auth_content) { + return std::nullopt; + } + + auto val_content = opt::get(maybe_auth_content); + const auto& auth_content = val_content.authenticated_content(); + if (!auth_content.verify(cipher_suite, signature_pub, group_context())) { + return std::nullopt; + } + + return auth_content.content; +} + +/// +/// PSKTestVector +/// +static std::vector +to_psk_w_secret(const std::vector& psks) +{ + auto pskws = std::vector(psks.size()); + std::transform( + std::begin(psks), std::end(psks), std::begin(pskws), [](const auto& psk) { + auto ext_id = ExternalPSK{ psk.psk_id }; + auto id = PreSharedKeyID{ ext_id, psk.psk_nonce }; + return PSKWithSecret{ id, psk.psk }; + }); + + return pskws; +} + +PSKSecretTestVector::PSKSecretTestVector(mlspp::CipherSuite suite, + size_t n_psks) + : PseudoRandom(suite, "psk_secret") + , cipher_suite(suite) + , psks(n_psks) +{ + uint32_t i = 0; + for (auto& psk : psks) { + auto ix = to_hex(tls::marshal(i)); + i += 1; + + psk.psk_id = prg.secret("psk_id" + ix); + psk.psk_nonce = prg.secret("psk_nonce" + ix); + psk.psk = prg.secret("psk" + ix); + } + + psk_secret = + KeyScheduleEpoch::make_psk_secret(cipher_suite, to_psk_w_secret(psks)); +} + +std::optional +PSKSecretTestVector::verify() const +{ + auto actual = + KeyScheduleEpoch::make_psk_secret(cipher_suite, to_psk_w_secret(psks)); + VERIFY_EQUAL("psk secret", actual, psk_secret); + + return std::nullopt; +} + +/// +/// TranscriptTestVector +/// +TranscriptTestVector::TranscriptTestVector(CipherSuite suite) + : PseudoRandom(suite, "transcript") + , cipher_suite(suite) + , interim_transcript_hash_before(prg.secret("interim_transcript_hash_before")) +{ + auto transcript = TranscriptHash(suite); + transcript.interim = interim_transcript_hash_before; + + auto group_id = prg.secret("group_id"); + auto epoch = prg.uint64("epoch"); + auto group_context_obj = + GroupContext{ suite, + group_id, + epoch, + prg.secret("tree_hash_before"), + prg.secret("confirmed_transcript_hash_before"), + {} }; + auto group_context = tls::marshal(group_context_obj); + + auto init_secret = prg.secret("init_secret"); + auto ks_epoch = KeyScheduleEpoch(suite, init_secret, group_context); + + auto sig_priv = prg.signature_key("sig_priv"); + auto leaf_index = LeafIndex{ 0 }; + + authenticated_content = AuthenticatedContent::sign( + WireFormat::mls_public_message, + GroupContent{ + group_id, epoch, { MemberSender{ leaf_index } }, {}, Commit{} }, + suite, + sig_priv, + group_context_obj); + + transcript.update_confirmed(authenticated_content); + + const auto confirmation_tag = ks_epoch.confirmation_tag(transcript.confirmed); + authenticated_content.set_confirmation_tag(confirmation_tag); + + transcript.update_interim(authenticated_content); + + // Store the required data + confirmation_key = ks_epoch.confirmation_key; + confirmed_transcript_hash_after = transcript.confirmed; + interim_transcript_hash_after = transcript.interim; +} + +std::optional +TranscriptTestVector::verify() const +{ + auto transcript = TranscriptHash(cipher_suite); + transcript.interim = interim_transcript_hash_before; + + transcript.update(authenticated_content); + VERIFY_EQUAL( + "confirmed", transcript.confirmed, confirmed_transcript_hash_after); + VERIFY_EQUAL("interim", transcript.interim, interim_transcript_hash_after); + + auto confirmation_tag = + cipher_suite.digest().hmac(confirmation_key, transcript.confirmed); + VERIFY_EQUAL("confirmation tag", + confirmation_tag, + authenticated_content.auth.confirmation_tag); + + return std::nullopt; +} + +/// +/// WelcomeTestVector +/// +WelcomeTestVector::WelcomeTestVector(CipherSuite suite) + : PseudoRandom(suite, "welcome") + , cipher_suite(suite) + , init_priv(prg.hpke_key("init_priv")) +{ + auto joiner_secret = prg.secret("joiner_secret"); + auto group_id = prg.secret("group_id"); + auto epoch = epoch_t(prg.uint64("epoch")); + auto tree_hash = prg.secret("tree_hash"); + auto confirmed_transcript_hash = prg.secret("confirmed_transcript_hash"); + auto enc_priv = prg.hpke_key("enc_priv"); + auto sig_priv = prg.signature_key("sig_priv"); + auto cred = Credential::basic(prg.secret("identity")); + + auto signer_index = LeafIndex{ prg.uint32("signer") }; + auto signer_priv = prg.signature_key("signer_priv"); + signer_pub = signer_priv.public_key; + + auto leaf_node = LeafNode{ + cipher_suite, + enc_priv.public_key, + sig_priv.public_key, + cred, + Capabilities::create_default(), + Lifetime::create_default(), + {}, + sig_priv, + }; + auto key_package_obj = KeyPackage{ + cipher_suite, init_priv.public_key, leaf_node, {}, sig_priv, + }; + key_package = key_package_obj; + + auto group_context = GroupContext{ + cipher_suite, group_id, epoch, tree_hash, confirmed_transcript_hash, {} + }; + + auto key_schedule = KeyScheduleEpoch::joiner( + cipher_suite, joiner_secret, {}, tls::marshal(group_context)); + auto confirmation_tag = + key_schedule.confirmation_tag(confirmed_transcript_hash); + + auto group_info = GroupInfo{ + group_context, + {}, + confirmation_tag, + }; + group_info.sign(signer_index, signer_priv); + + auto welcome_obj = Welcome(cipher_suite, joiner_secret, {}, group_info); + welcome_obj.encrypt(key_package_obj, std::nullopt); + welcome = welcome_obj; +} + +std::optional +WelcomeTestVector::verify() const +{ + VERIFY_EQUAL( + "kp format", key_package.wire_format(), WireFormat::mls_key_package); + VERIFY_EQUAL( + "welcome format", welcome.wire_format(), WireFormat::mls_welcome); + + const auto& key_package_obj = var::get(key_package.message); + const auto& welcome_obj = var::get(welcome.message); + + VERIFY_EQUAL("kp suite", key_package_obj.cipher_suite, cipher_suite); + VERIFY_EQUAL("welcome suite", welcome_obj.cipher_suite, cipher_suite); + + auto maybe_kpi = welcome_obj.find(key_package_obj); + VERIFY("found key package", maybe_kpi); + + auto kpi = opt::get(maybe_kpi); + auto group_secrets = welcome_obj.decrypt_secrets(kpi, init_priv); + auto group_info = welcome_obj.decrypt(group_secrets.joiner_secret, {}); + + // Verify signature on GroupInfo + VERIFY("group info verify", group_info.verify(signer_pub)); + + // Verify confirmation tag + const auto& group_context = group_info.group_context; + auto key_schedule = KeyScheduleEpoch::joiner( + cipher_suite, group_secrets.joiner_secret, {}, tls::marshal(group_context)); + auto confirmation_tag = + key_schedule.confirmation_tag(group_context.confirmed_transcript_hash); + + return std::nullopt; +} + +/// +/// TreeTestCase +/// + +std::array all_tree_structures{ + TreeStructure::full_tree_2, + TreeStructure::full_tree_3, + TreeStructure::full_tree_4, + TreeStructure::full_tree_5, + TreeStructure::full_tree_6, + TreeStructure::full_tree_7, + TreeStructure::full_tree_8, + TreeStructure::full_tree_32, + TreeStructure::full_tree_33, + TreeStructure::full_tree_34, + TreeStructure::internal_blanks_no_skipping, + TreeStructure::internal_blanks_with_skipping, + TreeStructure::unmerged_leaves_no_skipping, + TreeStructure::unmerged_leaves_with_skipping, +}; + +std::array treekem_test_tree_structures{ + // All cases except the big ones + TreeStructure::full_tree_2, + TreeStructure::full_tree_3, + TreeStructure::full_tree_4, + TreeStructure::full_tree_5, + TreeStructure::full_tree_6, + TreeStructure::full_tree_7, + TreeStructure::full_tree_8, + TreeStructure::internal_blanks_no_skipping, + TreeStructure::internal_blanks_with_skipping, + TreeStructure::unmerged_leaves_no_skipping, + TreeStructure::unmerged_leaves_with_skipping, +}; + +struct TreeTestCase +{ + CipherSuite suite; + PseudoRandom::Generator prg; + + bytes group_id; + uint32_t leaf_counter = 0; + uint32_t path_counter = 0; + + struct PrivateState + { + SignaturePrivateKey sig_priv; + TreeKEMPrivateKey priv; + std::vector senders; + }; + + std::map privs; + TreeKEMPublicKey pub; + + TreeTestCase(CipherSuite suite_in, PseudoRandom::Generator prg_in) + : suite(suite_in) + , prg(std::move(prg_in)) + , group_id(prg.secret("group_id")) + , pub(suite) + { + auto [where, enc_priv, sig_priv] = add_leaf(); + auto tree_priv = TreeKEMPrivateKey::solo(suite, where, enc_priv); + auto priv_state = PrivateState{ sig_priv, tree_priv, { LeafIndex{ 0 } } }; + privs.insert_or_assign(where, priv_state); + } + + std::tuple add_leaf() + { + leaf_counter += 1; + auto ix = to_hex(tls::marshal(leaf_counter)); + auto enc_priv = prg.hpke_key("encryption_key" + ix); + auto sig_priv = prg.signature_key("signature_key" + ix); + auto identity = prg.secret("identity" + ix); + + auto credential = Credential::basic(identity); + auto leaf_node = LeafNode{ suite, + enc_priv.public_key, + sig_priv.public_key, + credential, + Capabilities::create_default(), + Lifetime::create_default(), + {}, + sig_priv }; + auto where = pub.add_leaf(leaf_node); + pub.set_hash_all(); + return { where, enc_priv, sig_priv }; + } + + void commit(LeafIndex from, + const std::vector& remove, + bool add, + std::optional maybe_context) + { + // Remove members from the tree + for (auto i : remove) { + pub.blank_path(i); + privs.erase(i); + } + pub.set_hash_all(); + + auto joiner = std::vector{}; + auto maybe_enc_priv = std::optional{}; + auto maybe_sig_priv = std::optional{}; + if (add) { + auto [where, enc_priv, sig_priv] = add_leaf(); + joiner.push_back(where); + maybe_enc_priv = enc_priv; + maybe_sig_priv = sig_priv; + } + + auto path_secret = std::optional{}; + if (maybe_context) { + // Create an UpdatePath + path_counter += 1; + auto ix = to_hex(tls::marshal(path_counter)); + auto leaf_secret = prg.secret("leaf_secret" + ix); + auto priv = privs.at(from); + + auto context = opt::get(maybe_context); + auto pub_before = pub; + auto sender_priv = + pub.update(from, leaf_secret, group_id, priv.sig_priv, {}); + auto path = pub.encap(sender_priv, context, joiner); + + // Process the UpdatePath at all the members + for (auto& pair : privs) { + // XXX(RLB): It might seem like this could be done with a simple + // destructuring assignment, either here or in the `for` clause above. + // However, either of these options cause clang-tidy to segfault when + // evaulating the "bugprone-unchecked-optional-access" lint. + const auto& leaf = pair.first; + auto& priv_state = pair.second; + if (leaf == from) { + priv_state = + PrivateState{ priv_state.sig_priv, sender_priv, { from } }; + continue; + } + + priv_state.priv.decap(from, pub_before, context, path, joiner); + priv_state.senders.push_back(from); + } + + // Look up the path secret for the joiner + if (!joiner.empty()) { + auto index = joiner.front(); + auto [overlap, shared_path_secret, ok] = + sender_priv.shared_path_secret(index); + silence_unused(overlap); + silence_unused(ok); + + path_secret = shared_path_secret; + } + } + + // Add a private entry for the joiner if we added someone + if (!joiner.empty()) { + auto index = joiner.front(); + auto ancestor = index.ancestor(from); + auto enc_priv = opt::get(maybe_enc_priv); + auto sig_priv = opt::get(maybe_sig_priv); + auto tree_priv = + TreeKEMPrivateKey::joiner(pub, index, enc_priv, ancestor, path_secret); + privs.insert_or_assign(index, + PrivateState{ sig_priv, tree_priv, { from } }); + } + } + + static TreeTestCase full(CipherSuite suite, + const PseudoRandom::Generator& prg, + LeafCount leaves, + const std::string& label) + { + auto tc = TreeTestCase{ suite, prg.sub(label) }; + + for (LeafIndex i{ 0 }; i.val < leaves.val - 1; i.val++) { + tc.commit( + i, {}, true, tc.prg.secret("context" + to_hex(tls::marshal(i)))); + } + + return tc; + } + + static TreeTestCase with_structure(CipherSuite suite, + const PseudoRandom::Generator& prg, + TreeStructure tree_structure) + { + switch (tree_structure) { + case TreeStructure::full_tree_2: + return full(suite, prg, LeafCount{ 2 }, "full_tree_2"); + + case TreeStructure::full_tree_3: + return full(suite, prg, LeafCount{ 3 }, "full_tree_3"); + + case TreeStructure::full_tree_4: + return full(suite, prg, LeafCount{ 4 }, "full_tree_4"); + + case TreeStructure::full_tree_5: + return full(suite, prg, LeafCount{ 5 }, "full_tree_5"); + + case TreeStructure::full_tree_6: + return full(suite, prg, LeafCount{ 6 }, "full_tree_6"); + + case TreeStructure::full_tree_7: + return full(suite, prg, LeafCount{ 7 }, "full_tree_7"); + + case TreeStructure::full_tree_8: + return full(suite, prg, LeafCount{ 8 }, "full_tree_8"); + + case TreeStructure::full_tree_32: + return full(suite, prg, LeafCount{ 32 }, "full_tree_32"); + + case TreeStructure::full_tree_33: + return full(suite, prg, LeafCount{ 33 }, "full_tree_33"); + + case TreeStructure::full_tree_34: + return full(suite, prg, LeafCount{ 34 }, "full_tree_34"); + + case TreeStructure::internal_blanks_no_skipping: { + auto tc = TreeTestCase::full( + suite, prg, LeafCount{ 8 }, "internal_blanks_no_skipping"); + auto context = tc.prg.secret("context"); + tc.commit( + LeafIndex{ 0 }, { LeafIndex{ 2 }, LeafIndex{ 3 } }, true, context); + return tc; + } + + case TreeStructure::internal_blanks_with_skipping: { + auto tc = TreeTestCase::full( + suite, prg, LeafCount{ 8 }, "internal_blanks_with_skipping"); + auto context = tc.prg.secret("context"); + tc.commit(LeafIndex{ 0 }, + { LeafIndex{ 1 }, LeafIndex{ 2 }, LeafIndex{ 3 } }, + false, + context); + return tc; + } + + case TreeStructure::unmerged_leaves_no_skipping: { + auto tc = TreeTestCase::full( + suite, prg, LeafCount{ 7 }, "unmerged_leaves_no_skipping"); + auto context = tc.prg.secret("context"); + tc.commit(LeafIndex{ 0 }, {}, true, std::nullopt); + return tc; + } + + case TreeStructure::unmerged_leaves_with_skipping: { + auto tc = TreeTestCase::full( + suite, prg, LeafCount{ 1 }, "unmerged_leaves_with_skipping"); + + // 0 adds 1..6 + tc.commit(LeafIndex{ 0 }, {}, true, std::nullopt); + tc.commit(LeafIndex{ 0 }, {}, true, std::nullopt); + tc.commit(LeafIndex{ 0 }, {}, true, std::nullopt); + tc.commit(LeafIndex{ 0 }, {}, true, std::nullopt); + tc.commit(LeafIndex{ 0 }, {}, true, std::nullopt); + tc.commit(LeafIndex{ 0 }, {}, true, std::nullopt); + + // 0 reemoves 5 + tc.commit(LeafIndex{ 0 }, + { LeafIndex{ 5 } }, + false, + tc.prg.secret("context_remove5")); + + // 4 commits without any proupposals + tc.commit(LeafIndex{ 4 }, {}, false, tc.prg.secret("context_update4")); + + // 0 adds a new member + tc.commit(LeafIndex{ 0 }, {}, true, std::nullopt); + + return tc; + } + + default: + throw InvalidParameterError("Unsupported tree structure"); + } + } +}; + +/// +/// TreeHashTestVector +/// +TreeHashTestVector::TreeHashTestVector(mlspp::CipherSuite suite, + TreeStructure tree_structure) + : PseudoRandom(suite, "tree-hashes") + , cipher_suite(suite) +{ + auto tc = TreeTestCase::with_structure(suite, prg, tree_structure); + tree = tc.pub; + group_id = tc.group_id; + + auto width = NodeCount(tree.size); + for (NodeIndex i{ 0 }; i < width; i.val++) { + tree_hashes.push_back(tree.get_hash(i)); + resolutions.push_back(tree.resolve(i)); + } +} + +std::optional +TreeHashTestVector::verify() +{ + // Finish setting up the tree + tree.suite = cipher_suite; + tree.set_hash_all(); + + // Verify that each leaf node is properly signed + for (LeafIndex i{ 0 }; i < tree.size; i.val++) { + auto maybe_leaf = tree.leaf_node(i); + if (!maybe_leaf) { + continue; + } + + auto leaf = opt::get(maybe_leaf); + auto leaf_valid = leaf.verify(cipher_suite, { { group_id, i } }); + VERIFY("leaf sig valid", leaf_valid); + } + + // Verify the tree hashes + auto width = NodeCount{ tree.size }; + for (NodeIndex i{ 0 }; i < width; i.val++) { + VERIFY_EQUAL("tree hash", tree.get_hash(i), tree_hashes.at(i.val)); + VERIFY_EQUAL("resolution", tree.resolve(i), resolutions.at(i.val)); + } + + // Verify parent hashes + VERIFY("parent hash valid", tree.parent_hash_valid()); + + // Verify the resolutions + for (NodeIndex i{ 0 }; i < width; i.val++) { + VERIFY_EQUAL("resolution", tree.resolve(i), resolutions[i.val]); + } + + return std::nullopt; +} + +/// +/// TreeOperationsTestVector +/// + +const std::vector + TreeOperationsTestVector::all_scenarios{ + Scenario::add_right_edge, Scenario::add_internal, Scenario::update, + Scenario::remove_right_edge, Scenario::remove_internal, + }; + +TreeOperationsTestVector::TreeOperationsTestVector( + mlspp::CipherSuite suite, + Scenario scenario) + : PseudoRandom(suite, "tree-operations") + , cipher_suite(suite) + , proposal_sender(0) +{ + auto init_priv = prg.hpke_key("init_key"); + auto enc_priv = prg.hpke_key("encryption_key"); + auto sig_priv = prg.signature_key("signature_key"); + auto identity = prg.secret("identity"); + auto credential = Credential::basic(identity); + auto key_package = KeyPackage{ + suite, + init_priv.public_key, + { suite, + enc_priv.public_key, + sig_priv.public_key, + credential, + Capabilities::create_default(), + Lifetime::create_default(), + {}, + sig_priv }, + {}, + sig_priv, + }; + + switch (scenario) { + case Scenario::add_right_edge: { + auto tc = TreeTestCase::full(suite, prg, LeafCount{ 8 }, "tc"); + + proposal = Proposal{ Add{ key_package } }; + + tree_before = tc.pub; + tree_hash_before = tree_before.root_hash(); + + tree_after = tree_before; + tree_after.add_leaf(key_package.leaf_node); + break; + } + + case Scenario::add_internal: { + auto tc = TreeTestCase::full(suite, prg, LeafCount{ 8 }, "tc"); + + proposal = Proposal{ Add{ key_package } }; + + tree_before = tc.pub; + tree_before.blank_path(LeafIndex{ 4 }); + tree_before.set_hash_all(); + tree_hash_before = tree_before.root_hash(); + + tree_after = tree_before; + tree_after.add_leaf(key_package.leaf_node); + break; + } + + case Scenario::update: { + auto tc = TreeTestCase::full(suite, prg, LeafCount{ 8 }, "tc"); + + proposal_sender = LeafIndex{ 3 }; + proposal = Proposal{ Update{ key_package.leaf_node } }; + + tree_before = tc.pub; + tree_hash_before = tree_before.root_hash(); + + tree_after = tree_before; + tree_after.update_leaf(proposal_sender, key_package.leaf_node); + break; + } + + case Scenario::remove_right_edge: { + auto tc = TreeTestCase::full(suite, prg, LeafCount{ 9 }, "tc"); + + auto removed = LeafIndex{ 8 }; + proposal = Proposal{ Remove{ removed } }; + + tree_before = tc.pub; + tree_hash_before = tree_before.root_hash(); + + tree_after = tree_before; + tree_after.blank_path(removed); + tree_after.truncate(); + break; + } + + case Scenario::remove_internal: { + auto tc = TreeTestCase::full(suite, prg, LeafCount{ 8 }, "tc"); + + auto removed = LeafIndex{ 4 }; + proposal = Proposal{ Remove{ removed } }; + + tree_before = tc.pub; + tree_hash_before = tree_before.root_hash(); + + tree_after = tree_before; + tree_after.blank_path(removed); + tree_after.truncate(); + break; + } + } + + tree_after.set_hash_all(); + tree_hash_after = tree_after.root_hash(); +} + +std::optional +TreeOperationsTestVector::verify() +{ + tree_before.suite = cipher_suite; + tree_before.set_hash_all(); + + auto tree = tree_before; + VERIFY_EQUAL("tree hash before", tree.root_hash(), tree_hash_before); + + auto apply = overloaded{ + [&](const Add& add) { tree.add_leaf(add.key_package.leaf_node); }, + + [&](const Update& update) { + tree.update_leaf(proposal_sender, update.leaf_node); + }, + + [&](const Remove& remove) { + tree.blank_path(remove.removed); + tree.truncate(); + }, + + [](const auto& /* other */) { + throw InvalidParameterError("invalid proposal type"); + }, + }; + + var::visit(apply, proposal.content); + VERIFY_EQUAL("tree after", tree, tree_after); + + tree.set_hash_all(); + VERIFY_EQUAL("tree hash after", tree.root_hash(), tree_hash_after); + + return std::nullopt; +} + +/// +/// TreeKEMTestVector +/// + +TreeKEMTestVector::TreeKEMTestVector(mlspp::CipherSuite suite, + TreeStructure tree_structure) + : PseudoRandom(suite, "treekem") + , cipher_suite(suite) +{ + auto tc = TreeTestCase::with_structure(cipher_suite, prg, tree_structure); + + group_id = tc.group_id; + epoch = prg.uint64("epoch"); + confirmed_transcript_hash = prg.secret("confirmed_transcript_hash"); + + ratchet_tree = tc.pub; + + // Serialize out the private states + for (LeafIndex index{ 0 }; index < ratchet_tree.size; index.val++) { + if (tc.privs.count(index) == 0) { + continue; + } + + auto priv_state = tc.privs.at(index); + auto enc_priv = priv_state.priv.private_key_cache.at(NodeIndex(index)); + auto path_secrets = std::vector{}; + for (const auto& [node, path_secret] : priv_state.priv.path_secrets) { + if (node == NodeIndex(index)) { + // No need to serialize a secret for the leaf node + continue; + } + + path_secrets.push_back(PathSecret{ node, path_secret }); + } + + leaves_private.push_back(LeafPrivateInfo{ + index, + enc_priv, + priv_state.sig_priv, + path_secrets, + }); + } + + // Create test update paths + for (LeafIndex sender{ 0 }; sender < ratchet_tree.size; sender.val++) { + if (!tc.pub.has_leaf(sender)) { + continue; + } + + auto leaf_secret = prg.secret("update_path" + to_hex(tls::marshal(sender))); + const auto& sig_priv = tc.privs.at(sender).sig_priv; + + auto pub = tc.pub; + auto new_sender_priv = + pub.update(sender, leaf_secret, group_id, sig_priv, {}); + + auto group_context = GroupContext{ cipher_suite, + group_id, + epoch, + pub.root_hash(), + confirmed_transcript_hash, + {} }; + auto ctx = tls::marshal(group_context); + + auto path = pub.encap(new_sender_priv, ctx, {}); + + auto path_secrets = std::vector>{}; + for (LeafIndex to{ 0 }; to < ratchet_tree.size; to.val++) { + if (to == sender || !pub.has_leaf(to)) { + path_secrets.emplace_back(std::nullopt); + continue; + } + + auto [overlap, path_secret, ok] = new_sender_priv.shared_path_secret(to); + silence_unused(overlap); + silence_unused(ok); + + path_secrets.emplace_back(path_secret); + } + + update_paths.push_back(UpdatePathInfo{ + sender, + path, + path_secrets, + new_sender_priv.update_secret, + pub.root_hash(), + }); + } +} + +std::optional +TreeKEMTestVector::verify() +{ + // Finish initializing the ratchet tree + ratchet_tree.suite = cipher_suite; + ratchet_tree.set_hash_all(); + + // Validate public state + VERIFY("parent hash valid", ratchet_tree.parent_hash_valid()); + + for (LeafIndex i{ 0 }; i < ratchet_tree.size; i.val++) { + auto maybe_leaf = ratchet_tree.leaf_node(i); + if (!maybe_leaf) { + continue; + } + + auto leaf = opt::get(maybe_leaf); + VERIFY("leaf sig", leaf.verify(cipher_suite, { { group_id, i } })); + } + + // Import private keys + std::map tree_privs; + std::map sig_privs; + for (const auto& info : leaves_private) { + auto enc_priv = info.encryption_priv; + auto sig_priv = info.signature_priv; + enc_priv.set_public_key(cipher_suite); + sig_priv.set_public_key(cipher_suite); + + auto priv = TreeKEMPrivateKey{}; + priv.suite = cipher_suite; + priv.index = info.index; + priv.private_key_cache.insert_or_assign(NodeIndex(info.index), enc_priv); + + for (const auto& entry : info.path_secrets) { + priv.path_secrets.insert_or_assign(entry.node, entry.path_secret); + } + + VERIFY("priv consistent", priv.consistent(ratchet_tree)); + + tree_privs.insert_or_assign(info.index, priv); + sig_privs.insert_or_assign(info.index, sig_priv); + } + + for (const auto& info : update_paths) { + // Test decap of the existing group secrets + const auto& from = info.sender; + const auto& path = info.update_path; + VERIFY("path parent hash valid", + ratchet_tree.parent_hash_valid(from, path)); + + auto ratchet_tree_after = ratchet_tree; + ratchet_tree_after.merge(from, path); + ratchet_tree_after.set_hash_all(); + VERIFY_EQUAL( + "tree hash after", ratchet_tree_after.root_hash(), info.tree_hash_after); + + auto group_context = GroupContext{ cipher_suite, + group_id, + epoch, + ratchet_tree_after.root_hash(), + confirmed_transcript_hash, + {} }; + auto ctx = tls::marshal(group_context); + + for (LeafIndex to{ 0 }; to < ratchet_tree_after.size; to.val++) { + if (to == from || !ratchet_tree_after.has_leaf(to)) { + continue; + } + + auto priv = tree_privs.at(to); + priv.decap(from, ratchet_tree_after, ctx, path, {}); + VERIFY_EQUAL("commit secret", priv.update_secret, info.commit_secret); + + auto [overlap, path_secret, ok] = priv.shared_path_secret(from); + silence_unused(overlap); + silence_unused(ok); + VERIFY_EQUAL("path secret", path_secret, info.path_secrets[to.val]); + } + + // Test encap/decap + auto ratchet_tree_encap = ratchet_tree; + auto leaf_secret = random_bytes(cipher_suite.secret_size()); + const auto& sig_priv = sig_privs.at(from); + auto new_sender_priv = + ratchet_tree_encap.update(from, leaf_secret, group_id, sig_priv, {}); + auto new_path = ratchet_tree_encap.encap(new_sender_priv, ctx, {}); + VERIFY("new path parent hash valid", + ratchet_tree.parent_hash_valid(from, path)); + + for (LeafIndex to{ 0 }; to < ratchet_tree_encap.size; to.val++) { + if (to == from || !ratchet_tree_encap.has_leaf(to)) { + continue; + } + + auto priv = tree_privs.at(to); + priv.decap(from, ratchet_tree_encap, ctx, new_path, {}); + VERIFY_EQUAL( + "commit secret", priv.update_secret, new_sender_priv.update_secret); + } + } + + return std::nullopt; +} + +/// +/// MessagesTestVector +/// + +MessagesTestVector::MessagesTestVector() + : PseudoRandom(CipherSuite::ID::X25519_AES128GCM_SHA256_Ed25519, "messages") +{ + auto suite = CipherSuite{ CipherSuite::ID::X25519_AES128GCM_SHA256_Ed25519 }; + auto epoch = epoch_t(prg.uint64("epoch")); + auto index = LeafIndex{ prg.uint32("index") }; + auto user_id = prg.secret("user_id"); + auto group_id = prg.secret("group_id"); + // auto opaque = bytes(32, 0xD3); + // auto mac = bytes(32, 0xD5); + + auto app_id_ext = ApplicationIDExtension{ prg.secret("app_id") }; + auto ext_list = ExtensionList{}; + ext_list.add(app_id_ext); + + auto group_context = GroupContext{ suite, + group_id, + epoch, + prg.secret("tree_hash"), + prg.secret("confirmed_trasncript_hash"), + ext_list }; + + auto version = ProtocolVersion::mls10; + auto hpke_priv = prg.hpke_key("hpke_priv"); + auto hpke_priv_2 = prg.hpke_key("hpke_priv_2"); + auto hpke_pub = hpke_priv.public_key; + auto hpke_pub_2 = hpke_priv_2.public_key; + auto hpke_ct = + HPKECiphertext{ prg.secret("kem_output"), prg.secret("ciphertext") }; + auto sig_priv = prg.signature_key("signature_priv"); + auto sig_priv_2 = prg.signature_key("signature_priv_2"); + auto sig_pub = sig_priv.public_key; + auto sig_pub_2 = sig_priv_2.public_key; + + // KeyPackage and extensions + auto cred = Credential::basic(user_id); + auto leaf_node = LeafNode{ suite, + hpke_pub, + sig_pub, + cred, + Capabilities::create_default(), + Lifetime::create_default(), + ext_list, + sig_priv }; + auto leaf_node_2 = LeafNode{ suite, + hpke_pub_2, + sig_pub_2, + cred, + Capabilities::create_default(), + Lifetime::create_default(), + ext_list, + sig_priv_2 }; + auto key_package_obj = KeyPackage{ suite, hpke_pub, leaf_node, {}, sig_priv }; + + auto leaf_node_update = + leaf_node.for_update(suite, group_id, index, hpke_pub, {}, sig_priv); + auto leaf_node_commit = leaf_node.for_commit( + suite, group_id, index, hpke_pub, prg.secret("parent_hash"), {}, sig_priv); + + auto sender = Sender{ MemberSender{ index } }; + + auto tree = TreeKEMPublicKey{ suite }; + tree.add_leaf(leaf_node); + tree.add_leaf(leaf_node_2); + auto ratchet_tree_obj = RatchetTreeExtension{ tree }; + + // Welcome and its substituents + auto group_info_obj = + GroupInfo{ group_context, ext_list, prg.secret("confirmation_tag") }; + auto joiner_secret = prg.secret("joiner_secret"); + auto path_secret = prg.secret("path_secret"); + auto psk_id = ExternalPSK{ prg.secret("psk_id") }; + auto psk_nonce = prg.secret("psk_nonce"); + auto group_secrets_obj = GroupSecrets{ joiner_secret, + { { path_secret } }, + PreSharedKeys{ { + { psk_id, psk_nonce }, + } } }; + auto welcome_obj = Welcome{ suite, joiner_secret, {}, group_info_obj }; + welcome_obj.encrypt(key_package_obj, path_secret); + + // Proposals + auto add = Add{ key_package_obj }; + auto update = Update{ leaf_node_update }; + auto remove = Remove{ index }; + auto pre_shared_key = PreSharedKey{ psk_id, psk_nonce }; + auto reinit = ReInit{ group_id, version, suite, {} }; + auto external_init = ExternalInit{ prg.secret("external_init") }; + + // Commit + auto proposal_ref = ProposalRef{ 32, 0xa0 }; + + auto commit_obj = Commit{ { + { proposal_ref }, + { Proposal{ add } }, + }, + UpdatePath{ + leaf_node_commit, + { + { hpke_pub, { hpke_ct, hpke_ct } }, + { hpke_pub, { hpke_ct, hpke_ct, hpke_ct } }, + }, + } }; + + // AuthenticatedContent with Application / Proposal / Commit + + // PublicMessage + auto membership_key = prg.secret("membership_key"); + + auto content_auth_proposal = AuthenticatedContent::sign( + WireFormat::mls_public_message, + { group_id, epoch, sender, {}, Proposal{ remove } }, + suite, + sig_priv, + group_context); + auto public_message_proposal_obj = PublicMessage::protect( + content_auth_proposal, suite, membership_key, group_context); + + auto content_auth_commit = + AuthenticatedContent::sign(WireFormat::mls_public_message, + { group_id, epoch, sender, {}, commit_obj }, + suite, + sig_priv, + group_context); + content_auth_commit.set_confirmation_tag(prg.secret("confirmation_tag")); + auto public_message_commit_obj = PublicMessage::protect( + content_auth_commit, suite, membership_key, group_context); + + // PrivateMessage + auto content_auth_application_obj = AuthenticatedContent::sign( + WireFormat::mls_private_message, + { group_id, epoch, sender, {}, ApplicationData{} }, + suite, + sig_priv, + group_context); + + auto keys = GroupKeySource( + suite, LeafCount{ index.val + 1 }, prg.secret("encryption_secret")); + auto private_message_obj = + PrivateMessage::protect(content_auth_application_obj, + suite, + keys, + prg.secret("sender_data_secret"), + 10); + + // Serialize out all the objects + mls_welcome = tls::marshal(MLSMessage{ welcome_obj }); + mls_group_info = tls::marshal(MLSMessage{ group_info_obj }); + mls_key_package = tls::marshal(MLSMessage{ key_package_obj }); + + ratchet_tree = tls::marshal(ratchet_tree_obj); + group_secrets = tls::marshal(group_secrets_obj); + + add_proposal = tls::marshal(add); + update_proposal = tls::marshal(update); + remove_proposal = tls::marshal(remove); + pre_shared_key_proposal = tls::marshal(pre_shared_key); + re_init_proposal = tls::marshal(reinit); + external_init_proposal = tls::marshal(external_init); + + commit = tls::marshal(commit_obj); + + public_message_proposal = + tls::marshal(MLSMessage{ public_message_proposal_obj }); + public_message_commit = tls::marshal(MLSMessage{ public_message_commit_obj }); + private_message = tls::marshal(MLSMessage{ private_message_obj }); +} + +std::optional +MessagesTestVector::verify() const +{ + // TODO(RLB) Verify signatures + // TODO(RLB) Verify content types in PublicMessage objects + auto require_format = [](WireFormat format) { + return + [format](const MLSMessage& msg) { return msg.wire_format() == format; }; + }; + + VERIFY_TLS_RTT_VAL("Welcome", + MLSMessage, + mls_welcome, + require_format(WireFormat::mls_welcome)); + VERIFY_TLS_RTT_VAL("GroupInfo", + MLSMessage, + mls_group_info, + require_format(WireFormat::mls_group_info)); + VERIFY_TLS_RTT_VAL("KeyPackage", + MLSMessage, + mls_key_package, + require_format(WireFormat::mls_key_package)); + + VERIFY_TLS_RTT("RatchetTree", RatchetTreeExtension, ratchet_tree); + VERIFY_TLS_RTT("GroupSecrets", GroupSecrets, group_secrets); + + VERIFY_TLS_RTT("Add", Add, add_proposal); + VERIFY_TLS_RTT("Update", Update, update_proposal); + VERIFY_TLS_RTT("Remove", Remove, remove_proposal); + VERIFY_TLS_RTT("PreSharedKey", PreSharedKey, pre_shared_key_proposal); + VERIFY_TLS_RTT("ReInit", ReInit, re_init_proposal); + VERIFY_TLS_RTT("ExternalInit", ExternalInit, external_init_proposal); + + VERIFY_TLS_RTT("Commit", Commit, commit); + + VERIFY_TLS_RTT_VAL("Public(Proposal)", + MLSMessage, + public_message_proposal, + require_format(WireFormat::mls_public_message)); + VERIFY_TLS_RTT_VAL("Public(Commit)", + MLSMessage, + public_message_commit, + require_format(WireFormat::mls_public_message)); + VERIFY_TLS_RTT_VAL("PrivateMessage", + MLSMessage, + private_message, + require_format(WireFormat::mls_private_message)); + + return std::nullopt; +} + +std::optional +PassiveClientTestVector::verify() +{ + // Import everything + signature_priv.set_public_key(cipher_suite); + encryption_priv.set_public_key(cipher_suite); + init_priv.set_public_key(cipher_suite); + + const auto& key_package_raw = var::get(key_package.message); + const auto& welcome_raw = var::get(welcome.message); + + auto ext_psks = std::map{}; + for (const auto& [id, psk] : external_psks) { + ext_psks.insert_or_assign(id, psk); + } + + // Join the group and follow along + auto state = State(init_priv, + encryption_priv, + signature_priv, + key_package_raw, + welcome_raw, + ratchet_tree, + ext_psks); + VERIFY_EQUAL( + "initial epoch", state.epoch_authenticator(), initial_epoch_authenticator); + + for (const auto& tve : epochs) { + for (const auto& proposal : tve.proposals) { + state.handle(proposal); + } + + state = opt::get(state.handle(tve.commit)); + VERIFY_EQUAL( + "epoch auth", state.epoch_authenticator(), tve.epoch_authenticator) + } + + return std::nullopt; +} + +} // namespace mls_vectors diff --git a/mlspp/lib/tls_syntax/CMakeLists.txt b/mlspp/lib/tls_syntax/CMakeLists.txt new file mode 100755 index 0000000000..40d4f93619 --- /dev/null +++ b/mlspp/lib/tls_syntax/CMakeLists.txt @@ -0,0 +1,21 @@ +set(CURRENT_LIB_NAME tls_syntax) + +### +### Library Config +### + +file(GLOB_RECURSE LIB_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h") +file(GLOB_RECURSE LIB_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") + +add_library(${CURRENT_LIB_NAME} STATIC ${LIB_HEADERS} ${LIB_SOURCES}) +target_include_directories(${CURRENT_LIB_NAME} + PUBLIC + $ + $ + $ +) + +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../lib/bytes/include") +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../lib/hpke/include") +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../lib/mls_vectors/include") +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../lib/tls_syntax/include") diff --git a/mlspp/lib/tls_syntax/include/tls/compat.h b/mlspp/lib/tls_syntax/include/tls/compat.h new file mode 100755 index 0000000000..095cc17ff6 --- /dev/null +++ b/mlspp/lib/tls_syntax/include/tls/compat.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include + +#ifdef VARIANT_COMPAT +#include +#else +#include +#endif // VARIANT_COMPAT + +namespace mlspp::tls { + +namespace var = std; + +// In a similar vein, we provide our own safe accessors for std::optional, since +// std::optional::value() is not available on macOS 10.11. +namespace opt { + +template +T& +get(std::optional& opt) +{ + if (!opt) { + throw std::runtime_error("bad_optional_access"); + } + return *opt; +} + +template +const T& +get(const std::optional& opt) +{ + if (!opt) { + throw std::runtime_error("bad_optional_access"); + } + return *opt; +} + +template +T&& +get(std::optional&& opt) +{ + if (!opt) { + throw std::runtime_error("bad_optional_access"); + } + return std::move(*opt); +} + +template +const T&& +get(const std::optional&& opt) +{ + if (!opt) { + throw std::runtime_error("bad_optional_access"); + } + return std::move(*opt); +} + +} // namespace opt +} // namespace mlspp::tls diff --git a/mlspp/lib/tls_syntax/include/tls/tls_syntax.h b/mlspp/lib/tls_syntax/include/tls/tls_syntax.h new file mode 100755 index 0000000000..09d5940d9d --- /dev/null +++ b/mlspp/lib/tls_syntax/include/tls/tls_syntax.h @@ -0,0 +1,569 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace mlspp::tls { + +// For indicating no min or max in vector definitions +const size_t none = std::numeric_limits::max(); + +class WriteError : public std::invalid_argument +{ +public: + using parent = std::invalid_argument; + using parent::parent; +}; + +class ReadError : public std::invalid_argument +{ +public: + using parent = std::invalid_argument; + using parent::parent; +}; + +/// +/// Declarations of Streams and Traits +/// + +class ostream +{ +public: + static const size_t none = std::numeric_limits::max(); + + void write_raw(const std::vector& bytes); + + const std::vector& bytes() const { return _buffer; } + size_t size() const { return _buffer.size(); } + bool empty() const { return _buffer.empty(); } + +private: + std::vector _buffer; + ostream& write_uint(uint64_t value, int length); + + friend ostream& operator<<(ostream& out, bool data); + friend ostream& operator<<(ostream& out, uint8_t data); + friend ostream& operator<<(ostream& out, uint16_t data); + friend ostream& operator<<(ostream& out, uint32_t data); + friend ostream& operator<<(ostream& out, uint64_t data); + + template + friend ostream& operator<<(ostream& out, const std::vector& data); + + friend struct varint; +}; + +class istream +{ +public: + istream(const std::vector& data) + : _buffer(data) + { + // So that we can use the constant-time pop_back + std::reverse(_buffer.begin(), _buffer.end()); + } + + size_t size() const { return _buffer.size(); } + bool empty() const { return _buffer.empty(); } + + std::vector bytes() + { + auto bytes = _buffer; + std::reverse(bytes.begin(), bytes.end()); + return bytes; + } + +private: + istream() {} + std::vector _buffer; + uint8_t next(); + + template + istream& read_uint(T& data, size_t length) + { + uint64_t value = 0; + for (size_t i = 0; i < length; i += 1) { + value = (value << unsigned(8)) + next(); + } + data = static_cast(value); + return *this; + } + + friend istream& operator>>(istream& in, bool& data); + friend istream& operator>>(istream& in, uint8_t& data); + friend istream& operator>>(istream& in, uint16_t& data); + friend istream& operator>>(istream& in, uint32_t& data); + friend istream& operator>>(istream& in, uint64_t& data); + + template + friend istream& operator>>(istream& in, std::vector& data); + + friend struct varint; +}; + +// Traits must have static encode and decode methods, of the following form: +// +// static ostream& encode(ostream& str, const T& val); +// static istream& decode(istream& str, T& val); +// +// Trait types will never be constructed; only these static methods are used. +// The value arguments to encode and decode can be as strict or as loose as +// desired. +// +// Ultimately, all interesting encoding should be done through traits. +// +// * vectors +// * variants +// * varints + +struct pass +{ + template + static ostream& encode(ostream& str, const T& val); + + template + static istream& decode(istream& str, T& val); +}; + +template +struct variant +{ + template + static inline Ts type(const var::variant& data); + + template + static ostream& encode(ostream& str, const var::variant& data); + + template + static inline typename std::enable_if::type + read_variant(istream&, Te, var::variant&); + + template + static inline typename std::enable_if < + I::type read_variant(istream& str, + Te target_type, + var::variant& v); + + template + static istream& decode(istream& str, var::variant& data); +}; + +struct varint +{ + static ostream& encode(ostream& str, const uint64_t& val); + static istream& decode(istream& str, uint64_t& val); +}; + +/// +/// Writer implementations +/// + +// Primitive writers defined in .cpp file + +// Array writer +template +ostream& +operator<<(ostream& out, const std::array& data) +{ + for (const auto& item : data) { + out << item; + } + return out; +} + +// Optional writer +template +ostream& +operator<<(ostream& out, const std::optional& opt) +{ + if (!opt) { + return out << uint8_t(0); + } + + return out << uint8_t(1) << opt::get(opt); +} + +// Enum writer +template::value, int> = 0> +ostream& +operator<<(ostream& str, const T& val) +{ + auto u = static_cast>(val); + return str << u; +} + +// Vector writer +template +ostream& +operator<<(ostream& str, const std::vector& vec) +{ + // Pre-encode contents + ostream temp; + for (const auto& item : vec) { + temp << item; + } + + // Write the encoded length, then the pre-encoded data + varint::encode(str, temp._buffer.size()); + str.write_raw(temp.bytes()); + + return str; +} + +/// +/// Reader implementations +/// + +// Primitive type readers defined in .cpp file + +// Array reader +template +istream& +operator>>(istream& in, std::array& data) +{ + for (auto& item : data) { + in >> item; + } + return in; +} + +// Optional reader +template +istream& +operator>>(istream& in, std::optional& opt) +{ + uint8_t present = 0; + in >> present; + + switch (present) { + case 0: + opt.reset(); + return in; + + case 1: + opt.emplace(); + return in >> opt::get(opt); + + default: + throw std::invalid_argument("Malformed optional"); + } +} + +// Enum reader +// XXX(rlb): It would be nice if this could enforce that the values are valid, +// but C++ doesn't seem to have that ability. When used as a tag for variants, +// the variant reader will enforce, at least. +template::value, int> = 0> +istream& +operator>>(istream& str, T& val) +{ + std::underlying_type_t u; + str >> u; + val = static_cast(u); + return str; +} + +// Vector reader +template +istream& +operator>>(istream& str, std::vector& vec) +{ + // Read the encoded data size + auto size = uint64_t(0); + varint::decode(str, size); + if (size > str._buffer.size()) { + throw ReadError("Vector is longer than remaining data"); + } + + // Read the elements of the vector + // NB: Remember that we store the vector in reverse order + // NB: This requires that T be default-constructible + istream r; + r._buffer = + std::vector{ str._buffer.end() - size, str._buffer.end() }; + + vec.clear(); + while (r._buffer.size() > 0) { + vec.emplace_back(); + r >> vec.back(); + } + + // Truncate the primary buffer + str._buffer.erase(str._buffer.end() - size, str._buffer.end()); + + return str; +} + +// Abbreviations +template +std::vector +marshal(const T& value) +{ + ostream w; + w << value; + return w.bytes(); +} + +template +void +unmarshal(const std::vector& data, T& value) +{ + istream r(data); + r >> value; +} + +template +T +get(const std::vector& data, Tp... args) +{ + T value(args...); + unmarshal(data, value); + return value; +} + +// Use this macro to define struct serialization with minimal boilerplate +#define TLS_SERIALIZABLE(...) \ + static const bool _tls_serializable = true; \ + auto _tls_fields_r() \ + { \ + return std::forward_as_tuple(__VA_ARGS__); \ + } \ + auto _tls_fields_w() const \ + { \ + return std::forward_as_tuple(__VA_ARGS__); \ + } + +// If your struct contains nontrivial members (e.g., vectors), use this to +// define traits for them. +#define TLS_TRAITS(...) \ + static const bool _tls_has_traits = true; \ + using _tls_traits = std::tuple<__VA_ARGS__>; + +template +struct is_serializable +{ + template + static std::true_type test(decltype(U::_tls_serializable)); + + template + static std::false_type test(...); + + static const bool value = decltype(test(true))::value; +}; + +template +struct has_traits +{ + template + static std::true_type test(decltype(U::_tls_has_traits)); + + template + static std::false_type test(...); + + static const bool value = decltype(test(true))::value; +}; + +/// +/// Trait implementations +/// + +// Pass-through (normal encoding/decoding) +template +ostream& +pass::encode(ostream& str, const T& val) +{ + return str << val; +} + +template +istream& +pass::decode(istream& str, T& val) +{ + return str >> val; +} + +// Variant encoding +template +constexpr Ts +variant_map(); + +#define TLS_VARIANT_MAP(EnumType, MappedType, enum_value) \ + template<> \ + constexpr EnumType variant_map() \ + { \ + return EnumType::enum_value; \ + } + +template +template +inline Ts +variant::type(const var::variant& data) +{ + const auto get_type = [](const auto& v) { + return variant_map>(); + }; + return var::visit(get_type, data); +} + +template +template +ostream& +variant::encode(ostream& str, const var::variant& data) +{ + const auto write_variant = [&str](auto&& v) { + using Tv = std::decay_t; + str << variant_map() << v; + }; + var::visit(write_variant, data); + return str; +} + +template +template +inline typename std::enable_if::type +variant::read_variant(istream&, Te, var::variant&) +{ + throw ReadError("Invalid variant type label"); +} + +template + template + inline + typename std::enable_if < I::type + variant::read_variant(istream& str, + Te target_type, + var::variant& v) +{ + using Tc = var::variant_alternative_t>; + if (variant_map() == target_type) { + str >> v.template emplace(); + return; + } + + read_variant(str, target_type, v); +} + +template +template +istream& +variant::decode(istream& str, var::variant& data) +{ + Ts target_type; + str >> target_type; + read_variant(str, target_type, data); + return str; +} + +// Struct writer without traits (enabled by macro) +template +inline typename std::enable_if::type +write_tuple(ostream&, const std::tuple&) +{ +} + +template + inline typename std::enable_if < + I::type + write_tuple(ostream& str, const std::tuple& t) +{ + str << std::get(t); + write_tuple(str, t); +} + +template +inline + typename std::enable_if::value && !has_traits::value, + ostream&>::type + operator<<(ostream& str, const T& obj) +{ + write_tuple(str, obj._tls_fields_w()); + return str; +} + +// Struct writer with traits (enabled by macro) +template +inline typename std::enable_if::type +write_tuple_traits(ostream&, const std::tuple&) +{ +} + +template + inline typename std::enable_if < + I::type + write_tuple_traits(ostream& str, const std::tuple& t) +{ + std::tuple_element_t::encode(str, std::get(t)); + write_tuple_traits(str, t); +} + +template +inline + typename std::enable_if::value && has_traits::value, + ostream&>::type + operator<<(ostream& str, const T& obj) +{ + write_tuple_traits(str, obj._tls_fields_w()); + return str; +} + +// Struct reader without traits (enabled by macro) +template +inline typename std::enable_if::type +read_tuple(istream&, const std::tuple&) +{ +} + +template + inline + typename std::enable_if < I::type + read_tuple(istream& str, const std::tuple& t) +{ + str >> std::get(t); + read_tuple(str, t); +} + +template +inline + typename std::enable_if::value && !has_traits::value, + istream&>::type + operator>>(istream& str, T& obj) +{ + read_tuple(str, obj._tls_fields_r()); + return str; +} + +// Struct reader with traits (enabled by macro) +template +inline typename std::enable_if::type +read_tuple_traits(istream&, const std::tuple&) +{ +} + +template + inline typename std::enable_if < + I::type + read_tuple_traits(istream& str, const std::tuple& t) +{ + std::tuple_element_t::decode(str, std::get(t)); + read_tuple_traits(str, t); +} + +template +inline + typename std::enable_if::value && has_traits::value, + istream&>::type + operator>>(istream& str, T& obj) +{ + read_tuple_traits(str, obj._tls_fields_r()); + return str; +} + +} // namespace mlspp::tls diff --git a/mlspp/lib/tls_syntax/src/tls_syntax.cpp b/mlspp/lib/tls_syntax/src/tls_syntax.cpp new file mode 100755 index 0000000000..efbea150b5 --- /dev/null +++ b/mlspp/lib/tls_syntax/src/tls_syntax.cpp @@ -0,0 +1,178 @@ +#include + +// NOLINTNEXTLINE(llvmlibc-implementation-in-namespace) +namespace mlspp::tls { + +void +ostream::write_raw(const std::vector& bytes) +{ + // Not sure what the default argument is here + _buffer.insert(_buffer.end(), bytes.begin(), bytes.end()); +} + +// Primitive type writers +ostream& +ostream::write_uint(uint64_t value, int length) +{ + for (int i = length - 1; i >= 0; --i) { + _buffer.push_back(static_cast(value >> unsigned(8 * i))); + } + return *this; +} + +ostream& +operator<<(ostream& out, bool data) +{ + if (data) { + return out << uint8_t(1); + } + + return out << uint8_t(0); +} + +ostream& +operator<<(ostream& out, uint8_t data) // NOLINT(llvmlibc-callee-namespace) +{ + return out.write_uint(data, 1); +} + +ostream& +operator<<(ostream& out, uint16_t data) +{ + return out.write_uint(data, 2); +} + +ostream& +operator<<(ostream& out, uint32_t data) +{ + return out.write_uint(data, 4); +} + +ostream& +operator<<(ostream& out, uint64_t data) +{ + return out.write_uint(data, 8); +} + +// Because pop_back() on an empty vector is undefined +uint8_t +istream::next() +{ + if (_buffer.empty()) { + throw ReadError("Attempt to read from empty buffer"); + } + + const uint8_t value = _buffer.back(); + _buffer.pop_back(); + return value; +} + +// Primitive type readers + +istream& +operator>>(istream& in, bool& data) +{ + uint8_t val = 0; + in >> val; + + // Linter thinks uint8_t is signed (?) + // NOLINTNEXTLINE(hicpp-signed-bitwise) + if ((val & 0xFE) != 0) { + throw ReadError("Malformed boolean"); + } + + data = (val == 1); + return in; +} + +istream& +operator>>(istream& in, uint8_t& data) // NOLINT(llvmlibc-callee-namespace) +{ + return in.read_uint(data, 1); +} + +istream& +operator>>(istream& in, uint16_t& data) +{ + return in.read_uint(data, 2); +} + +istream& +operator>>(istream& in, uint32_t& data) +{ + return in.read_uint(data, 4); +} + +istream& +operator>>(istream& in, uint64_t& data) +{ + return in.read_uint(data, 8); +} + +// Varint encoding +static constexpr size_t VARINT_HEADER_OFFSET = 6; +static constexpr uint64_t VARINT_1_HEADER = 0x00; // 0 << V1_OFFSET +static constexpr uint64_t VARINT_2_HEADER = 0x4000; // 1 << V2_OFFSET +static constexpr uint64_t VARINT_4_HEADER = 0x80000000; // 2 << V4_OFFSET +static constexpr uint64_t VARINT_1_MAX = 0x3f; +static constexpr uint64_t VARINT_2_MAX = 0x3fff; +static constexpr uint64_t VARINT_4_MAX = 0x3fffffff; + +ostream& +varint::encode(ostream& str, const uint64_t& val) +{ + if (val <= VARINT_1_MAX) { + return str.write_uint(VARINT_1_HEADER | val, 1); + } + + if (val <= VARINT_2_MAX) { + return str.write_uint(VARINT_2_HEADER | val, 2); + } + + if (val <= VARINT_4_MAX) { + return str.write_uint(VARINT_4_HEADER | val, 4); + } + + throw WriteError("Varint value exceeds maximum size"); +} + +istream& +varint::decode(istream& str, uint64_t& val) +{ + auto log_size = size_t(str._buffer.back() >> VARINT_HEADER_OFFSET); + if (log_size > 2) { + throw ReadError("Malformed varint header"); + } + + auto read = uint64_t(0); + auto read_bytes = size_t(size_t(1) << log_size); + str.read_uint(read, read_bytes); + + switch (log_size) { + case 0: + read ^= VARINT_1_HEADER; + break; + + case 1: + read ^= VARINT_2_HEADER; + if (read <= VARINT_1_MAX) { + throw ReadError("Non-minimal varint"); + } + break; + + case 2: + read ^= VARINT_4_HEADER; + if (read <= VARINT_2_MAX) { + throw ReadError("Non-minimal varint"); + } + break; + + default: + throw ReadError("Malformed varint header"); + } + + val = read; + return str; +} + +} // namespace mlspp::tls diff --git a/mlspp/src/common.cpp b/mlspp/src/common.cpp new file mode 100755 index 0000000000..6895bfb4d7 --- /dev/null +++ b/mlspp/src/common.cpp @@ -0,0 +1,13 @@ +#include "mls/common.h" + +namespace mlspp { + +uint64_t +seconds_since_epoch() +{ + // TODO(RLB) This should use std::chrono, but that seems not to be available + // on some platforms. + return std::time(nullptr); +} + +} // namespace mlspp diff --git a/mlspp/src/core_types.cpp b/mlspp/src/core_types.cpp new file mode 100755 index 0000000000..19cee0d4c2 --- /dev/null +++ b/mlspp/src/core_types.cpp @@ -0,0 +1,443 @@ +#include "mls/core_types.h" +#include "mls/messages.h" + +#include "grease.h" + +#include + +namespace mlspp { + +/// +/// Extensions +/// + +const Extension::Type RequiredCapabilitiesExtension::type = + ExtensionType::required_capabilities; +const Extension::Type ApplicationIDExtension::type = + ExtensionType::application_id; + +const std::array default_extensions = { + ExtensionType::application_id, ExtensionType::ratchet_tree, + ExtensionType::required_capabilities, ExtensionType::external_pub, + ExtensionType::external_senders, +}; + +const std::array default_proposals = { + ProposalType::add, + ProposalType::update, + ProposalType::remove, + ProposalType::psk, + ProposalType::reinit, + ProposalType::external_init, + ProposalType::group_context_extensions, +}; + +const std::array all_supported_versions = { + ProtocolVersion::mls10 +}; + +const std::array all_supported_ciphersuites = { + CipherSuite::ID::X25519_AES128GCM_SHA256_Ed25519, + CipherSuite::ID::P256_AES128GCM_SHA256_P256, + CipherSuite::ID::X25519_CHACHA20POLY1305_SHA256_Ed25519, + CipherSuite::ID::X448_AES256GCM_SHA512_Ed448, + CipherSuite::ID::P521_AES256GCM_SHA512_P521, + CipherSuite::ID::X448_CHACHA20POLY1305_SHA512_Ed448, +}; + +const std::array all_supported_credentials = { + CredentialType::basic, + CredentialType::x509, + CredentialType::userinfo_vc_draft_00, + CredentialType::multi_draft_00 +}; + +Capabilities +Capabilities::create_default() +{ + return { + { all_supported_versions.begin(), all_supported_versions.end() }, + { all_supported_ciphersuites.begin(), all_supported_ciphersuites.end() }, + { /* No non-default extensions */ }, + { /* No non-default proposals */ }, + { all_supported_credentials.begin(), all_supported_credentials.end() }, + }; +} + +bool +Capabilities::extensions_supported( + const std::vector& required) const +{ + return stdx::all_of(required, [&](Extension::Type type) { + if (stdx::contains(default_extensions, type)) { + return true; + } + + return stdx::contains(extensions, type); + }); +} + +bool +Capabilities::proposals_supported( + const std::vector& required) const +{ + return stdx::all_of(required, [&](Proposal::Type type) { + if (stdx::contains(default_proposals, type)) { + return true; + } + + return stdx::contains(proposals, type); + }); +} + +bool +Capabilities::credential_supported(const Credential& credential) const +{ + return stdx::contains(credentials, credential.type()); +} + +Lifetime +Lifetime::create_default() +{ + return Lifetime{ 0x0000000000000000, 0xffffffffffffffff }; +} + +void +ExtensionList::add(uint16_t type, bytes data) +{ + auto curr = stdx::find_if( + extensions, [&](const Extension& ext) -> bool { return ext.type == type; }); + if (curr != extensions.end()) { + curr->data = std::move(data); + return; + } + + extensions.push_back({ type, std::move(data) }); +} + +bool +ExtensionList::has(uint16_t type) const +{ + return stdx::any_of(extensions, + [&](const Extension& ext) { return ext.type == type; }); +} + +/// +/// LeafNode +/// +LeafNode::LeafNode(CipherSuite cipher_suite, + HPKEPublicKey encryption_key_in, + SignaturePublicKey signature_key_in, + Credential credential_in, + Capabilities capabilities_in, + Lifetime lifetime_in, + ExtensionList extensions_in, + const SignaturePrivateKey& sig_priv) + : encryption_key(std::move(encryption_key_in)) + , signature_key(std::move(signature_key_in)) + , credential(std::move(credential_in)) + , capabilities(std::move(capabilities_in)) + , content(lifetime_in) + , extensions(std::move(extensions_in)) +{ + grease(extensions); + grease(capabilities, extensions); + sign(cipher_suite, sig_priv, std::nullopt); +} + +void +LeafNode::set_capabilities(Capabilities capabilities_in) +{ + capabilities = std::move(capabilities_in); + grease(capabilities, extensions); +} + +LeafNode +LeafNode::for_update(CipherSuite cipher_suite, + const bytes& group_id, + LeafIndex leaf_index, + HPKEPublicKey encryption_key_in, + const LeafNodeOptions& opts, + const SignaturePrivateKey& sig_priv) const +{ + auto clone = clone_with_options(std::move(encryption_key_in), opts); + + clone.content = Empty{}; + clone.sign(cipher_suite, sig_priv, { { group_id, leaf_index } }); + + return clone; +} + +LeafNode +LeafNode::for_commit(CipherSuite cipher_suite, + const bytes& group_id, + LeafIndex leaf_index, + HPKEPublicKey encryption_key_in, + const bytes& parent_hash, + const LeafNodeOptions& opts, + const SignaturePrivateKey& sig_priv) const +{ + auto clone = clone_with_options(std::move(encryption_key_in), opts); + + clone.content = ParentHash{ parent_hash }; + clone.sign(cipher_suite, sig_priv, { { group_id, leaf_index } }); + + return clone; +} + +LeafNodeSource +LeafNode::source() const +{ + return tls::variant::type(content); +} + +void +LeafNode::sign(CipherSuite cipher_suite, + const SignaturePrivateKey& sig_priv, + const std::optional& binding) +{ + const auto tbs = to_be_signed(binding); + + if (sig_priv.public_key != signature_key) { + throw InvalidParameterError("Signature key mismatch"); + } + + if (!credential.valid_for(signature_key)) { + throw InvalidParameterError("Credential not valid for signature key"); + } + + signature = sig_priv.sign(cipher_suite, sign_label::leaf_node, tbs); +} + +bool +LeafNode::verify(CipherSuite cipher_suite, + const std::optional& binding) const +{ + const auto tbs = to_be_signed(binding); + + if (CredentialType::x509 == credential.type()) { + const auto& cred = credential.get(); + if (cred.signature_scheme() != + tls_signature_scheme(cipher_suite.sig().id)) { + throw std::runtime_error("Signature algorithm invalid"); + } + } + + return signature_key.verify( + cipher_suite, sign_label::leaf_node, tbs, signature); +} + +bool +LeafNode::verify_expiry(uint64_t now) const +{ + const auto valid = overloaded{ + [now](const Lifetime& lt) { + return lt.not_before <= now && now <= lt.not_after; + }, + [](const auto& /* other */) { return false; }, + }; + return var::visit(valid, content); +} + +bool +LeafNode::verify_extension_support(const ExtensionList& ext_list) const +{ + // Verify that extensions in the list are supported + auto ext_types = stdx::transform( + ext_list.extensions, [](const auto& ext) { return ext.type; }); + + if (!capabilities.extensions_supported(ext_types)) { + return false; + } + + // If there's a RequiredCapabilities extension, verify support + const auto maybe_req_capas = ext_list.find(); + if (!maybe_req_capas) { + return true; + } + + const auto& req_capas = opt::get(maybe_req_capas); + return capabilities.extensions_supported(req_capas.extensions) && + capabilities.proposals_supported(req_capas.proposals); +} + +LeafNode +LeafNode::clone_with_options(HPKEPublicKey encryption_key_in, + const LeafNodeOptions& opts) const +{ + auto clone = *this; + + clone.encryption_key = std::move(encryption_key_in); + + if (opts.credential) { + clone.credential = opt::get(opts.credential); + } + + if (opts.capabilities) { + clone.capabilities = opt::get(opts.capabilities); + } + + if (opts.extensions) { + clone.extensions = opt::get(opts.extensions); + } + + return clone; +} + +// struct { +// HPKEPublicKey encryption_key; +// SignaturePublicKey signature_key; +// Credential credential; +// Capabilities capabilities; +// +// LeafNodeSource leaf_node_source; +// select (leaf_node_source) { +// case key_package: +// Lifetime lifetime; +// +// case update: +// struct{}; +// +// case commit: +// opaque parent_hash; +// } +// +// Extension extensions; +// +// select (leaf_node_source) { +// case key_package: +// struct{}; +// +// case update: +// opaque group_id; +// +// case commit: +// opaque group_id; +// } +// } LeafNodeTBS; +struct LeafNodeTBS +{ + const HPKEPublicKey& encryption_key; + const SignaturePublicKey& signature_key; + const Credential& credential; + const Capabilities& capabilities; + const var::variant& content; + const ExtensionList& extensions; + + TLS_SERIALIZABLE(encryption_key, + signature_key, + credential, + capabilities, + content, + extensions) + TLS_TRAITS(tls::pass, + tls::pass, + tls::pass, + tls::pass, + tls::variant, + tls::pass) +}; + +bytes +LeafNode::to_be_signed(const std::optional& binding) const +{ + tls::ostream w; + + w << LeafNodeTBS{ + encryption_key, signature_key, credential, + capabilities, content, extensions, + }; + + switch (source()) { + case LeafNodeSource::key_package: + break; + + case LeafNodeSource::update: + case LeafNodeSource::commit: + w << opt::get(binding); + } + + return w.bytes(); +} + +/// +/// NodeType, ParentNode, and KeyPackage +/// + +bytes +ParentNode::hash(CipherSuite suite) const +{ + return suite.digest().hash(tls::marshal(this)); +} + +KeyPackage::KeyPackage() + : version(ProtocolVersion::mls10) + , cipher_suite(CipherSuite::ID::unknown) +{ +} + +KeyPackage::KeyPackage(CipherSuite suite_in, + HPKEPublicKey init_key_in, + LeafNode leaf_node_in, + ExtensionList extensions_in, + const SignaturePrivateKey& sig_priv_in) + : version(ProtocolVersion::mls10) + , cipher_suite(suite_in) + , init_key(std::move(init_key_in)) + , leaf_node(std::move(leaf_node_in)) + , extensions(std::move(extensions_in)) +{ + grease(extensions); + sign(sig_priv_in); +} + +KeyPackageRef +KeyPackage::ref() const +{ + return cipher_suite.ref(*this); +} + +void +KeyPackage::sign(const SignaturePrivateKey& sig_priv) +{ + auto tbs = to_be_signed(); + signature = sig_priv.sign(cipher_suite, sign_label::key_package, tbs); +} + +bool +KeyPackage::verify() const +{ + // Verify the inner leaf node + if (!leaf_node.verify(cipher_suite, std::nullopt)) { + return false; + } + + // Check that the inner leaf node is intended for use in a KeyPackage + if (leaf_node.source() != LeafNodeSource::key_package) { + return false; + } + + // Verify the KeyPackage + const auto tbs = to_be_signed(); + + if (CredentialType::x509 == leaf_node.credential.type()) { + const auto& cred = leaf_node.credential.get(); + if (cred.signature_scheme() != + tls_signature_scheme(cipher_suite.sig().id)) { + throw std::runtime_error("Signature algorithm invalid"); + } + } + + return leaf_node.signature_key.verify( + cipher_suite, sign_label::key_package, tbs, signature); +} + +bytes +KeyPackage::to_be_signed() const +{ + tls::ostream out; + out << version << cipher_suite << init_key << leaf_node << extensions; + return out.bytes(); +} + +} // namespace mlspp diff --git a/mlspp/src/credential.cpp b/mlspp/src/credential.cpp new file mode 100755 index 0000000000..4edb3a9b82 --- /dev/null +++ b/mlspp/src/credential.cpp @@ -0,0 +1,298 @@ +#include +#include +#include +#include + +namespace mlspp { + +/// +/// X509Credential +/// + +using mlspp::hpke::Certificate; // NOLINT(misc-unused-using-decls) +using mlspp::hpke::Signature; // NOLINT(misc-unused-using-decls) +using mlspp::hpke::UserInfoVC; // NOLINT(misc-unused-using-decls) + +static const Signature& +find_signature(Signature::ID id) +{ + switch (id) { + case Signature::ID::P256_SHA256: + return Signature::get(); + case Signature::ID::P384_SHA384: + return Signature::get(); + case Signature::ID::P521_SHA512: + return Signature::get(); + case Signature::ID::Ed25519: + return Signature::get(); +#if !defined(WITH_BORINGSSL) + case Signature::ID::Ed448: + return Signature::get(); +#endif + case Signature::ID::RSA_SHA256: + return Signature::get(); + default: + throw InvalidParameterError("Unsupported algorithm"); + } +} + +static std::vector +bytes_to_x509_credential_data(const std::vector& data_in) +{ + return stdx::transform( + data_in, [](const bytes& der) { return X509Credential::CertData{ der }; }); +} + +X509Credential::X509Credential(const std::vector& der_chain_in) + : der_chain(bytes_to_x509_credential_data(der_chain_in)) +{ + if (der_chain.empty()) { + throw std::invalid_argument("empty certificate chain"); + } + + // Parse the chain + auto parsed = std::vector(); + for (const auto& cert : der_chain) { + parsed.emplace_back(cert.data); + } + + // first element represents leaf cert + const auto& sig = find_signature(parsed[0].public_key_algorithm()); + const auto pub_data = sig.serialize(*parsed[0].public_key); + _signature_scheme = tls_signature_scheme(parsed[0].public_key_algorithm()); + _public_key = SignaturePublicKey{ pub_data }; + + // verify chain for valid signatures + for (size_t i = 0; i < der_chain.size() - 1; i++) { + if (!parsed[i].valid_from(parsed[i + 1])) { + throw std::runtime_error("Certificate Chain validation failure"); + } + } +} + +SignatureScheme +X509Credential::signature_scheme() const +{ + return _signature_scheme; +} + +SignaturePublicKey +X509Credential::public_key() const +{ + return _public_key; +} + +bool +X509Credential::valid_for(const SignaturePublicKey& pub) const +{ + return pub == public_key(); +} + +tls::ostream& +operator<<(tls::ostream& str, const X509Credential& obj) +{ + return str << obj.der_chain; +} + +tls::istream& +operator>>(tls::istream& str, X509Credential& obj) +{ + auto der_chain = std::vector{}; + str >> der_chain; + + auto der_in = stdx::transform( + der_chain, [](const auto& cert_data) { return cert_data.data; }); + obj = X509Credential(der_in); + + return str; +} + +bool +operator==(const X509Credential& lhs, const X509Credential& rhs) +{ + return lhs.der_chain == rhs.der_chain; +} + +/// +/// UserInfoVCCredential +/// +UserInfoVCCredential::UserInfoVCCredential(std::string userinfo_vc_jwt_in) + : userinfo_vc_jwt(std::move(userinfo_vc_jwt_in)) + , _vc(std::make_shared(userinfo_vc_jwt)) +{ +} + +bool +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) +UserInfoVCCredential::valid_for(const SignaturePublicKey& pub) const +{ + const auto& vc_pub = _vc->public_key(); + return pub.data == vc_pub.sig.serialize(*vc_pub.key); +} + +bool +UserInfoVCCredential::valid_from(const PublicJWK& pub) const +{ + const auto& sig = _vc->signature_algorithm(); + if (pub.signature_scheme != tls_signature_scheme(sig.id)) { + return false; + } + + const auto sig_pub = sig.deserialize(pub.public_key.data); + return _vc->valid_from(*sig_pub); +} + +tls::ostream +operator<<(tls::ostream& str, const UserInfoVCCredential& obj) +{ + return str << from_ascii(obj.userinfo_vc_jwt); +} + +tls::istream +operator>>(tls::istream& str, UserInfoVCCredential& obj) +{ + auto jwt = bytes{}; + str >> jwt; + obj = UserInfoVCCredential(to_ascii(jwt)); + return str; +} + +bool +operator==(const UserInfoVCCredential& lhs, const UserInfoVCCredential& rhs) +{ + return lhs.userinfo_vc_jwt == rhs.userinfo_vc_jwt; +} + +bool +operator!=(const UserInfoVCCredential& lhs, const UserInfoVCCredential& rhs) +{ + return !(lhs == rhs); +} + +/// +/// CredentialBinding and MultiCredential +/// + +struct CredentialBindingTBS +{ + const CipherSuite& cipher_suite; + const Credential& credential; + const SignaturePublicKey& credential_key; + const SignaturePublicKey& signature_key; + + TLS_SERIALIZABLE(cipher_suite, credential, credential_key, signature_key) +}; + +CredentialBinding::CredentialBinding(CipherSuite cipher_suite_in, + Credential credential_in, + const SignaturePrivateKey& credential_priv, + const SignaturePublicKey& signature_key) + : cipher_suite(cipher_suite_in) + , credential(std::move(credential_in)) + , credential_key(credential_priv.public_key) +{ + if (credential.type() == CredentialType::multi_draft_00) { + throw InvalidParameterError("Multi-credentials cannot be nested"); + } + + if (!credential.valid_for(credential_key)) { + throw InvalidParameterError("Credential key does not match credential"); + } + + signature = credential_priv.sign( + cipher_suite, sign_label::multi_credential, to_be_signed(signature_key)); +} + +bytes +CredentialBinding::to_be_signed(const SignaturePublicKey& signature_key) const +{ + return tls::marshal(CredentialBindingTBS{ + cipher_suite, credential, credential_key, signature_key }); +} + +bool +CredentialBinding::valid_for(const SignaturePublicKey& signature_key) const +{ + auto valid_self = credential.valid_for(credential_key); + auto valid_other = credential_key.verify(cipher_suite, + sign_label::multi_credential, + to_be_signed(signature_key), + signature); + + return valid_self && valid_other; +} + +MultiCredential::MultiCredential( + const std::vector& binding_inputs, + const SignaturePublicKey& signature_key) +{ + bindings = + stdx::transform(binding_inputs, [&](auto&& input) { + return CredentialBinding(input.cipher_suite, + input.credential, + input.credential_priv, + signature_key); + }); +} + +bool +MultiCredential::valid_for(const SignaturePublicKey& pub) const +{ + return stdx::all_of( + bindings, [&](const auto& binding) { return binding.valid_for(pub); }); +} + +/// +/// Credential +/// + +CredentialType +Credential::type() const +{ + return tls::variant::type(_cred); +} + +Credential +Credential::basic(const bytes& identity) +{ + return { BasicCredential{ identity } }; +} + +Credential +Credential::x509(const std::vector& der_chain) +{ + return { X509Credential{ der_chain } }; +} + +Credential +Credential::multi(const std::vector& binding_inputs, + const SignaturePublicKey& signature_key) +{ + return { MultiCredential{ binding_inputs, signature_key } }; +} + +Credential +Credential::userinfo_vc(const std::string& userinfo_vc_jwt) +{ + return { UserInfoVCCredential{ userinfo_vc_jwt } }; +} + +bool +Credential::valid_for(const SignaturePublicKey& pub) const +{ + const auto pub_key_match = overloaded{ + [&](const X509Credential& x509) { return x509.valid_for(pub); }, + [](const BasicCredential& /* basic */) { return true; }, + [&](const UserInfoVCCredential& vc) { return vc.valid_for(pub); }, + [&](const MultiCredential& multi) { return multi.valid_for(pub); }, + }; + + return var::visit(pub_key_match, _cred); +} + +Credential::Credential(SpecificCredential specific) + : _cred(std::move(specific)) +{ +} + +} // namespace mlspp diff --git a/mlspp/src/crypto.cpp b/mlspp/src/crypto.cpp new file mode 100755 index 0000000000..5126036acd --- /dev/null +++ b/mlspp/src/crypto.cpp @@ -0,0 +1,498 @@ +#include +#include +#include + +#include + +using mlspp::hpke::AEAD; // NOLINT(misc-unused-using-decls) +using mlspp::hpke::Digest; // NOLINT(misc-unused-using-decls) +using mlspp::hpke::HPKE; // NOLINT(misc-unused-using-decls) +using mlspp::hpke::KDF; // NOLINT(misc-unused-using-decls) +using mlspp::hpke::KEM; // NOLINT(misc-unused-using-decls) +using mlspp::hpke::Signature; // NOLINT(misc-unused-using-decls) + +namespace mlspp { + +SignatureScheme +tls_signature_scheme(Signature::ID id) +{ + switch (id) { + case Signature::ID::P256_SHA256: + return SignatureScheme::ecdsa_secp256r1_sha256; + case Signature::ID::P384_SHA384: + return SignatureScheme::ecdsa_secp384r1_sha384; + case Signature::ID::P521_SHA512: + return SignatureScheme::ecdsa_secp521r1_sha512; + case Signature::ID::Ed25519: + return SignatureScheme::ed25519; +#if !defined(WITH_BORINGSSL) + case Signature::ID::Ed448: + return SignatureScheme::ed448; +#endif + case Signature::ID::RSA_SHA256: + return SignatureScheme::rsa_pkcs1_sha256; + default: + throw InvalidParameterError("Unsupported algorithm"); + } +} + +/// +/// CipherSuites and details +/// + +CipherSuite::CipherSuite() + : id(ID::unknown) +{ +} + +CipherSuite::CipherSuite(ID id_in) + : id(id_in) +{ +} + +SignatureScheme +CipherSuite::signature_scheme() const +{ + switch (id) { + case ID::X25519_AES128GCM_SHA256_Ed25519: + case ID::X25519_CHACHA20POLY1305_SHA256_Ed25519: + return SignatureScheme::ed25519; + case ID::P256_AES128GCM_SHA256_P256: + return SignatureScheme::ecdsa_secp256r1_sha256; + case ID::X448_AES256GCM_SHA512_Ed448: + case ID::X448_CHACHA20POLY1305_SHA512_Ed448: + return SignatureScheme::ed448; + case ID::P521_AES256GCM_SHA512_P521: + return SignatureScheme::ecdsa_secp521r1_sha512; + case ID::P384_AES256GCM_SHA384_P384: + return SignatureScheme::ecdsa_secp384r1_sha384; + default: + throw InvalidParameterError("Unsupported algorithm"); + } +} + +const CipherSuite::Ciphers& +CipherSuite::get() const +{ + static const auto ciphers_X25519_AES128GCM_SHA256_Ed25519 = + CipherSuite::Ciphers{ + HPKE(KEM::ID::DHKEM_X25519_SHA256, + KDF::ID::HKDF_SHA256, + AEAD::ID::AES_128_GCM), + Digest::get(), + Signature::get(), + }; + + static const auto ciphers_P256_AES128GCM_SHA256_P256 = CipherSuite::Ciphers{ + HPKE( + KEM::ID::DHKEM_P256_SHA256, KDF::ID::HKDF_SHA256, AEAD::ID::AES_128_GCM), + Digest::get(), + Signature::get(), + }; + + static const auto ciphers_X25519_CHACHA20POLY1305_SHA256_Ed25519 = + CipherSuite::Ciphers{ + HPKE(KEM::ID::DHKEM_X25519_SHA256, + KDF::ID::HKDF_SHA256, + AEAD::ID::CHACHA20_POLY1305), + Digest::get(), + Signature::get(), + }; + + static const auto ciphers_P521_AES256GCM_SHA512_P521 = CipherSuite::Ciphers{ + HPKE( + KEM::ID::DHKEM_P521_SHA512, KDF::ID::HKDF_SHA512, AEAD::ID::AES_256_GCM), + Digest::get(), + Signature::get(), + }; + + static const auto ciphers_P384_AES256GCM_SHA384_P384 = CipherSuite::Ciphers{ + HPKE( + KEM::ID::DHKEM_P384_SHA384, KDF::ID::HKDF_SHA384, AEAD::ID::AES_256_GCM), + Digest::get(), + Signature::get(), + }; + +#if !defined(WITH_BORINGSSL) + static const auto ciphers_X448_AES256GCM_SHA512_Ed448 = CipherSuite::Ciphers{ + HPKE( + KEM::ID::DHKEM_X448_SHA512, KDF::ID::HKDF_SHA512, AEAD::ID::AES_256_GCM), + Digest::get(), + Signature::get(), + }; + + static const auto ciphers_X448_CHACHA20POLY1305_SHA512_Ed448 = + CipherSuite::Ciphers{ + HPKE(KEM::ID::DHKEM_X448_SHA512, + KDF::ID::HKDF_SHA512, + AEAD::ID::CHACHA20_POLY1305), + Digest::get(), + Signature::get(), + }; +#endif + + switch (id) { + case ID::unknown: + throw InvalidParameterError("Uninitialized ciphersuite"); + + case ID::X25519_AES128GCM_SHA256_Ed25519: + return ciphers_X25519_AES128GCM_SHA256_Ed25519; + + case ID::P256_AES128GCM_SHA256_P256: + return ciphers_P256_AES128GCM_SHA256_P256; + + case ID::X25519_CHACHA20POLY1305_SHA256_Ed25519: + return ciphers_X25519_CHACHA20POLY1305_SHA256_Ed25519; + + case ID::P521_AES256GCM_SHA512_P521: + return ciphers_P521_AES256GCM_SHA512_P521; + + case ID::P384_AES256GCM_SHA384_P384: + return ciphers_P384_AES256GCM_SHA384_P384; + +#if !defined(WITH_BORINGSSL) + case ID::X448_AES256GCM_SHA512_Ed448: + return ciphers_X448_AES256GCM_SHA512_Ed448; + + case ID::X448_CHACHA20POLY1305_SHA512_Ed448: + return ciphers_X448_CHACHA20POLY1305_SHA512_Ed448; +#endif + + default: + throw InvalidParameterError("Unsupported ciphersuite"); + } +} + +struct HKDFLabel +{ + uint16_t length; + bytes label; + bytes context; + + TLS_SERIALIZABLE(length, label, context) +}; + +bytes +CipherSuite::expand_with_label(const bytes& secret, + const std::string& label, + const bytes& context, + size_t length) const +{ + auto mls_label = from_ascii(std::string("MLS 1.0 ") + label); + auto length16 = static_cast(length); + auto label_bytes = tls::marshal(HKDFLabel{ length16, mls_label, context }); + return get().hpke.kdf.expand(secret, label_bytes, length); +} + +bytes +CipherSuite::derive_secret(const bytes& secret, const std::string& label) const +{ + return expand_with_label(secret, label, {}, secret_size()); +} + +bytes +CipherSuite::derive_tree_secret(const bytes& secret, + const std::string& label, + uint32_t generation, + size_t length) const +{ + return expand_with_label(secret, label, tls::marshal(generation), length); +} + +#if WITH_BORINGSSL +const std::array all_supported_suites = { + CipherSuite::ID::X25519_AES128GCM_SHA256_Ed25519, + CipherSuite::ID::P256_AES128GCM_SHA256_P256, + CipherSuite::ID::X25519_CHACHA20POLY1305_SHA256_Ed25519, + CipherSuite::ID::P521_AES256GCM_SHA512_P521, + CipherSuite::ID::P384_AES256GCM_SHA384_P384, +}; +#else +const std::array all_supported_suites = { + CipherSuite::ID::X25519_AES128GCM_SHA256_Ed25519, + CipherSuite::ID::P256_AES128GCM_SHA256_P256, + CipherSuite::ID::X25519_CHACHA20POLY1305_SHA256_Ed25519, + CipherSuite::ID::P521_AES256GCM_SHA512_P521, + CipherSuite::ID::P384_AES256GCM_SHA384_P384, + CipherSuite::ID::X448_CHACHA20POLY1305_SHA512_Ed448, + CipherSuite::ID::X448_AES256GCM_SHA512_Ed448, +}; +#endif + +// MakeKeyPackageRef(value) = KDF.expand( +// KDF.extract("", value), "MLS 1.0 KeyPackage Reference", 16) +template<> +const bytes& +CipherSuite::reference_label() +{ + static const auto label = from_ascii("MLS 1.0 KeyPackage Reference"); + return label; +} + +// MakeProposalRef(value) = KDF.expand( +// KDF.extract("", value), "MLS 1.0 Proposal Reference", 16) +// +// Even though the label says "Proposal", we actually hash the entire enclosing +// AuthenticatedContent object. +template<> +const bytes& +CipherSuite::reference_label() +{ + static const auto label = from_ascii("MLS 1.0 Proposal Reference"); + return label; +} + +/// +/// HPKEPublicKey and HPKEPrivateKey +/// + +// This function produces a non-literal type, so it can't be constexpr. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define MLS_1_0_PLUS(label) from_ascii("MLS 1.0 " label) + +static bytes +mls_1_0_plus(const std::string& label) +{ + auto plus = "MLS 1.0 "s + label; + return from_ascii(plus); +} + +namespace encrypt_label { +const std::string update_path_node = "UpdatePathNode"; +const std::string welcome = "Welcome"; +} // namespace encrypt_label + +struct EncryptContext +{ + const bytes& label; + const bytes& content; + TLS_SERIALIZABLE(label, content) +}; + +HPKECiphertext +HPKEPublicKey::encrypt(CipherSuite suite, + const std::string& label, + const bytes& context, + const bytes& pt) const +{ + auto label_plus = mls_1_0_plus(label); + auto encrypt_context = tls::marshal(EncryptContext{ label_plus, context }); + auto pkR = suite.hpke().kem.deserialize(data); + auto [enc, ctx] = suite.hpke().setup_base_s(*pkR, encrypt_context); + auto ct = ctx.seal({}, pt); + return HPKECiphertext{ enc, ct }; +} + +std::tuple +HPKEPublicKey::do_export(CipherSuite suite, + const bytes& info, + const std::string& label, + size_t size) const +{ + auto label_data = from_ascii(label); + auto pkR = suite.hpke().kem.deserialize(data); + auto [enc, ctx] = suite.hpke().setup_base_s(*pkR, info); + auto exported = ctx.do_export(label_data, size); + return std::make_tuple(enc, exported); +} + +HPKEPrivateKey +HPKEPrivateKey::generate(CipherSuite suite) +{ + auto priv = suite.hpke().kem.generate_key_pair(); + auto priv_data = suite.hpke().kem.serialize_private(*priv); + auto pub = priv->public_key(); + auto pub_data = suite.hpke().kem.serialize(*pub); + return { priv_data, pub_data }; +} + +HPKEPrivateKey +HPKEPrivateKey::parse(CipherSuite suite, const bytes& data) +{ + auto priv = suite.hpke().kem.deserialize_private(data); + auto pub = priv->public_key(); + auto pub_data = suite.hpke().kem.serialize(*pub); + return { data, pub_data }; +} + +HPKEPrivateKey +HPKEPrivateKey::derive(CipherSuite suite, const bytes& secret) +{ + auto priv = suite.hpke().kem.derive_key_pair(secret); + auto priv_data = suite.hpke().kem.serialize_private(*priv); + auto pub = priv->public_key(); + auto pub_data = suite.hpke().kem.serialize(*pub); + return { priv_data, pub_data }; +} + +bytes +HPKEPrivateKey::decrypt(CipherSuite suite, + const std::string& label, + const bytes& context, + const HPKECiphertext& ct) const +{ + auto label_plus = mls_1_0_plus(label); + auto encrypt_context = tls::marshal(EncryptContext{ label_plus, context }); + auto skR = suite.hpke().kem.deserialize_private(data); + auto ctx = suite.hpke().setup_base_r(ct.kem_output, *skR, encrypt_context); + auto pt = ctx.open({}, ct.ciphertext); + if (!pt) { + throw InvalidParameterError("HPKE decryption failure"); + } + + return opt::get(pt); +} + +bytes +HPKEPrivateKey::do_export(CipherSuite suite, + const bytes& info, + const bytes& kem_output, + const std::string& label, + size_t size) const +{ + auto label_data = from_ascii(label); + auto skR = suite.hpke().kem.deserialize_private(data); + auto ctx = suite.hpke().setup_base_r(kem_output, *skR, info); + return ctx.do_export(label_data, size); +} + +HPKEPrivateKey::HPKEPrivateKey(bytes priv_data, bytes pub_data) + : data(std::move(priv_data)) + , public_key{ std::move(pub_data) } +{ +} + +void +HPKEPrivateKey::set_public_key(CipherSuite suite) +{ + const auto priv = suite.hpke().kem.deserialize_private(data); + auto pub = priv->public_key(); + public_key.data = suite.hpke().kem.serialize(*pub); +} + +/// +/// SignaturePublicKey and SignaturePrivateKey +/// +namespace sign_label { +const std::string mls_content = "FramedContentTBS"; +const std::string leaf_node = "LeafNodeTBS"; +const std::string key_package = "KeyPackageTBS"; +const std::string group_info = "GroupInfoTBS"; +const std::string multi_credential = "MultiCredential"; +} // namespace sign_label + +struct SignContent +{ + const bytes& label; + const bytes& content; + TLS_SERIALIZABLE(label, content) +}; + +bool +SignaturePublicKey::verify(const CipherSuite& suite, + const std::string& label, + const bytes& message, + const bytes& signature) const +{ + auto label_plus = mls_1_0_plus(label); + const auto content = tls::marshal(SignContent{ label_plus, message }); + auto pub = suite.sig().deserialize(data); + return suite.sig().verify(content, signature, *pub); +} + +SignaturePublicKey +SignaturePublicKey::from_jwk(CipherSuite suite, const std::string& json_str) +{ + auto pub = suite.sig().import_jwk(json_str); + auto pub_data = suite.sig().serialize(*pub); + return SignaturePublicKey{ pub_data }; +} + +std::string +SignaturePublicKey::to_jwk(CipherSuite suite) const +{ + auto pub = suite.sig().deserialize(data); + return suite.sig().export_jwk(*pub); +} + +PublicJWK +PublicJWK::parse(const std::string& jwk_json) +{ + const auto parsed = Signature::parse_jwk(jwk_json); + const auto scheme = tls_signature_scheme(parsed.sig.id); + const auto pub_data = parsed.sig.serialize(*parsed.key); + return { scheme, parsed.key_id, { pub_data } }; +} + +SignaturePrivateKey +SignaturePrivateKey::generate(CipherSuite suite) +{ + auto priv = suite.sig().generate_key_pair(); + auto priv_data = suite.sig().serialize_private(*priv); + auto pub = priv->public_key(); + auto pub_data = suite.sig().serialize(*pub); + return { priv_data, pub_data }; +} + +SignaturePrivateKey +SignaturePrivateKey::parse(CipherSuite suite, const bytes& data) +{ + auto priv = suite.sig().deserialize_private(data); + auto pub = priv->public_key(); + auto pub_data = suite.sig().serialize(*pub); + return { data, pub_data }; +} + +SignaturePrivateKey +SignaturePrivateKey::derive(CipherSuite suite, const bytes& secret) +{ + auto priv = suite.sig().derive_key_pair(secret); + auto priv_data = suite.sig().serialize_private(*priv); + auto pub = priv->public_key(); + auto pub_data = suite.sig().serialize(*pub); + return { priv_data, pub_data }; +} + +bytes +SignaturePrivateKey::sign(const CipherSuite& suite, + const std::string& label, + const bytes& message) const +{ + auto label_plus = mls_1_0_plus(label); + const auto content = tls::marshal(SignContent{ label_plus, message }); + const auto priv = suite.sig().deserialize_private(data); + return suite.sig().sign(content, *priv); +} + +SignaturePrivateKey::SignaturePrivateKey(bytes priv_data, bytes pub_data) + : data(std::move(priv_data)) + , public_key{ std::move(pub_data) } +{ +} + +void +SignaturePrivateKey::set_public_key(CipherSuite suite) +{ + const auto priv = suite.sig().deserialize_private(data); + auto pub = priv->public_key(); + public_key.data = suite.sig().serialize(*pub); +} + +SignaturePrivateKey +SignaturePrivateKey::from_jwk(CipherSuite suite, const std::string& json_str) +{ + auto priv = suite.sig().import_jwk_private(json_str); + auto priv_data = suite.sig().serialize_private(*priv); + auto pub = priv->public_key(); + auto pub_data = suite.sig().serialize(*pub); + return { priv_data, pub_data }; +} + +std::string +SignaturePrivateKey::to_jwk(CipherSuite suite) const +{ + const auto priv = suite.sig().deserialize_private(data); + return suite.sig().export_jwk_private(*priv); +} + +} // namespace mlspp diff --git a/mlspp/src/grease.cpp b/mlspp/src/grease.cpp new file mode 100755 index 0000000000..05aaddbe9e --- /dev/null +++ b/mlspp/src/grease.cpp @@ -0,0 +1,126 @@ +#include "grease.h" + +#include +#include + +namespace mlspp { + +#ifdef DISABLE_GREASE + +void +grease([[maybe_unused]] Capabilities& capabilities, + [[maybe_unused]] const ExtensionList& extensions) +{ +} + +void +grease([[maybe_unused]] ExtensionList& extensions) +{ +} + +#else + +// Randomness parmeters: +// * Given a list of N items, insert max(1, rand(p_grease * N)) GREASE values +// * Each GREASE value added is distinct, unless more than 15 values are needed +// * For extensions, each GREASE extension has rand(n_grease_ext) random bytes +// of data +const size_t log_p_grease = 1; // -log2(p_grease) => p_grease = 1/2 +const size_t max_grease_ext_size = 16; + +const std::array grease_values = { 0x0A0A, 0x1A1A, 0x2A2A, 0x3A3A, + 0x4A4A, 0x5A5A, 0x6A6A, 0x7A7A, + 0x8A8A, 0x9A9A, 0xAAAA, 0xBABA, + 0xCACA, 0xDADA, 0xEAEA }; + +static size_t +rand_int(size_t n) +{ + static auto seed = std::random_device()(); + static auto rng = std::mt19937(seed); + return std::uniform_int_distribution(0, n)(rng); +} + +static uint16_t +grease_value() +{ + const auto where = rand_int(grease_values.size() - 1); + return grease_values.at(where); +} + +static bool +grease_value(uint16_t val) +{ + static constexpr auto grease_mask = uint16_t(0x0F0F); + return ((val & grease_mask) == 0x0A0A) && val != 0xFAFA; +} + +static std::set +grease_sample(size_t count) +{ + auto vals = std::set{}; + + while (vals.size() < count) { + uint16_t val = grease_value(); + while (vals.count(val) > 0 && vals.size() < grease_values.size()) { + val = grease_value(); + } + + vals.insert(val); + } + + return vals; +} + +template +static void +grease(std::vector& vec) +{ + const auto count = std::max(size_t(1), rand_int(vec.size() >> log_p_grease)); + for (const auto val : grease_sample(count)) { + const auto where = static_cast(rand_int(vec.size())); + vec.insert(std::begin(vec) + where, static_cast(val)); + } +} + +void +grease(Capabilities& capabilities, const ExtensionList& extensions) +{ + // Add GREASE to the appropriate portions of the capabilities + grease(capabilities.cipher_suites); + grease(capabilities.extensions); + grease(capabilities.proposals); + grease(capabilities.credentials); + + // Ensure that the GREASE extensions are reflected in Capabilities.extensions + for (const auto& ext : extensions.extensions) { + if (!grease_value(ext.type)) { + continue; + } + + if (stdx::contains(capabilities.extensions, ext.type)) { + continue; + } + + const auto where = + static_cast(rand_int(capabilities.extensions.size())); + const auto where_ptr = std::begin(capabilities.extensions) + where; + capabilities.extensions.insert(where_ptr, ext.type); + } +} + +void +grease(ExtensionList& extensions) +{ + auto& ext = extensions.extensions; + const auto count = std::max(size_t(1), rand_int(ext.size() >> log_p_grease)); + for (const auto ext_type : grease_sample(count)) { + const auto where = static_cast(rand_int(ext.size())); + auto ext_data = random_bytes(rand_int(max_grease_ext_size)); + ext.insert(std::begin(ext) + where, { ext_type, std::move(ext_data) }); + } +} + +#endif // DISABLE_GREASE + +} // namespace mlspp diff --git a/mlspp/src/grease.h b/mlspp/src/grease.h new file mode 100755 index 0000000000..94ed157eed --- /dev/null +++ b/mlspp/src/grease.h @@ -0,0 +1,13 @@ +#pragma once + +#include "mls/core_types.h" + +namespace mlspp { + +void +grease(Capabilities& capabilities, const ExtensionList& extensions); + +void +grease(ExtensionList& extensions); + +} // namespace mlspp diff --git a/mlspp/src/key_schedule.cpp b/mlspp/src/key_schedule.cpp new file mode 100755 index 0000000000..7a5788103e --- /dev/null +++ b/mlspp/src/key_schedule.cpp @@ -0,0 +1,579 @@ +#include + +namespace mlspp { + +/// +/// Key Derivation Functions +/// + +struct TreeContext +{ + NodeIndex node; + uint32_t generation = 0; + + TLS_SERIALIZABLE(node, generation) +}; + +/// +/// HashRatchet +/// + +HashRatchet::HashRatchet(CipherSuite suite_in, bytes base_secret_in) + : suite(suite_in) + , next_secret(std::move(base_secret_in)) + , next_generation(0) + , key_size(suite.hpke().aead.key_size) + , nonce_size(suite.hpke().aead.nonce_size) + , secret_size(suite.secret_size()) +{ +} + +std::tuple +HashRatchet::next() +{ + auto generation = next_generation; + auto key = suite.derive_tree_secret(next_secret, "key", generation, key_size); + auto nonce = + suite.derive_tree_secret(next_secret, "nonce", generation, nonce_size); + auto secret = + suite.derive_tree_secret(next_secret, "secret", generation, secret_size); + + next_generation += 1; + next_secret = secret; + + cache[generation] = { key, nonce }; + return { generation, cache.at(generation) }; +} + +// Note: This construction deliberately does not preserve the forward-secrecy +// invariant, in that keys/nonces are not deleted after they are used. +// Otherwise, it would not be possible for a node to send to itself. Keys can +// be deleted once they are not needed by calling HashRatchet::erase(). +KeyAndNonce +HashRatchet::get(uint32_t generation) +{ + if (cache.count(generation) > 0) { + auto out = cache.at(generation); + return out; + } + + if (next_generation > generation) { + throw ProtocolError("Request for expired key"); + } + + while (next_generation <= generation) { + next(); + } + + return cache.at(generation); +} + +void +HashRatchet::erase(uint32_t generation) +{ + if (cache.count(generation) == 0) { + return; + } + + cache.erase(generation); +} + +/// +/// SecretTree +/// + +SecretTree::SecretTree(CipherSuite suite_in, + LeafCount group_size_in, + bytes encryption_secret_in) + : suite(suite_in) + , group_size(LeafCount::full(group_size_in)) + , root(NodeIndex::root(group_size)) + , secret_size(suite_in.secret_size()) +{ + secrets.emplace(root, std::move(encryption_secret_in)); +} + +bytes +SecretTree::get(LeafIndex sender) +{ + static const auto context_left = from_ascii("left"); + static const auto context_right = from_ascii("right"); + auto node = NodeIndex(sender); + + // Find an ancestor that is populated + auto dirpath = node.dirpath(group_size); + dirpath.insert(dirpath.begin(), node); + dirpath.push_back(root); + uint32_t curr = 0; + for (; curr < dirpath.size(); ++curr) { + auto i = dirpath.at(curr); + if (secrets.count(i) > 0) { + break; + } + } + + if (curr > dirpath.size()) { + throw InvalidParameterError("No secret found to derive base key"); + } + + // Derive down + for (; curr > 0; --curr) { + auto curr_node = dirpath.at(curr); + auto left = curr_node.left(); + auto right = curr_node.right(); + + auto& secret = secrets.at(curr_node); + + const auto left_secret = + suite.expand_with_label(secret, "tree", context_left, secret_size); + const auto right_secret = + suite.expand_with_label(secret, "tree", context_right, secret_size); + + secrets.insert_or_assign(left, left_secret); + secrets.insert_or_assign(right, right_secret); + } + + // Copy the leaf + auto out = secrets.at(node); + + // Zeroize along the direct path + for (auto i : dirpath) { + secrets.erase(i); + } + + return out; +} + +/// +/// ReuseGuard +/// + +static ReuseGuard +new_reuse_guard() +{ + auto random = random_bytes(4); + auto guard = ReuseGuard(); + std::copy(random.begin(), random.end(), guard.begin()); + return guard; +} + +static void +apply_reuse_guard(const ReuseGuard& guard, bytes& nonce) +{ + for (size_t i = 0; i < guard.size(); i++) { + nonce.at(i) ^= guard.at(i); + } +} + +/// +/// GroupKeySource +/// + +GroupKeySource::GroupKeySource(CipherSuite suite_in, + LeafCount group_size, + bytes encryption_secret) + : suite(suite_in) + , secret_tree(suite, group_size, std::move(encryption_secret)) +{ +} + +HashRatchet& +GroupKeySource::chain(ContentType type, LeafIndex sender) +{ + switch (type) { + case ContentType::proposal: + case ContentType::commit: + return chain(RatchetType::handshake, sender); + + case ContentType::application: + return chain(RatchetType::application, sender); + + default: + throw InvalidParameterError("Invalid content type"); + } +} + +HashRatchet& +GroupKeySource::chain(RatchetType type, LeafIndex sender) +{ + auto key = Key{ type, sender }; + if (chains.count(key) > 0) { + return chains[key]; + } + + auto secret_size = suite.secret_size(); + auto leaf_secret = secret_tree.get(sender); + + auto handshake_secret = + suite.expand_with_label(leaf_secret, "handshake", {}, secret_size); + auto application_secret = + suite.expand_with_label(leaf_secret, "application", {}, secret_size); + + chains.emplace(Key{ RatchetType::handshake, sender }, + HashRatchet{ suite, handshake_secret }); + chains.emplace(Key{ RatchetType::application, sender }, + HashRatchet{ suite, application_secret }); + + return chains[key]; +} + +std::tuple +GroupKeySource::next(ContentType type, LeafIndex sender) +{ + auto [generation, keys] = chain(type, sender).next(); + + auto reuse_guard = new_reuse_guard(); + apply_reuse_guard(reuse_guard, keys.nonce); + + return { generation, reuse_guard, keys }; +} + +KeyAndNonce +GroupKeySource::get(ContentType type, + LeafIndex sender, + uint32_t generation, + ReuseGuard reuse_guard) +{ + auto keys = chain(type, sender).get(generation); + apply_reuse_guard(reuse_guard, keys.nonce); + return keys; +} + +void +GroupKeySource::erase(ContentType type, LeafIndex sender, uint32_t generation) +{ + return chain(type, sender).erase(generation); +} + +// struct { +// opaque group_id<0..255>; +// uint64 epoch; +// ContentType content_type; +// opaque authenticated_data<0..2^32-1>; +// } ContentAAD; +struct ContentAAD +{ + const bytes& group_id; + const epoch_t epoch; + const ContentType content_type; + const bytes& authenticated_data; + + TLS_SERIALIZABLE(group_id, epoch, content_type, authenticated_data) +}; + +/// +/// KeyScheduleEpoch +/// + +struct PSKLabel +{ + const PreSharedKeyID& id; + uint16_t index; + uint16_t count; + + TLS_SERIALIZABLE(id, index, count); +}; + +static bytes +make_joiner_secret(CipherSuite suite, + const bytes& context, + const bytes& init_secret, + const bytes& commit_secret) +{ + auto pre_joiner_secret = suite.hpke().kdf.extract(init_secret, commit_secret); + return suite.expand_with_label( + pre_joiner_secret, "joiner", context, suite.secret_size()); +} + +static bytes +make_epoch_secret(CipherSuite suite, + const bytes& joiner_secret, + const bytes& psk_secret, + const bytes& context) +{ + auto member_secret = suite.hpke().kdf.extract(joiner_secret, psk_secret); + return suite.expand_with_label( + member_secret, "epoch", context, suite.secret_size()); +} + +KeyScheduleEpoch +KeyScheduleEpoch::joiner(CipherSuite suite_in, + const bytes& joiner_secret, + const std::vector& psks, + const bytes& context) +{ + return { suite_in, joiner_secret, make_psk_secret(suite_in, psks), context }; +} + +KeyScheduleEpoch::KeyScheduleEpoch(CipherSuite suite_in, + const bytes& joiner_secret, + const bytes& psk_secret, + const bytes& context) + : suite(suite_in) + , joiner_secret(joiner_secret) + , epoch_secret( + make_epoch_secret(suite_in, joiner_secret, psk_secret, context)) + , sender_data_secret(suite.derive_secret(epoch_secret, "sender data")) + , encryption_secret(suite.derive_secret(epoch_secret, "encryption")) + , exporter_secret(suite.derive_secret(epoch_secret, "exporter")) + , epoch_authenticator(suite.derive_secret(epoch_secret, "authentication")) + , external_secret(suite.derive_secret(epoch_secret, "external")) + , confirmation_key(suite.derive_secret(epoch_secret, "confirm")) + , membership_key(suite.derive_secret(epoch_secret, "membership")) + , resumption_psk(suite.derive_secret(epoch_secret, "resumption")) + , init_secret(suite.derive_secret(epoch_secret, "init")) + , external_priv(HPKEPrivateKey::derive(suite, external_secret)) +{ +} + +KeyScheduleEpoch::KeyScheduleEpoch(CipherSuite suite_in) + : suite(suite_in) +{ +} + +KeyScheduleEpoch::KeyScheduleEpoch(CipherSuite suite_in, + const bytes& init_secret, + const bytes& context) + : KeyScheduleEpoch( + suite_in, + make_joiner_secret(suite_in, context, init_secret, suite_in.zero()), + { /* no PSKs */ }, + context) +{ +} + +KeyScheduleEpoch::KeyScheduleEpoch(CipherSuite suite_in, + const bytes& init_secret, + const bytes& commit_secret, + const bytes& psk_secret, + const bytes& context) + : KeyScheduleEpoch( + suite_in, + make_joiner_secret(suite_in, context, init_secret, commit_secret), + psk_secret, + context) +{ +} + +std::tuple +KeyScheduleEpoch::external_init(CipherSuite suite, + const HPKEPublicKey& external_pub) +{ + auto size = suite.secret_size(); + return external_pub.do_export( + suite, {}, "MLS 1.0 external init secret", size); +} + +bytes +KeyScheduleEpoch::receive_external_init(const bytes& kem_output) const +{ + auto size = suite.secret_size(); + return external_priv.do_export( + suite, {}, kem_output, "MLS 1.0 external init secret", size); +} + +KeyScheduleEpoch +KeyScheduleEpoch::next(const bytes& commit_secret, + const std::vector& psks, + const std::optional& force_init_secret, + const bytes& context) const +{ + return next_raw( + commit_secret, make_psk_secret(suite, psks), force_init_secret, context); +} + +KeyScheduleEpoch +KeyScheduleEpoch::next_raw(const bytes& commit_secret, + const bytes& psk_secret, + const std::optional& force_init_secret, + const bytes& context) const +{ + auto actual_init_secret = init_secret; + if (force_init_secret) { + actual_init_secret = opt::get(force_init_secret); + } + + return { suite, actual_init_secret, commit_secret, psk_secret, context }; +} + +GroupKeySource +KeyScheduleEpoch::encryption_keys(LeafCount size) const +{ + return { suite, size, encryption_secret }; +} + +bytes +KeyScheduleEpoch::confirmation_tag(const bytes& confirmed_transcript_hash) const +{ + return suite.digest().hmac(confirmation_key, confirmed_transcript_hash); +} + +bytes +KeyScheduleEpoch::do_export(const std::string& label, + const bytes& context, + size_t size) const +{ + auto secret = suite.derive_secret(exporter_secret, label); + auto context_hash = suite.digest().hash(context); + return suite.expand_with_label(secret, "exported", context_hash, size); +} + +PSKWithSecret +KeyScheduleEpoch::resumption_psk_w_secret(ResumptionPSKUsage usage, + const bytes& group_id, + epoch_t epoch) +{ + auto nonce = random_bytes(suite.secret_size()); + auto psk = ResumptionPSK{ usage, group_id, epoch }; + return { { psk, nonce }, resumption_psk }; +} + +bytes +KeyScheduleEpoch::make_psk_secret(CipherSuite suite, + const std::vector& psks) +{ + auto psk_secret = suite.zero(); + auto count = uint16_t(psks.size()); + auto index = uint16_t(0); + for (const auto& psk : psks) { + auto psk_extracted = suite.hpke().kdf.extract(suite.zero(), psk.secret); + auto psk_label = tls::marshal(PSKLabel{ psk.id, index, count }); + auto psk_input = suite.expand_with_label( + psk_extracted, "derived psk", psk_label, suite.secret_size()); + psk_secret = suite.hpke().kdf.extract(psk_input, psk_secret); + index += 1; + } + return psk_secret; +} + +bytes +KeyScheduleEpoch::welcome_secret(CipherSuite suite, + const bytes& joiner_secret, + const std::vector& psks) +{ + auto psk_secret = make_psk_secret(suite, psks); + return welcome_secret_raw(suite, joiner_secret, psk_secret); +} + +bytes +KeyScheduleEpoch::welcome_secret_raw(CipherSuite suite, + const bytes& joiner_secret, + const bytes& psk_secret) +{ + auto extract = suite.hpke().kdf.extract(joiner_secret, psk_secret); + return suite.derive_secret(extract, "welcome"); +} + +KeyAndNonce +KeyScheduleEpoch::sender_data_keys(CipherSuite suite, + const bytes& sender_data_secret, + const bytes& ciphertext) +{ + auto sample_size = suite.secret_size(); + auto sample = bytes(sample_size); + if (ciphertext.size() <= sample_size) { + sample = ciphertext; + } else { + sample = ciphertext.slice(0, sample_size); + } + + auto key_size = suite.hpke().aead.key_size; + auto nonce_size = suite.hpke().aead.nonce_size; + return { + suite.expand_with_label(sender_data_secret, "key", sample, key_size), + suite.expand_with_label(sender_data_secret, "nonce", sample, nonce_size), + }; +} + +bool +operator==(const KeyScheduleEpoch& lhs, const KeyScheduleEpoch& rhs) +{ + auto epoch_secret = (lhs.epoch_secret == rhs.epoch_secret); + auto sender_data_secret = (lhs.sender_data_secret == rhs.sender_data_secret); + auto encryption_secret = (lhs.encryption_secret == rhs.encryption_secret); + auto exporter_secret = (lhs.exporter_secret == rhs.exporter_secret); + auto confirmation_key = (lhs.confirmation_key == rhs.confirmation_key); + auto init_secret = (lhs.init_secret == rhs.init_secret); + auto external_priv = (lhs.external_priv == rhs.external_priv); + + return epoch_secret && sender_data_secret && encryption_secret && + exporter_secret && confirmation_key && init_secret && external_priv; +} + +// struct { +// WireFormat wire_format; +// GroupContent content; // with content.content_type == commit +// opaque signature; +// } ConfirmedTranscriptHashInput; +struct ConfirmedTranscriptHashInput +{ + WireFormat wire_format; + const GroupContent& content; + const bytes& signature; + + TLS_SERIALIZABLE(wire_format, content, signature) +}; + +// struct { +// MAC confirmation_tag; +// } InterimTranscriptHashInput; +struct InterimTranscriptHashInput +{ + bytes confirmation_tag; + + TLS_SERIALIZABLE(confirmation_tag) +}; + +TranscriptHash::TranscriptHash(CipherSuite suite_in) + : suite(suite_in) +{ +} + +TranscriptHash::TranscriptHash(CipherSuite suite_in, + bytes confirmed_in, + const bytes& confirmation_tag) + : suite(suite_in) + , confirmed(std::move(confirmed_in)) +{ + update_interim(confirmation_tag); +} + +void +TranscriptHash::update(const AuthenticatedContent& content_auth) +{ + update_confirmed(content_auth); + update_interim(content_auth); +} + +void +TranscriptHash::update_confirmed(const AuthenticatedContent& content_auth) +{ + const auto transcript = + interim + content_auth.confirmed_transcript_hash_input(); + confirmed = suite.digest().hash(transcript); +} + +void +TranscriptHash::update_interim(const bytes& confirmation_tag) +{ + const auto transcript = confirmed + tls::marshal(confirmation_tag); + interim = suite.digest().hash(transcript); +} + +void +TranscriptHash::update_interim(const AuthenticatedContent& content_auth) +{ + const auto transcript = + confirmed + content_auth.interim_transcript_hash_input(); + interim = suite.digest().hash(transcript); +} + +bool +operator==(const TranscriptHash& lhs, const TranscriptHash& rhs) +{ + auto confirmed = (lhs.confirmed == rhs.confirmed); + auto interim = (lhs.interim == rhs.interim); + return confirmed && interim; +} + +} // namespace mlspp diff --git a/mlspp/src/messages.cpp b/mlspp/src/messages.cpp new file mode 100755 index 0000000000..5372ea5236 --- /dev/null +++ b/mlspp/src/messages.cpp @@ -0,0 +1,947 @@ +#include +#include +#include +#include + +#include "grease.h" + +namespace mlspp { + +// Extensions + +const Extension::Type ExternalPubExtension::type = ExtensionType::external_pub; +const Extension::Type RatchetTreeExtension::type = ExtensionType::ratchet_tree; +const Extension::Type ExternalSendersExtension::type = + ExtensionType::external_senders; +const Extension::Type SFrameParameters::type = ExtensionType::sframe_parameters; +const Extension::Type SFrameCapabilities::type = + ExtensionType::sframe_parameters; + +bool +SFrameCapabilities::compatible(const SFrameParameters& params) const +{ + return stdx::contains(cipher_suites, params.cipher_suite); +} + +// GroupContext + +GroupContext::GroupContext(CipherSuite cipher_suite_in, + bytes group_id_in, + epoch_t epoch_in, + bytes tree_hash_in, + bytes confirmed_transcript_hash_in, + ExtensionList extensions_in) + : cipher_suite(cipher_suite_in) + , group_id(std::move(group_id_in)) + , epoch(epoch_in) + , tree_hash(std::move(tree_hash_in)) + , confirmed_transcript_hash(std::move(confirmed_transcript_hash_in)) + , extensions(std::move(extensions_in)) +{ +} + +// GroupInfo + +GroupInfo::GroupInfo(GroupContext group_context_in, + ExtensionList extensions_in, + bytes confirmation_tag_in) + : group_context(std::move(group_context_in)) + , extensions(std::move(extensions_in)) + , confirmation_tag(std::move(confirmation_tag_in)) + , signer(0) +{ + grease(extensions); +} + +struct GroupInfoTBS +{ + GroupContext group_context; + ExtensionList extensions; + bytes confirmation_tag; + LeafIndex signer; + + TLS_SERIALIZABLE(group_context, extensions, confirmation_tag, signer) +}; + +bytes +GroupInfo::to_be_signed() const +{ + return tls::marshal( + GroupInfoTBS{ group_context, extensions, confirmation_tag, signer }); +} + +void +GroupInfo::sign(const TreeKEMPublicKey& tree, + LeafIndex signer_index, + const SignaturePrivateKey& priv) +{ + auto maybe_leaf = tree.leaf_node(signer_index); + if (!maybe_leaf) { + throw InvalidParameterError("Cannot sign from a blank leaf"); + } + + if (priv.public_key != opt::get(maybe_leaf).signature_key) { + throw InvalidParameterError("Bad key for index"); + } + + signer = signer_index; + signature = priv.sign(tree.suite, sign_label::group_info, to_be_signed()); +} + +bool +GroupInfo::verify(const TreeKEMPublicKey& tree) const +{ + auto maybe_leaf = tree.leaf_node(signer); + if (!maybe_leaf) { + throw InvalidParameterError("Signer not found"); + } + + const auto& leaf = opt::get(maybe_leaf); + return verify(leaf.signature_key); +} + +void +GroupInfo::sign(LeafIndex signer_index, const SignaturePrivateKey& priv) +{ + signer = signer_index; + signature = priv.sign( + group_context.cipher_suite, sign_label::group_info, to_be_signed()); +} + +bool +GroupInfo::verify(const SignaturePublicKey& pub) const +{ + return pub.verify(group_context.cipher_suite, + sign_label::group_info, + to_be_signed(), + signature); +} + +// Welcome + +Welcome::Welcome() + : cipher_suite(CipherSuite::ID::unknown) +{ +} + +Welcome::Welcome(CipherSuite suite, + const bytes& joiner_secret, + const std::vector& psks, + const GroupInfo& group_info) + : cipher_suite(suite) + , _joiner_secret(joiner_secret) +{ + // Cache the list of PSK IDs + for (const auto& psk : psks) { + _psks.psks.push_back(psk.id); + } + + // Pre-encrypt the GroupInfo + auto [key, nonce] = group_info_key_nonce(suite, joiner_secret, psks); + auto group_info_data = tls::marshal(group_info); + encrypted_group_info = + cipher_suite.hpke().aead.seal(key, nonce, {}, group_info_data); +} + +std::optional +Welcome::find(const KeyPackage& kp) const +{ + auto ref = kp.ref(); + for (size_t i = 0; i < secrets.size(); i++) { + if (ref == secrets[i].new_member) { + return static_cast(i); + } + } + return std::nullopt; +} + +void +Welcome::encrypt(const KeyPackage& kp, const std::optional& path_secret) +{ + auto gs = GroupSecrets{ _joiner_secret, std::nullopt, _psks }; + if (path_secret) { + gs.path_secret = GroupSecrets::PathSecret{ opt::get(path_secret) }; + } + + auto gs_data = tls::marshal(gs); + auto enc_gs = kp.init_key.encrypt( + kp.cipher_suite, encrypt_label::welcome, encrypted_group_info, gs_data); + secrets.push_back({ kp.ref(), enc_gs }); +} + +GroupSecrets +Welcome::decrypt_secrets(int kp_index, const HPKEPrivateKey& init_priv) const +{ + auto secrets_data = + init_priv.decrypt(cipher_suite, + encrypt_label::welcome, + encrypted_group_info, + secrets.at(kp_index).encrypted_group_secrets); + return tls::get(secrets_data); +} + +GroupInfo +Welcome::decrypt(const bytes& joiner_secret, + const std::vector& psks) const +{ + auto [key, nonce] = group_info_key_nonce(cipher_suite, joiner_secret, psks); + auto group_info_data = + cipher_suite.hpke().aead.open(key, nonce, {}, encrypted_group_info); + if (!group_info_data) { + throw ProtocolError("Welcome decryption failed"); + } + + return tls::get(opt::get(group_info_data)); +} + +KeyAndNonce +Welcome::group_info_key_nonce(CipherSuite suite, + const bytes& joiner_secret, + const std::vector& psks) +{ + auto welcome_secret = + KeyScheduleEpoch::welcome_secret(suite, joiner_secret, psks); + + // XXX(RLB): These used to be done with ExpandWithLabel. Should we do that + // instead, for better domain separation? (In particular, including "mls10") + // That is what we do for the sender data key/nonce. + auto key = + suite.expand_with_label(welcome_secret, "key", {}, suite.key_size()); + auto nonce = + suite.expand_with_label(welcome_secret, "nonce", {}, suite.nonce_size()); + return { std::move(key), std::move(nonce) }; +} + +// Commit +std::optional +Commit::valid_external() const +{ + // External Commits MUST contain a path field (and is therefore a "full" + // Commit). The joiner is added at the leftmost free leaf node (just as if + // they were added with an Add proposal), and the path is calculated relative + // to that leaf node. + // + // The Commit MUST NOT include any proposals by reference, since an external + // joiner cannot determine the validity of proposals sent within the group + const auto all_by_value = stdx::all_of(proposals, [](const auto& p) { + return var::holds_alternative(p.content); + }); + if (!path || !all_by_value) { + return std::nullopt; + } + + const auto ext_init_ptr = stdx::find_if(proposals, [](const auto& p) { + const auto proposal = var::get(p.content); + return proposal.proposal_type() == ProposalType::external_init; + }); + if (ext_init_ptr == proposals.end()) { + return std::nullopt; + } + + const auto& ext_init_proposal = var::get(ext_init_ptr->content); + const auto& ext_init = var::get(ext_init_proposal.content); + return ext_init.kem_output; +} + +// PublicMessage +Proposal::Type +Proposal::proposal_type() const +{ + return tls::variant::type(content).val; +} + +SenderType +Sender::sender_type() const +{ + return tls::variant::type(sender); +} + +tls::ostream& +operator<<(tls::ostream& str, const GroupContentAuthData& obj) +{ + switch (obj.content_type) { + case ContentType::proposal: + case ContentType::application: + return str << obj.signature; + + case ContentType::commit: + return str << obj.signature << opt::get(obj.confirmation_tag); + + default: + throw InvalidParameterError("Invalid content type"); + } +} + +tls::istream& +operator>>(tls::istream& str, GroupContentAuthData& obj) +{ + switch (obj.content_type) { + case ContentType::proposal: + case ContentType::application: + return str >> obj.signature; + + case ContentType::commit: + obj.confirmation_tag.emplace(); + return str >> obj.signature >> opt::get(obj.confirmation_tag); + + default: + throw InvalidParameterError("Invalid content type"); + } +} + +bool +operator==(const GroupContentAuthData& lhs, const GroupContentAuthData& rhs) +{ + return lhs.content_type == rhs.content_type && + lhs.signature == rhs.signature && + lhs.confirmation_tag == rhs.confirmation_tag; +} + +GroupContent::GroupContent(bytes group_id_in, + epoch_t epoch_in, + Sender sender_in, + bytes authenticated_data_in, + RawContent content_in) + : group_id(std::move(group_id_in)) + , epoch(epoch_in) + , sender(sender_in) + , authenticated_data(std::move(authenticated_data_in)) + , content(std::move(content_in)) +{ +} + +GroupContent::GroupContent(bytes group_id_in, + epoch_t epoch_in, + Sender sender_in, + bytes authenticated_data_in, + ContentType content_type) + : group_id(std::move(group_id_in)) + , epoch(epoch_in) + , sender(sender_in) + , authenticated_data(std::move(authenticated_data_in)) +{ + switch (content_type) { + case ContentType::commit: + content.emplace(); + break; + + case ContentType::proposal: + content.emplace(); + break; + + case ContentType::application: + content.emplace(); + break; + + default: + throw InvalidParameterError("Invalid content type"); + } +} + +ContentType +GroupContent::content_type() const +{ + return tls::variant::type(content); +} + +AuthenticatedContent +AuthenticatedContent::sign(WireFormat wire_format, + GroupContent content, + CipherSuite suite, + const SignaturePrivateKey& sig_priv, + const std::optional& context) +{ + if (wire_format == WireFormat::mls_public_message && + content.content_type() == ContentType::application) { + throw InvalidParameterError( + "Application data cannot be sent as PublicMessage"); + } + + auto content_auth = AuthenticatedContent{ wire_format, std::move(content) }; + auto tbs = content_auth.to_be_signed(context); + content_auth.auth.signature = + sig_priv.sign(suite, sign_label::mls_content, tbs); + return content_auth; +} + +bool +AuthenticatedContent::verify(CipherSuite suite, + const SignaturePublicKey& sig_pub, + const std::optional& context) const +{ + if (wire_format == WireFormat::mls_public_message && + content.content_type() == ContentType::application) { + return false; + } + + auto tbs = to_be_signed(context); + return sig_pub.verify(suite, sign_label::mls_content, tbs, auth.signature); +} + +struct ConfirmedTranscriptHashInput +{ + WireFormat wire_format; + const GroupContent& content; + const bytes& signature; + + TLS_SERIALIZABLE(wire_format, content, signature); +}; + +struct InterimTranscriptHashInput +{ + const bytes& confirmation_tag; + + TLS_SERIALIZABLE(confirmation_tag); +}; + +bytes +AuthenticatedContent::confirmed_transcript_hash_input() const +{ + return tls::marshal(ConfirmedTranscriptHashInput{ + wire_format, + content, + auth.signature, + }); +} + +bytes +AuthenticatedContent::interim_transcript_hash_input() const +{ + return tls::marshal( + InterimTranscriptHashInput{ opt::get(auth.confirmation_tag) }); +} + +void +AuthenticatedContent::set_confirmation_tag(const bytes& confirmation_tag) +{ + auth.confirmation_tag = confirmation_tag; +} + +bool +AuthenticatedContent::check_confirmation_tag( + const bytes& confirmation_tag) const +{ + return confirmation_tag == opt::get(auth.confirmation_tag); +} + +tls::ostream& +operator<<(tls::ostream& str, const AuthenticatedContent& obj) +{ + return str << obj.wire_format << obj.content << obj.auth; +} + +tls::istream& +operator>>(tls::istream& str, AuthenticatedContent& obj) +{ + str >> obj.wire_format >> obj.content; + + obj.auth.content_type = obj.content.content_type(); + return str >> obj.auth; +} + +bool +operator==(const AuthenticatedContent& lhs, const AuthenticatedContent& rhs) +{ + return lhs.wire_format == rhs.wire_format && lhs.content == rhs.content && + lhs.auth == rhs.auth; +} + +AuthenticatedContent::AuthenticatedContent(WireFormat wire_format_in, + GroupContent content_in) + : wire_format(wire_format_in) + , content(std::move(content_in)) +{ + auth.content_type = content.content_type(); +} + +AuthenticatedContent::AuthenticatedContent(WireFormat wire_format_in, + GroupContent content_in, + GroupContentAuthData auth_in) + : wire_format(wire_format_in) + , content(std::move(content_in)) + , auth(std::move(auth_in)) +{ +} + +const AuthenticatedContent& +ValidatedContent::authenticated_content() const +{ + return content_auth; +} + +ValidatedContent::ValidatedContent(AuthenticatedContent content_auth_in) + : content_auth(std::move(content_auth_in)) +{ +} + +bool +operator==(const ValidatedContent& lhs, const ValidatedContent& rhs) +{ + return lhs.content_auth == rhs.content_auth; +} + +struct GroupContentTBS +{ + WireFormat wire_format = WireFormat::reserved; + const GroupContent& content; + const std::optional& context; +}; + +static tls::ostream& +operator<<(tls::ostream& str, const GroupContentTBS& obj) +{ + str << ProtocolVersion::mls10 << obj.wire_format << obj.content; + + switch (obj.content.sender.sender_type()) { + case SenderType::member: + case SenderType::new_member_commit: + str << opt::get(obj.context); + break; + + case SenderType::external: + case SenderType::new_member_proposal: + break; + + default: + throw InvalidParameterError("Invalid sender type"); + } + + return str; +} + +bytes +AuthenticatedContent::to_be_signed( + const std::optional& context) const +{ + return tls::marshal(GroupContentTBS{ + wire_format, + content, + context, + }); +} + +PublicMessage +PublicMessage::protect(AuthenticatedContent content_auth, + CipherSuite suite, + const std::optional& membership_key, + const std::optional& context) +{ + auto pt = PublicMessage(std::move(content_auth)); + + // Add the membership_mac if required + switch (pt.content.sender.sender_type()) { + case SenderType::member: + pt.membership_tag = + pt.membership_mac(suite, opt::get(membership_key), context); + break; + + default: + break; + } + + return pt; +} + +std::optional +PublicMessage::unprotect(CipherSuite suite, + const std::optional& membership_key, + const std::optional& context) const +{ + // Verify the membership_tag if the message was sent within the group + switch (content.sender.sender_type()) { + case SenderType::member: { + auto candidate = membership_mac(suite, opt::get(membership_key), context); + if (candidate != opt::get(membership_tag)) { + return std::nullopt; + } + break; + } + + default: + break; + } + + return { { AuthenticatedContent{ + WireFormat::mls_public_message, + content, + auth, + } } }; +} + +bool +PublicMessage::contains(const AuthenticatedContent& content_auth) const +{ + return content == content_auth.content && auth == content_auth.auth; +} + +AuthenticatedContent +PublicMessage::authenticated_content() const +{ + auto auth_content = AuthenticatedContent{}; + auth_content.wire_format = WireFormat::mls_public_message; + auth_content.content = content; + auth_content.auth = auth; + return auth_content; +} + +PublicMessage::PublicMessage(AuthenticatedContent content_auth) + : content(std::move(content_auth.content)) + , auth(std::move(content_auth.auth)) +{ + if (content_auth.wire_format != WireFormat::mls_public_message) { + throw InvalidParameterError("Wire format mismatch (not mls_plaintext)"); + } +} + +struct GroupContentTBM +{ + GroupContentTBS content_tbs; + GroupContentAuthData auth; + + TLS_SERIALIZABLE(content_tbs, auth); +}; + +bytes +PublicMessage::membership_mac(CipherSuite suite, + const bytes& membership_key, + const std::optional& context) const +{ + auto tbm = tls::marshal(GroupContentTBM{ + { WireFormat::mls_public_message, content, context }, + auth, + }); + + return suite.digest().hmac(membership_key, tbm); +} + +tls::ostream& +operator<<(tls::ostream& str, const PublicMessage& obj) +{ + switch (obj.content.sender.sender_type()) { + case SenderType::member: + return str << obj.content << obj.auth << opt::get(obj.membership_tag); + + case SenderType::external: + case SenderType::new_member_proposal: + case SenderType::new_member_commit: + return str << obj.content << obj.auth; + + default: + throw InvalidParameterError("Invalid sender type"); + } +} + +tls::istream& +operator>>(tls::istream& str, PublicMessage& obj) +{ + str >> obj.content; + + obj.auth.content_type = obj.content.content_type(); + str >> obj.auth; + + if (obj.content.sender.sender_type() == SenderType::member) { + obj.membership_tag.emplace(); + str >> opt::get(obj.membership_tag); + } + + return str; +} + +bool +operator==(const PublicMessage& lhs, const PublicMessage& rhs) +{ + return lhs.content == rhs.content && lhs.auth == rhs.auth && + lhs.membership_tag == rhs.membership_tag; +} + +bool +operator!=(const PublicMessage& lhs, const PublicMessage& rhs) +{ + return !(lhs == rhs); +} + +static bytes +marshal_ciphertext_content(const GroupContent& content, + const GroupContentAuthData& auth, + size_t padding_size) +{ + auto w = tls::ostream{}; + var::visit([&w](const auto& val) { w << val; }, content.content); + w << auth; + w.write_raw(bytes(padding_size, 0)); + return w.bytes(); +} + +static void +unmarshal_ciphertext_content(const bytes& content_pt, + GroupContent& content, + GroupContentAuthData& auth) +{ + auto r = tls::istream(content_pt); + + var::visit([&r](auto& val) { r >> val; }, content.content); + r >> auth; + + const auto padding = r.bytes(); + const auto nonzero = [](const auto& x) { return x != 0; }; + if (stdx::any_of(padding, nonzero)) { + throw ProtocolError("Malformed AuthenticatedContentTBE padding"); + } +} + +struct ContentAAD +{ + const bytes& group_id; + const epoch_t epoch; + const ContentType content_type; + const bytes& authenticated_data; + + TLS_SERIALIZABLE(group_id, epoch, content_type, authenticated_data) +}; + +struct SenderData +{ + LeafIndex sender{ 0 }; + uint32_t generation{ 0 }; + ReuseGuard reuse_guard{ 0, 0, 0, 0 }; + + TLS_SERIALIZABLE(sender, generation, reuse_guard) +}; + +struct SenderDataAAD +{ + const bytes& group_id; + const epoch_t epoch; + const ContentType content_type; + + TLS_SERIALIZABLE(group_id, epoch, content_type) +}; + +PrivateMessage +PrivateMessage::protect(AuthenticatedContent content_auth, + CipherSuite suite, + GroupKeySource& keys, + const bytes& sender_data_secret, + size_t padding_size) +{ + // Pull keys from the secret tree + auto index = + var::get(content_auth.content.sender.sender).sender; + auto content_type = content_auth.content.content_type(); + auto [generation, reuse_guard, content_keys] = keys.next(content_type, index); + + // Encrypt the content + auto content_pt = marshal_ciphertext_content( + content_auth.content, content_auth.auth, padding_size); + auto content_aad = tls::marshal(ContentAAD{ + content_auth.content.group_id, + content_auth.content.epoch, + content_auth.content.content_type(), + content_auth.content.authenticated_data, + }); + + auto content_ct = suite.hpke().aead.seal( + content_keys.key, content_keys.nonce, content_aad, content_pt); + + // Encrypt the sender data + auto sender_index = + var::get(content_auth.content.sender.sender).sender; + auto sender_data_pt = tls::marshal(SenderData{ + sender_index, + generation, + reuse_guard, + }); + auto sender_data_aad = tls::marshal(SenderDataAAD{ + content_auth.content.group_id, + content_auth.content.epoch, + content_auth.content.content_type(), + }); + + auto sender_data_keys = + KeyScheduleEpoch::sender_data_keys(suite, sender_data_secret, content_ct); + + auto sender_data_ct = suite.hpke().aead.seal(sender_data_keys.key, + sender_data_keys.nonce, + sender_data_aad, + sender_data_pt); + + return PrivateMessage{ + std::move(content_auth.content), + std::move(sender_data_ct), + std::move(content_ct), + }; +} + +std::optional +PrivateMessage::unprotect(CipherSuite suite, + GroupKeySource& keys, + const bytes& sender_data_secret) const +{ + // Decrypt and parse the sender data + auto sender_data_keys = + KeyScheduleEpoch::sender_data_keys(suite, sender_data_secret, ciphertext); + auto sender_data_aad = tls::marshal(SenderDataAAD{ + group_id, + epoch, + content_type, + }); + + auto sender_data_pt = suite.hpke().aead.open(sender_data_keys.key, + sender_data_keys.nonce, + sender_data_aad, + encrypted_sender_data); + if (!sender_data_pt) { + return std::nullopt; + } + + auto sender_data = tls::get(opt::get(sender_data_pt)); + if (!keys.has_leaf(sender_data.sender)) { + return std::nullopt; + } + + // Decrypt the content + auto content_keys = keys.get(content_type, + sender_data.sender, + sender_data.generation, + sender_data.reuse_guard); + keys.erase(content_type, sender_data.sender, sender_data.generation); + + auto content_aad = tls::marshal(ContentAAD{ + group_id, + epoch, + content_type, + authenticated_data, + }); + + auto content_pt = suite.hpke().aead.open( + content_keys.key, content_keys.nonce, content_aad, ciphertext); + if (!content_pt) { + return std::nullopt; + } + + // Parse the content + auto content = GroupContent{ group_id, + epoch, + { MemberSender{ sender_data.sender } }, + authenticated_data, + content_type }; + auto auth = GroupContentAuthData{ content_type, {}, {} }; + + unmarshal_ciphertext_content(opt::get(content_pt), content, auth); + + return { { AuthenticatedContent{ + WireFormat::mls_private_message, + std::move(content), + std::move(auth), + } } }; +} + +PrivateMessage::PrivateMessage(GroupContent content, + bytes encrypted_sender_data_in, + bytes ciphertext_in) + : group_id(std::move(content.group_id)) + , epoch(content.epoch) + , content_type(content.content_type()) + , authenticated_data(std::move(content.authenticated_data)) + , encrypted_sender_data(std::move(encrypted_sender_data_in)) + , ciphertext(std::move(ciphertext_in)) +{ +} + +bytes +MLSMessage::group_id() const +{ + return var::visit( + overloaded{ + [](const PublicMessage& pt) -> bytes { return pt.get_group_id(); }, + [](const PrivateMessage& ct) -> bytes { return ct.get_group_id(); }, + [](const GroupInfo& gi) -> bytes { return gi.group_context.group_id; }, + [](const auto& /* unused */) -> bytes { + throw InvalidParameterError("MLSMessage has no group_id"); + }, + }, + message); +} + +epoch_t +MLSMessage::epoch() const +{ + return var::visit( + overloaded{ + [](const PublicMessage& pt) -> epoch_t { return pt.get_epoch(); }, + [](const PrivateMessage& pt) -> epoch_t { return pt.get_epoch(); }, + [](const auto& /* unused */) -> epoch_t { + throw InvalidParameterError("MLSMessage has no epoch"); + }, + }, + message); +} + +WireFormat +MLSMessage::wire_format() const +{ + return tls::variant::type(message); +} + +MLSMessage::MLSMessage(PublicMessage public_message) + : message(std::move(public_message)) +{ +} + +MLSMessage::MLSMessage(PrivateMessage private_message) + : message(std::move(private_message)) +{ +} + +MLSMessage::MLSMessage(Welcome welcome) + : message(std::move(welcome)) +{ +} + +MLSMessage::MLSMessage(GroupInfo group_info) + : message(std::move(group_info)) +{ +} + +MLSMessage::MLSMessage(KeyPackage key_package) + : message(std::move(key_package)) +{ +} + +MLSMessage +external_proposal(CipherSuite suite, + const bytes& group_id, + epoch_t epoch, + const Proposal& proposal, + uint32_t signer_index, + const SignaturePrivateKey& sig_priv) +{ + switch (proposal.proposal_type()) { + // These proposal types are OK + case ProposalType::add: + case ProposalType::remove: + case ProposalType::psk: + case ProposalType::reinit: + case ProposalType::group_context_extensions: + break; + + // These proposal types are forbidden + case ProposalType::invalid: + case ProposalType::update: + case ProposalType::external_init: + default: + throw ProtocolError("External proposal has invalid type"); + } + + auto content = GroupContent{ group_id, + epoch, + { ExternalSenderIndex{ signer_index } }, + { /* no authenticated data */ }, + { proposal } }; + auto content_auth = AuthenticatedContent::sign( + WireFormat::mls_public_message, std::move(content), suite, sig_priv, {}); + + return PublicMessage::protect(std::move(content_auth), suite, {}, {}); +} + +} // namespace mlspp diff --git a/mlspp/src/session.cpp b/mlspp/src/session.cpp new file mode 100755 index 0000000000..f96b6bba63 --- /dev/null +++ b/mlspp/src/session.cpp @@ -0,0 +1,437 @@ +#include +#include + +#include + +namespace mlspp { + +/// +/// Inner struct declarations for PendingJoin and Session +/// + +struct PendingJoin::Inner +{ + const CipherSuite suite; + const HPKEPrivateKey init_priv; + const HPKEPrivateKey leaf_priv; + const SignaturePrivateKey sig_priv; + const KeyPackage key_package; + + Inner(CipherSuite suite_in, + SignaturePrivateKey sig_priv_in, + Credential cred_in); + + static PendingJoin create(CipherSuite suite, + SignaturePrivateKey sig_priv, + Credential cred); +}; + +struct Session::Inner +{ + std::deque history; + std::map outbound_cache; + bool encrypt_handshake{ false }; + + explicit Inner(State state); + + static Session begin(CipherSuite suite, + const bytes& group_id, + const HPKEPrivateKey& leaf_priv, + const SignaturePrivateKey& sig_priv, + const LeafNode& leaf_node); + static Session join(const HPKEPrivateKey& init_priv, + const HPKEPrivateKey& leaf_priv, + const SignaturePrivateKey& sig_priv, + const KeyPackage& key_package, + const bytes& welcome_data); + + bytes fresh_secret() const; + MLSMessage import_handshake(const bytes& encoded) const; + State& for_epoch(epoch_t epoch); +}; + +/// +/// Client +/// + +Client::Client(CipherSuite suite_in, + SignaturePrivateKey sig_priv_in, + Credential cred_in) + : suite(suite_in) + , sig_priv(std::move(sig_priv_in)) + , cred(std::move(cred_in)) +{ +} + +Session +Client::begin_session(const bytes& group_id) const +{ + auto leaf_priv = HPKEPrivateKey::generate(suite); + auto leaf_node = LeafNode(suite, + leaf_priv.public_key, + sig_priv.public_key, + cred, + Capabilities::create_default(), + Lifetime::create_default(), + {}, + sig_priv); + + return Session::Inner::begin(suite, group_id, leaf_priv, sig_priv, leaf_node); +} + +PendingJoin +Client::start_join() const +{ + return PendingJoin::Inner::create(suite, sig_priv, cred); +} + +/// +/// PendingJoin +/// + +PendingJoin::Inner::Inner(CipherSuite suite_in, + SignaturePrivateKey sig_priv_in, + Credential cred_in) + : suite(suite_in) + , init_priv(HPKEPrivateKey::generate(suite)) + , leaf_priv(HPKEPrivateKey::generate(suite)) + , sig_priv(std::move(sig_priv_in)) + , key_package(suite, + init_priv.public_key, + LeafNode(suite, + leaf_priv.public_key, + sig_priv.public_key, + std::move(cred_in), + Capabilities::create_default(), + Lifetime::create_default(), + {}, + sig_priv), + {}, + sig_priv) +{ +} + +PendingJoin +PendingJoin::Inner::create(CipherSuite suite, + SignaturePrivateKey sig_priv, + Credential cred) +{ + auto inner = + std::make_unique(suite, std::move(sig_priv), std::move(cred)); + return { inner.release() }; +} + +PendingJoin::PendingJoin(PendingJoin&& other) noexcept = default; + +PendingJoin& +PendingJoin::operator=(PendingJoin&& other) noexcept = default; + +PendingJoin::~PendingJoin() = default; + +PendingJoin::PendingJoin(Inner* inner_in) + : inner(inner_in) +{ +} + +bytes +PendingJoin::key_package() const +{ + return tls::marshal(inner->key_package); +} + +Session +PendingJoin::complete(const bytes& welcome) const +{ + return Session::Inner::join(inner->init_priv, + inner->leaf_priv, + inner->sig_priv, + inner->key_package, + welcome); +} + +/// +/// Session +/// + +Session::Inner::Inner(State state) + : history{ std::move(state) } + , encrypt_handshake(true) +{ +} + +Session +Session::Inner::begin(CipherSuite suite, + const bytes& group_id, + const HPKEPrivateKey& leaf_priv, + const SignaturePrivateKey& sig_priv, + const LeafNode& leaf_node) +{ + auto state = State(group_id, suite, leaf_priv, sig_priv, leaf_node, {}); + auto inner = std::make_unique(state); + return { inner.release() }; +} + +Session +Session::Inner::join(const HPKEPrivateKey& init_priv, + const HPKEPrivateKey& leaf_priv, + const SignaturePrivateKey& sig_priv, + const KeyPackage& key_package, + const bytes& welcome_data) +{ + auto welcome = tls::get(welcome_data); + + auto state = State( + init_priv, leaf_priv, sig_priv, key_package, welcome, std::nullopt, {}); + auto inner = std::make_unique(state); + return { inner.release() }; +} + +bytes +Session::Inner::fresh_secret() const +{ + const auto suite = history.front().cipher_suite(); + return random_bytes(suite.secret_size()); +} + +MLSMessage +Session::Inner::import_handshake(const bytes& encoded) const +{ + auto msg = tls::get(encoded); + + switch (msg.wire_format()) { + case WireFormat::mls_public_message: + if (encrypt_handshake) { + throw ProtocolError("Handshake not encrypted as required"); + } + + return msg; + + case WireFormat::mls_private_message: { + if (!encrypt_handshake) { + throw ProtocolError("Unexpected handshake encryption"); + } + + return msg; + } + + default: + throw InvalidParameterError("Illegal wire format"); + } +} + +State& +Session::Inner::for_epoch(epoch_t epoch) +{ + for (auto& state : history) { + if (state.epoch() == epoch) { + return state; + } + } + + throw MissingStateError("No state for epoch"); +} + +Session::Session(Session&& other) noexcept = default; + +Session& +Session::operator=(Session&& other) noexcept = default; + +Session::~Session() = default; + +Session::Session(Inner* inner_in) + : inner(inner_in) +{ +} + +void +Session::encrypt_handshake(bool enabled) +{ + inner->encrypt_handshake = enabled; +} + +bytes +Session::add(const bytes& key_package_data) +{ + auto key_package = tls::get(key_package_data); + auto proposal = inner->history.front().add( + key_package, { inner->encrypt_handshake, {}, 0 }); + return tls::marshal(proposal); +} + +bytes +Session::update() +{ + auto leaf_secret = inner->fresh_secret(); + + auto leaf_priv = HPKEPrivateKey::generate(cipher_suite()); + auto proposal = inner->history.front().update( + std::move(leaf_priv), {}, { inner->encrypt_handshake, {}, 0 }); + return tls::marshal(proposal); +} + +bytes +Session::remove(uint32_t index) +{ + auto proposal = inner->history.front().remove( + RosterIndex{ index }, { inner->encrypt_handshake, {}, 0 }); + return tls::marshal(proposal); +} + +std::tuple +Session::commit(const bytes& proposal) +{ + return commit(std::vector{ proposal }); +} + +std::tuple +Session::commit(const std::vector& proposals) +{ + auto provisional_state = inner->history.front(); + for (const auto& proposal_data : proposals) { + auto msg = inner->import_handshake(proposal_data); + auto maybe_state = provisional_state.handle(msg); + if (maybe_state) { + throw InvalidParameterError("Invalid proposal; actually a commit"); + } + } + + inner->history.front() = std::move(provisional_state); + return commit(); +} + +std::tuple +Session::commit() +{ + auto commit_secret = inner->fresh_secret(); + auto encrypt = inner->encrypt_handshake; + auto [commit, welcome, new_state] = inner->history.front().commit( + commit_secret, CommitOpts{ {}, true, encrypt, {} }, { encrypt, {}, 0 }); + + auto commit_msg = tls::marshal(commit); + auto welcome_msg = tls::marshal(welcome); + + inner->outbound_cache.insert({ commit_msg, new_state }); + return std::make_tuple(welcome_msg, commit_msg); +} + +bool +Session::handle(const bytes& handshake_data) +{ + auto msg = inner->import_handshake(handshake_data); + + auto maybe_cached_state = std::optional{}; + auto node = inner->outbound_cache.extract(handshake_data); + if (!node.empty()) { + maybe_cached_state = node.mapped(); + } + + auto maybe_next_state = + inner->history.front().handle(msg, maybe_cached_state); + if (!maybe_next_state) { + return false; + } + + inner->history.emplace_front(opt::get(maybe_next_state)); + return true; +} + +epoch_t +Session::epoch() const +{ + return inner->history.front().epoch(); +} + +LeafIndex +Session::index() const +{ + return inner->history.front().index(); +} + +CipherSuite +Session::cipher_suite() const +{ + return inner->history.front().cipher_suite(); +} + +const ExtensionList& +Session::extensions() const +{ + return inner->history.front().extensions(); +} + +const TreeKEMPublicKey& +Session::tree() const +{ + return inner->history.front().tree(); +} + +bytes +Session::do_export(const std::string& label, + const bytes& context, + size_t size) const +{ + return inner->history.front().do_export(label, context, size); +} + +GroupInfo +Session::group_info() const +{ + return inner->history.front().group_info(true); +} + +std::vector +Session::roster() const +{ + return inner->history.front().roster(); +} + +bytes +Session::epoch_authenticator() const +{ + return inner->history.front().epoch_authenticator(); +} + +bytes +Session::protect(const bytes& plaintext) +{ + auto msg = inner->history.front().protect({}, plaintext, 0); + return tls::marshal(msg); +} + +// TODO(rlb@ipv.sx): It would be good to expose identity information +// here, since ciphertexts are authenticated per sender. Who sent +// this ciphertext? +bytes +Session::unprotect(const bytes& ciphertext) +{ + auto ciphertext_obj = tls::get(ciphertext); + auto& state = inner->for_epoch(ciphertext_obj.epoch()); + auto [aad, pt] = state.unprotect(ciphertext_obj); + silence_unused(aad); + return pt; +} + +bool +operator==(const Session& lhs, const Session& rhs) +{ + if (lhs.inner->encrypt_handshake != rhs.inner->encrypt_handshake) { + return false; + } + + auto size = std::min(lhs.inner->history.size(), rhs.inner->history.size()); + for (size_t i = 0; i < size; i += 1) { + if (lhs.inner->history.at(i) != rhs.inner->history.at(i)) { + return false; + } + } + + return true; +} + +bool +operator!=(const Session& lhs, const Session& rhs) +{ + return !(lhs == rhs); +} + +} // namespace mlspp diff --git a/mlspp/src/state.cpp b/mlspp/src/state.cpp new file mode 100755 index 0000000000..ed15c1e9eb --- /dev/null +++ b/mlspp/src/state.cpp @@ -0,0 +1,2219 @@ +#include +#include + +namespace mlspp { + +/// +/// Constructors +/// + +State::State(bytes group_id, + CipherSuite suite, + HPKEPrivateKey enc_priv, + SignaturePrivateKey sig_priv, + const LeafNode& leaf_node, + ExtensionList extensions) + : _suite(suite) + , _group_id(std::move(group_id)) + , _epoch(0) + , _tree(suite) + , _transcript_hash(suite) + , _extensions(std::move(extensions)) + , _index(0) + , _identity_priv(std::move(sig_priv)) +{ + // Verify that the client supports the proposed group extensions + if (!leaf_node.verify_extension_support(_extensions)) { + throw InvalidParameterError("Client doesn't support required extensions"); + } + + _index = _tree.add_leaf(leaf_node); + _tree.set_hash_all(); + _tree_priv = TreeKEMPrivateKey::solo(suite, _index, std::move(enc_priv)); + if (!_tree_priv.consistent(_tree)) { + throw InvalidParameterError("LeafNode inconsistent with private key"); + } + + // XXX(RLB): Convert KeyScheduleEpoch to take GroupContext? + auto ctx = tls::marshal(group_context()); + _key_schedule = + KeyScheduleEpoch(_suite, random_bytes(_suite.secret_size()), ctx); + _keys = _key_schedule.encryption_keys(_tree.size); + + // Update the interim transcript hash with a virtual confirmation tag + _transcript_hash.update_interim( + _key_schedule.confirmation_tag(_transcript_hash.confirmed)); +} + +TreeKEMPublicKey +State::import_tree(const bytes& tree_hash, + const std::optional& external, + const ExtensionList& extensions) +{ + auto tree = TreeKEMPublicKey(_suite); + auto maybe_tree_extn = extensions.find(); + if (external) { + tree = opt::get(external); + } else if (maybe_tree_extn) { + tree = opt::get(maybe_tree_extn).tree; + } else { + throw InvalidParameterError("No tree available"); + } + + tree.suite = _suite; + + tree.set_hash_all(); + if (tree.root_hash() != tree_hash) { + throw InvalidParameterError("Tree does not match GroupInfo"); + } + + return tree; +} + +bool +State::validate_tree() const +{ + // The functionality here is somewhat duplicative of State::valid(const + // LeafNode&). Simply calling that method, however, would result in this + // method having quadratic scaling, since each call to valid() does a linear + // scan through the tree to check uniqueness of keys and compatibility of + // credential support. + + // Validate that the tree is parent-hash valid + if (!_tree.parent_hash_valid()) { + return false; + } + + // Validate the signatures on all leaves + const auto signature_valid = + _tree.all_leaves([&](auto i, const auto& leaf_node) { + auto binding = std::optional{}; + switch (leaf_node.source()) { + case LeafNodeSource::commit: + case LeafNodeSource::update: + binding = LeafNode::MemberBinding{ _group_id, i }; + break; + + default: + // Nothing to do + break; + } + + return leaf_node.verify(_suite, binding); + }); + if (!signature_valid) { + return false; + } + + // Collect cross-tree properties + auto n_leaves = size_t(0); + auto encryption_keys = std::set{}; + auto signature_keys = std::set{}; + auto credential_types = std::set{}; + _tree.all_leaves([&](auto /* i */, const auto& leaf_node) { + n_leaves += 1; + encryption_keys.insert(leaf_node.encryption_key.data); + signature_keys.insert(leaf_node.signature_key.data); + credential_types.insert(leaf_node.credential.type()); + return true; + }); + + // Verify uniqueness of keys + if (encryption_keys.size() != n_leaves) { + return false; + } + + if (signature_keys.size() != n_leaves) { + return false; + } + + // Verify that each leaf indicates support for all required parameters + return _tree.all_leaves([&](auto /* i */, const auto& leaf_node) { + const auto supports_group_extensions = + leaf_node.verify_extension_support(_extensions); + const auto supports_own_extensions = + leaf_node.verify_extension_support(leaf_node.extensions); + const auto supports_group_credentials = + leaf_node.capabilities.credentials_supported(credential_types); + return supports_group_extensions && supports_own_extensions && + supports_group_credentials; + }); +} + +State::State(SignaturePrivateKey sig_priv, + const GroupInfo& group_info, + const std::optional& tree) + : _suite(group_info.group_context.cipher_suite) + , _group_id(group_info.group_context.group_id) + , _epoch(group_info.group_context.epoch) + , _tree(import_tree(group_info.group_context.tree_hash, + tree, + group_info.extensions)) + , _transcript_hash(_suite, + group_info.group_context.confirmed_transcript_hash, + group_info.confirmation_tag) + , _extensions(group_info.group_context.extensions) + , _key_schedule(_suite) + , _index(0) + , _identity_priv(std::move(sig_priv)) +{ + if (!validate_tree()) { + throw InvalidParameterError("Invalid tree"); + } + + // The following are not set: + // _index + // _tree_priv + // + // This ctor should only be used within external_commit, in which case these + // fields are populated by the subsequent commit() +} + +// Initialize a group from a Welcome +State::State(const HPKEPrivateKey& init_priv, + HPKEPrivateKey leaf_priv, + SignaturePrivateKey sig_priv, + const KeyPackage& key_package, + const Welcome& welcome, + const std::optional& tree, + std::map external_psks) + : State(init_priv, + std::move(leaf_priv), + std::move(sig_priv), + key_package, + welcome, + tree, + std::move(external_psks), + {}) +{ +} + +State::State(const HPKEPrivateKey& init_priv, + HPKEPrivateKey leaf_priv, + SignaturePrivateKey sig_priv, + const KeyPackage& key_package, + const Welcome& welcome, + const std::optional& tree, + std::map external_psks, + std::map resumption_psks) + : _suite(welcome.cipher_suite) + , _epoch(0) + , _tree(welcome.cipher_suite) + , _transcript_hash(welcome.cipher_suite) + , _identity_priv(std::move(sig_priv)) + , _external_psks(std::move(external_psks)) + , _resumption_psks(std::move(resumption_psks)) +{ + auto maybe_kpi = welcome.find(key_package); + if (!maybe_kpi) { + throw InvalidParameterError("Welcome not intended for key package"); + } + auto kpi = opt::get(maybe_kpi); + + if (key_package.cipher_suite != welcome.cipher_suite) { + throw InvalidParameterError("Ciphersuite mismatch"); + } + + // Decrypt the GroupSecrets and look up required PSKs + auto secrets = welcome.decrypt_secrets(kpi, init_priv); + auto psks = resolve(secrets.psks.psks); + + // Decrypt the GroupInfo + auto group_info = welcome.decrypt(secrets.joiner_secret, psks); + if (group_info.group_context.cipher_suite != _suite) { + throw InvalidParameterError("GroupInfo and Welcome ciphersuites disagree"); + } + + // Import the tree from the argument or from the extension + _tree = import_tree( + group_info.group_context.tree_hash, tree, group_info.extensions); + + // Verify the signature on the GroupInfo + if (!group_info.verify(_tree)) { + throw InvalidParameterError("Invalid GroupInfo"); + } + + // Ingest the GroupSecrets and GroupInfo + _epoch = group_info.group_context.epoch; + _group_id = group_info.group_context.group_id; + + _transcript_hash.confirmed = + group_info.group_context.confirmed_transcript_hash; + _transcript_hash.update_interim(group_info.confirmation_tag); + + _extensions = group_info.group_context.extensions; + + // Validate that the tree is in fact consistent with the group's parameters + if (!validate_tree()) { + throw InvalidParameterError("Invalid tree"); + } + + // Construct TreeKEM private key from parts provided + auto maybe_index = _tree.find(key_package.leaf_node); + if (!maybe_index) { + throw InvalidParameterError("New joiner not in tree"); + } + + _index = opt::get(maybe_index); + + auto ancestor = _index.ancestor(group_info.signer); + auto path_secret = std::optional{}; + if (secrets.path_secret) { + path_secret = opt::get(secrets.path_secret).secret; + } + + _tree_priv = TreeKEMPrivateKey::joiner( + _tree, _index, std::move(leaf_priv), ancestor, path_secret); + + // Ratchet forward into the current epoch + auto group_ctx = tls::marshal(group_context()); + _key_schedule = + KeyScheduleEpoch::joiner(_suite, secrets.joiner_secret, psks, group_ctx); + _keys = _key_schedule.encryption_keys(_tree.size); + + // Verify the confirmation + const auto confirmation_tag = + _key_schedule.confirmation_tag(_transcript_hash.confirmed); + if (confirmation_tag != group_info.confirmation_tag) { + throw ProtocolError("Confirmation failed to verify"); + } +} + +std::tuple +State::external_join(const bytes& leaf_secret, + SignaturePrivateKey sig_priv, + const KeyPackage& key_package, + const GroupInfo& group_info, + const std::optional& tree, + const MessageOpts& msg_opts, + std::optional remove_prior, + const std::map& psks) +{ + // Create a preliminary state + auto initial_state = State(std::move(sig_priv), group_info, tree); + + // Look up the external public key for the group + const auto maybe_external_pub = + group_info.extensions.find(); + if (!maybe_external_pub) { + throw InvalidParameterError("No external pub in GroupInfo"); + } + + const auto& external_pub = opt::get(maybe_external_pub).external_pub; + + // Insert an ExternalInit proposal + auto opts = CommitOpts{}; + auto [enc, force_init_secret] = + KeyScheduleEpoch::external_init(key_package.cipher_suite, external_pub); + auto ext_init = Proposal{ ExternalInit{ enc } }; + opts.extra_proposals.push_back(ext_init); + + // Evict a prior appearance if required + if (remove_prior) { + auto remove = initial_state.remove_proposal(opt::get(remove_prior)); + opts.extra_proposals.push_back(remove); + } + + // Inject PSKs + for (const auto& [id, secret] : psks) { + initial_state.add_external_psk(id, secret); + auto psk = initial_state.pre_shared_key_proposal(id); + opts.extra_proposals.push_back(psk); + } + + // Use the preliminary state to create a commit and advance to a real state + auto params = ExternalCommitParams{ key_package, force_init_secret }; + auto [commit_msg, welcome, state] = + initial_state.commit(leaf_secret, opts, msg_opts, params); + silence_unused(welcome); + return { commit_msg, state }; +} + +MLSMessage +State::new_member_add(const bytes& group_id, + epoch_t epoch, + const KeyPackage& new_member, + const SignaturePrivateKey& sig_priv) +{ + const auto suite = new_member.cipher_suite; + auto proposal = Proposal{ Add{ new_member } }; + auto content = GroupContent{ group_id, + epoch, + { NewMemberProposalSender{} }, + { /* no authenticated data */ }, + { std::move(proposal) } }; + auto content_auth = AuthenticatedContent::sign( + WireFormat::mls_public_message, std::move(content), suite, sig_priv, {}); + + return PublicMessage::protect(std::move(content_auth), suite, {}, {}); +} + +/// +/// Proposal and commit factories +/// +template +MLSMessage +State::protect_full(Inner&& inner_content, const MessageOpts& msg_opts) +{ + auto content_auth = sign({ MemberSender{ _index } }, + std::forward(inner_content), + msg_opts.authenticated_data, + msg_opts.encrypt); + return protect(std::move(content_auth), msg_opts.padding_size); +} + +template +AuthenticatedContent +State::sign(const Sender& sender, + Inner&& inner_content, + const bytes& authenticated_data, + bool encrypt) const +{ + auto content = GroupContent{ _group_id, + _epoch, + sender, + authenticated_data, + { std::forward(inner_content) } }; + + auto wire_format = (encrypt) ? WireFormat::mls_private_message + : WireFormat::mls_public_message; + + auto content_auth = AuthenticatedContent::sign( + wire_format, std::move(content), _suite, _identity_priv, group_context()); + + return content_auth; +} + +MLSMessage +State::protect(AuthenticatedContent&& content_auth, size_t padding_size) +{ + switch (content_auth.wire_format) { + case WireFormat::mls_public_message: + return PublicMessage::protect(std::move(content_auth), + _suite, + _key_schedule.membership_key, + group_context()); + + case WireFormat::mls_private_message: + return PrivateMessage::protect(std::move(content_auth), + _suite, + _keys, + _key_schedule.sender_data_secret, + padding_size); + + default: + throw InvalidParameterError("Malformed AuthenticatedContent"); + } +} + +ValidatedContent +State::unwrap(const MLSMessage& msg) +{ + if (msg.version != ProtocolVersion::mls10) { + throw InvalidParameterError("Unsupported version"); + } + + const auto unprotect = overloaded{ + [&](const PublicMessage& pt) -> ValidatedContent { + if (pt.get_group_id() != _group_id) { + throw ProtocolError("PublicMessage not for this group"); + } + + if (pt.get_epoch() != _epoch) { + throw ProtocolError("PublicMessage not for this epoch"); + } + + auto maybe_content_auth = + pt.unprotect(_suite, _key_schedule.membership_key, group_context()); + if (!maybe_content_auth) { + throw ProtocolError("Membership tag failed to verify"); + } + return opt::get(maybe_content_auth); + }, + + [&](const PrivateMessage& ct) -> ValidatedContent { + if (ct.get_group_id() != _group_id) { + throw ProtocolError("PrivateMessage not for this group"); + } + + if (ct.get_epoch() != _epoch) { + throw ProtocolError("PrivateMessage not for this epoch"); + } + + auto maybe_content_auth = + ct.unprotect(_suite, _keys, _key_schedule.sender_data_secret); + if (!maybe_content_auth) { + throw ProtocolError("PrivateMessage decryption failure"); + } + return opt::get(maybe_content_auth); + }, + + [](const auto& /* unused */) -> ValidatedContent { + throw ProtocolError("Invalid wire format"); + }, + }; + + auto val_content = var::visit(unprotect, msg.message); + if (!verify(val_content.content_auth)) { + throw InvalidParameterError("Message signature failed to verify"); + } + + return val_content; +} + +Proposal +State::add_proposal(const KeyPackage& key_package) const +{ + // Check that the key package is validly signed + if (!key_package.verify()) { + throw InvalidParameterError("Invalid signature on key package"); + } + + // Check that the group's basic properties are supported + auto now = seconds_since_epoch(); + if (!key_package.leaf_node.verify_expiry(now)) { + throw InvalidParameterError("Expired key package"); + } + + // Check that the group's extensions are supported + if (!key_package.leaf_node.verify_extension_support(_extensions)) { + throw InvalidParameterError( + "Key package does not support group's extensions"); + } + + return { Add{ key_package } }; +} + +Proposal +State::update_proposal(HPKEPrivateKey leaf_priv, const LeafNodeOptions& opts) +{ + if (_cached_update) { + throw ProtocolError("Only one update may be generated per epoch"); + } + + auto leaf = opt::get(_tree.leaf_node(_index)); + + auto new_leaf = leaf.for_update( + _suite, _group_id, _index, leaf_priv.public_key, opts, _identity_priv); + + auto update = Update{ new_leaf }; + _cached_update = CachedUpdate{ std::move(leaf_priv), update }; + return { update }; +} + +Proposal +State::remove_proposal(RosterIndex index) const +{ + return remove_proposal(leaf_for_roster_entry(index)); +} + +Proposal +State::remove_proposal(LeafIndex removed) const +{ + if (!_tree.has_leaf(removed)) { + throw InvalidParameterError("Remove on blank leaf"); + } + + return { Remove{ removed } }; +} + +Proposal +State::group_context_extensions_proposal(ExtensionList exts) const +{ + if (!extensions_supported(exts)) { + throw InvalidParameterError("Unsupported extensions"); + } + + return { GroupContextExtensions{ std::move(exts) } }; +} + +Proposal +State::pre_shared_key_proposal(const bytes& external_psk_id) const +{ + if (_external_psks.count(external_psk_id) == 0) { + throw InvalidParameterError("Unknown PSK"); + } + + auto psk_id = PreSharedKeyID{ + { ExternalPSK{ external_psk_id } }, + random_bytes(_suite.secret_size()), + }; + return { PreSharedKey{ psk_id } }; +} + +Proposal +State::pre_shared_key_proposal(const bytes& group_id, epoch_t epoch) const +{ + if (epoch != _epoch && _resumption_psks.count({ group_id, epoch }) == 0) { + throw InvalidParameterError("Unknown PSK"); + } + + auto psk_id = PreSharedKeyID{ + { ResumptionPSK{ ResumptionPSKUsage::application, group_id, epoch } }, + random_bytes(_suite.secret_size()), + }; + return { PreSharedKey{ psk_id } }; +} + +Proposal +State::reinit_proposal(bytes group_id, + ProtocolVersion version, + CipherSuite cipher_suite, + ExtensionList extensions) +{ + return { ReInit{ + std::move(group_id), version, cipher_suite, std::move(extensions) } }; +} + +MLSMessage +State::add(const KeyPackage& key_package, const MessageOpts& msg_opts) +{ + return protect_full(add_proposal(key_package), msg_opts); +} + +MLSMessage +State::update(HPKEPrivateKey leaf_priv, + const LeafNodeOptions& opts, + const MessageOpts& msg_opts) +{ + return protect_full(update_proposal(std::move(leaf_priv), opts), msg_opts); +} + +MLSMessage +State::remove(RosterIndex index, const MessageOpts& msg_opts) +{ + return protect_full(remove_proposal(index), msg_opts); +} + +MLSMessage +State::remove(LeafIndex removed, const MessageOpts& msg_opts) +{ + return protect_full(remove_proposal(removed), msg_opts); +} + +MLSMessage +State::group_context_extensions(ExtensionList exts, const MessageOpts& msg_opts) +{ + return protect_full(group_context_extensions_proposal(std::move(exts)), + msg_opts); +} + +MLSMessage +State::pre_shared_key(const bytes& external_psk_id, const MessageOpts& msg_opts) +{ + return protect_full(pre_shared_key_proposal(external_psk_id), msg_opts); +} + +MLSMessage +State::pre_shared_key(const bytes& group_id, + epoch_t epoch, + const MessageOpts& msg_opts) +{ + return protect_full(pre_shared_key_proposal(group_id, epoch), msg_opts); +} + +MLSMessage +State::reinit(bytes group_id, + ProtocolVersion version, + CipherSuite cipher_suite, + ExtensionList extensions, + const MessageOpts& msg_opts) +{ + return protect_full( + reinit_proposal( + std::move(group_id), version, cipher_suite, std::move(extensions)), + msg_opts); +} + +std::tuple +State::commit(const bytes& leaf_secret, + const std::optional& opts, + const MessageOpts& msg_opts) +{ + return commit(leaf_secret, opts, msg_opts, NormalCommitParams{}); +} + +std::tuple +State::commit(const bytes& leaf_secret, + const std::optional& opts, + const MessageOpts& msg_opts, + CommitParams params) +{ + // Construct a commit from cached proposals + // TODO(rlb) ignore some proposals: + // * Update after Update + // * Update after Remove + // * Remove after Remove + Commit commit; + auto joiners = std::vector{}; + for (const auto& cached : _pending_proposals) { + if (var::holds_alternative(cached.proposal.content)) { + const auto& add = var::get(cached.proposal.content); + joiners.push_back(add.key_package); + } + + commit.proposals.push_back({ cached.ref }); + } + + // Add the extra proposals to those we had cached + if (opts) { + const auto& extra_proposals = opt::get(opts).extra_proposals; + for (const auto& proposal : extra_proposals) { + if (var::holds_alternative(proposal.content)) { + const auto& add = var::get(proposal.content); + joiners.push_back(add.key_package); + } + + commit.proposals.push_back({ proposal }); + } + } + + // If this is an external commit, insert an ExternalInit proposal + auto external_commit = std::optional{}; + if (var::holds_alternative(params)) { + external_commit = var::get(params); + } + + auto force_init_secret = std::optional{}; + if (external_commit) { + force_init_secret = opt::get(external_commit).force_init_secret; + } + + // Apply proposals + State next = successor(); + + const auto proposals = must_resolve(commit.proposals, _index); + if (!valid(proposals, _index, params)) { + throw ProtocolError("Invalid proposal list"); + } + + const auto [joiner_locations, psks] = next.apply(proposals); + + if (external_commit) { + const auto& leaf_node = + opt::get(external_commit).joiner_key_package.leaf_node; + next._index = next._tree.add_leaf(leaf_node); + } + + // If this is an external commit, indicate it in the sender field + auto sender = Sender{ MemberSender{ _index } }; + if (external_commit) { + sender = Sender{ NewMemberCommitSender{} }; + } + + // KEM new entropy to the group and the new joiners + auto commit_secret = _suite.zero(); + auto path_secrets = + std::vector>(joiner_locations.size()); + auto force_path = opts && opt::get(opts).force_path; + if (force_path || path_required(proposals)) { + auto leaf_node_opts = LeafNodeOptions{}; + if (opts) { + leaf_node_opts = opt::get(opts).leaf_node_opts; + } + + auto new_priv = next._tree.update( + next._index, leaf_secret, next._group_id, _identity_priv, leaf_node_opts); + + auto ctx = tls::marshal(GroupContext{ + next._suite, + next._group_id, + next._epoch + 1, + next._tree.root_hash(), + next._transcript_hash.confirmed, + next._extensions, + }); + auto path = next._tree.encap(new_priv, ctx, joiner_locations); + + next._tree_priv = new_priv; + commit.path = path; + commit_secret = new_priv.update_secret; + + for (size_t i = 0; i < joiner_locations.size(); i++) { + auto [overlap, shared_path_secret, ok] = + new_priv.shared_path_secret(joiner_locations[i]); + silence_unused(overlap); + silence_unused(ok); + + path_secrets[i] = shared_path_secret; + } + } + + // Create the Commit message and advance the transcripts / key schedule + auto commit_content_auth = + sign(sender, commit, msg_opts.authenticated_data, msg_opts.encrypt); + + next._transcript_hash.update_confirmed(commit_content_auth); + next._epoch += 1; + next.update_epoch_secrets(commit_secret, psks, force_init_secret); + + const auto confirmation_tag = + next._key_schedule.confirmation_tag(next._transcript_hash.confirmed); + commit_content_auth.set_confirmation_tag(confirmation_tag); + + next._transcript_hash.update_interim(commit_content_auth); + + auto commit_message = + protect(std::move(commit_content_auth), msg_opts.padding_size); + + // Complete the GroupInfo and form the Welcome + auto group_info = GroupInfo{ + { + next._suite, + next._group_id, + next._epoch, + next._tree.root_hash(), + next._transcript_hash.confirmed, + next._extensions, + }, + { /* No other extensions */ }, + { confirmation_tag }, + }; + if (opts && opt::get(opts).inline_tree) { + group_info.extensions.add(RatchetTreeExtension{ next._tree }); + } + group_info.sign(next._tree, next._index, next._identity_priv); + + auto welcome = + Welcome{ _suite, next._key_schedule.joiner_secret, psks, group_info }; + for (size_t i = 0; i < joiners.size(); i++) { + welcome.encrypt(joiners[i], path_secrets[i]); + } + + return std::make_tuple(commit_message, welcome, next); +} + +/// +/// Message handlers +/// + +GroupContext +State::group_context() const +{ + return GroupContext{ + _suite, + _group_id, + _epoch, + _tree.root_hash(), + _transcript_hash.confirmed, + _extensions, + }; +} + +std::optional +State::handle(const MLSMessage& msg) +{ + return handle(unwrap(msg), std::nullopt, std::nullopt); +} + +std::optional +State::handle(const MLSMessage& msg, std::optional cached_state) +{ + return handle(unwrap(msg), std::move(cached_state), std::nullopt); +} + +std::optional +State::handle(const ValidatedContent& content_auth) +{ + return handle(content_auth, std::nullopt, std::nullopt); +} + +std::optional +State::handle(const ValidatedContent& content_auth, + std::optional cached_state) +{ + return handle(content_auth, std::move(cached_state), std::nullopt); +} + +std::optional +State::handle(const MLSMessage& msg, + std::optional cached_state, + const std::optional& expected_params) +{ + return handle(unwrap(msg), std::move(cached_state), expected_params); +} + +std::optional +State::handle(const ValidatedContent& val_content, + std::optional cached_state, + const std::optional& expected_params) +{ + // Dispatch on content type + const auto& content_auth = val_content.authenticated_content(); + const auto& content = content_auth.content; + switch (content.content_type()) { + // Proposals get queued, do not result in a state transition + case ContentType::proposal: + cache_proposal(content_auth); + return std::nullopt; + + // Commits are handled in the remainder of this method + case ContentType::commit: + break; + + // Any other content type in this method is an error + default: + throw InvalidParameterError("Invalid content type"); + } + + switch (content.sender.sender_type()) { + case SenderType::member: + case SenderType::new_member_commit: + break; + + default: + throw ProtocolError("Invalid commit sender type"); + } + + auto sender = std::optional(); + if (content.sender.sender_type() == SenderType::member) { + sender = var::get(content.sender.sender).sender; + } + + if (sender == _index) { + if (cached_state) { + // Verify that the cached state is a plausible successor to this state + const auto& next = opt::get(cached_state); + if (next._group_id != _group_id || next._epoch != _epoch + 1 || + next._index != _index) { + throw InvalidParameterError("Invalid successor state"); + } + + return next; + } + + throw InvalidParameterError("Handle own commits with caching"); + } + + // Apply the commit + const auto& commit = var::get(content.content); + const auto proposals = must_resolve(commit.proposals, sender); + + const auto params = infer_commit_type(sender, proposals, expected_params); + auto external_commit = var::holds_alternative(params); + + // Check that a path is present when required + if (path_required(proposals) && !commit.path) { + throw ProtocolError("Path required but not present"); + } + + // Apply the proposals + auto next = successor(); + auto [joiner_locations, psks] = next.apply(proposals); + + // If this is an external commit, add the joiner to the tree and note the + // location where they were added. Also, compute the "externally forced" + // value that we will use for the init_secret (as opposed to the init_secret + // from the key schedule). + auto force_init_secret = std::optional{}; + auto sender_location = LeafIndex{ 0 }; + if (!external_commit) { + sender_location = opt::get(sender); + } else { + // Find where the joiner will be added + sender_location = next._tree.allocate_leaf(); + + // Extract the forced init secret + auto kem_output = commit.valid_external(); + if (!kem_output) { + throw ProtocolError("Invalid external commit"); + } + + force_init_secret = + _key_schedule.receive_external_init(opt::get(kem_output)); + } + + // Decapsulate and apply the UpdatePath, if provided + auto commit_secret = _suite.zero(); + if (commit.path) { + const auto& path = opt::get(commit.path); + + if (!valid(path.leaf_node, LeafNodeSource::commit, sender_location)) { + throw ProtocolError("Commit path has invalid leaf node"); + } + + if (!next._tree.parent_hash_valid(sender_location, path)) { + throw ProtocolError("Commit path has invalid parent hash"); + } + + next._tree.merge(sender_location, path); + + auto ctx = tls::marshal(GroupContext{ + next._suite, + next._group_id, + next._epoch + 1, + next._tree.root_hash(), + next._transcript_hash.confirmed, + next._extensions, + }); + next._tree_priv.decap( + sender_location, next._tree, ctx, path, joiner_locations); + + commit_secret = next._tree_priv.update_secret; + } + + // Update the transcripts and advance the key schedule + next._transcript_hash.update(content_auth); + next._epoch += 1; + next.update_epoch_secrets(commit_secret, { psks }, force_init_secret); + + // Verify the confirmation MAC + const auto confirmation_tag = + next._key_schedule.confirmation_tag(next._transcript_hash.confirmed); + if (!content_auth.check_confirmation_tag(confirmation_tag)) { + throw ProtocolError("Confirmation failed to verify"); + } + + return next; +} + +/// +/// Subgroup branching +/// + +// Parameters: +// * ctor inputs +// * leaf_secret +// * commit_opts +std::tuple +State::create_branch(bytes group_id, + HPKEPrivateKey enc_priv, + SignaturePrivateKey sig_priv, + const LeafNode& leaf_node, + ExtensionList extensions, + const std::vector& key_packages, + const bytes& leaf_secret, + const CommitOpts& commit_opts) const +{ + // Create new empty group with the appropriate PSK + auto new_group = + State{ std::move(group_id), _suite, std::move(enc_priv), + std::move(sig_priv), leaf_node, std::move(extensions) }; + + new_group.add_resumption_psk(_group_id, _epoch, _key_schedule.resumption_psk); + + // Create Add proposals + auto proposals = stdx::transform( + key_packages, [&](const auto& kp) { return new_group.add_proposal(kp); }); + + // Create PSK proposal + proposals.push_back({ PreSharedKey{ + { ResumptionPSK{ ResumptionPSKUsage::branch, _group_id, _epoch }, + random_bytes(_suite.secret_size()) } } }); + + // Commit the Add and PSK proposals + auto opts = CommitOpts{ + proposals, + commit_opts.inline_tree, + commit_opts.force_path, + commit_opts.leaf_node_opts, + }; + auto [_commit, welcome, state] = new_group.commit( + leaf_secret, opts, {}, RestartCommitParams{ ResumptionPSKUsage::branch }); + return { state, welcome }; +} + +State +State::handle_branch(const HPKEPrivateKey& init_priv, + HPKEPrivateKey enc_priv, + SignaturePrivateKey sig_priv, + const KeyPackage& key_package, + const Welcome& welcome, + const std::optional& tree) const +{ + auto resumption_psks = + std::map{ { { _group_id, _epoch }, + _key_schedule.resumption_psk } }; + auto branch_state = State{ + init_priv, + std::move(enc_priv), + std::move(sig_priv), + key_package, + welcome, + tree, + {}, + resumption_psks, + }; + + if (branch_state._suite != _suite) { + throw ProtocolError("Attempt to branch with a different ciphersuite"); + } + + if (branch_state._epoch != 1) { + throw ProtocolError("Branch not done at the beginning of the group"); + } + + return branch_state; +} + +State::Tombstone::Tombstone(const State& state_in, ReInit reinit_in) + : epoch_authenticator(state_in.epoch_authenticator()) + , reinit(std::move(reinit_in)) + , prior_group_id(state_in._group_id) + , prior_epoch(state_in._epoch) + , resumption_psk(state_in._key_schedule.resumption_psk) +{ +} + +std::tuple +State::Tombstone::create_welcome(HPKEPrivateKey enc_priv, + SignaturePrivateKey sig_priv, + const LeafNode& leaf_node, + const std::vector& key_packages, + const bytes& leaf_secret, + const CommitOpts& commit_opts) const +{ + // Create new empty group with the appropriate PSK + auto new_group = + State{ reinit.group_id, reinit.cipher_suite, std::move(enc_priv), + std::move(sig_priv), leaf_node, reinit.extensions }; + + new_group.add_resumption_psk(prior_group_id, prior_epoch, resumption_psk); + + // Create Add proposals + auto proposals = stdx::transform( + key_packages, [&](const auto& kp) { return new_group.add_proposal(kp); }); + + // Create PSK proposal + proposals.push_back({ PreSharedKey{ + { ResumptionPSK{ ResumptionPSKUsage::reinit, prior_group_id, prior_epoch }, + random_bytes(reinit.cipher_suite.secret_size()) } } }); + + // Commit the Add and PSK proposals + auto opts = CommitOpts{ + proposals, + commit_opts.inline_tree, + commit_opts.force_path, + commit_opts.leaf_node_opts, + }; + auto [_commit, welcome, state] = new_group.commit( + leaf_secret, opts, {}, RestartCommitParams{ ResumptionPSKUsage::reinit }); + return { state, welcome }; +} + +State +State::Tombstone::handle_welcome( + const HPKEPrivateKey& init_priv, + HPKEPrivateKey enc_priv, + SignaturePrivateKey sig_priv, + const KeyPackage& key_package, + const Welcome& welcome, + const std::optional& tree) const +{ + auto resumption_psks = + std::map{ { { prior_group_id, prior_epoch }, + resumption_psk } }; + auto new_state = State{ + init_priv, + std::move(enc_priv), + std::move(sig_priv), + key_package, + welcome, + tree, + {}, + resumption_psks, + }; + + if (new_state._suite != reinit.cipher_suite) { + throw ProtocolError("Attempt to reinit with the wrong ciphersuite"); + } + + if (new_state._epoch != 1) { + throw ProtocolError("ReInit not done at the beginning of the group"); + } + + return new_state; +} + +std::tuple +State::reinit_commit(const bytes& leaf_secret, + const std::optional& opts, + const MessageOpts& msg_opts) +{ + // Ensure that either the proposal cache or the inline proposals have a ReInit + // proposal, and no others. + auto reinit_proposal = Proposal{}; + if (_pending_proposals.size() == 1) { + reinit_proposal = _pending_proposals.front().proposal; + } else if (opts && opt::get(opts).extra_proposals.size() == 1) { + reinit_proposal = opt::get(opts).extra_proposals.front(); + } else { + throw ProtocolError("Illegal proposals for reinitialization"); + } + + auto reinit = var::get(reinit_proposal.content); + + // Create the commit + const auto [commit_msg, welcome, new_state] = + commit(leaf_secret, opts, msg_opts, ReInitCommitParams{}); + silence_unused(welcome); + + // Create the Tombstone from the terminal state + return { { new_state, reinit }, commit_msg }; +} + +State::Tombstone +State::handle_reinit_commit(const MLSMessage& commit_msg) +{ + // Verify the signature and process the commit + const auto val_content = unwrap(commit_msg); + const auto& content_auth = val_content.authenticated_content(); + if (!verify(content_auth)) { + throw InvalidParameterError("Message signature failed to verify"); + } + + auto new_state = + opt::get(handle(content_auth, std::nullopt, ReInitCommitParams{})); + + // Extract the ReInit and create the Tombstone + const auto& commit = var::get(content_auth.content.content); + const auto proposals = must_resolve(commit.proposals, std::nullopt); + if (!valid_reinit(proposals)) { + throw ProtocolError("Invalid proposals for reinit"); + } + + const auto& reinit_proposal = proposals.front(); + const auto& reinit = var::get(reinit_proposal.proposal.content); + return Tombstone{ new_state, reinit }; +} + +/// +/// Internals +/// + +LeafIndex +State::apply(const Add& add) +{ + return _tree.add_leaf(add.key_package.leaf_node); +} + +void +State::apply(LeafIndex target, const Update& update) +{ + _tree.update_leaf(target, update.leaf_node); +} + +void +State::apply(LeafIndex target, + const Update& update, + const HPKEPrivateKey& leaf_priv) +{ + _tree.update_leaf(target, update.leaf_node); + _tree_priv.set_leaf_priv(leaf_priv); +} + +LeafIndex +State::apply(const Remove& remove) +{ + if (!_tree.has_leaf(remove.removed)) { + throw ProtocolError("Attempt to remove non-member"); + } + + _tree.blank_path(remove.removed); + return remove.removed; +} + +void +State::apply(const GroupContextExtensions& gce) +{ + // TODO(RLB): Update spec to clarify that you MUST verify that the new + // extensions are compatible with all members. + if (!extensions_supported(gce.group_context_extensions)) { + throw ProtocolError("Unsupported extensions in GroupContextExtensions"); + } + + _extensions = gce.group_context_extensions; +} + +bool +State::extensions_supported(const ExtensionList& exts) const +{ + return _tree.all_leaves([&](auto /* i */, const auto& leaf_node) { + return leaf_node.verify_extension_support(exts); + }); +} + +void +State::cache_proposal(AuthenticatedContent content_auth) +{ + auto ref = _suite.ref(content_auth); + if (stdx::any_of(_pending_proposals, + [&](const auto& cached) { return cached.ref == ref; })) { + return; + } + + auto sender_location = std::optional(); + if (content_auth.content.sender.sender_type() == SenderType::member) { + const auto& sender = content_auth.content.sender.sender; + sender_location = var::get(sender).sender; + } + + const auto& proposal = var::get(content_auth.content.content); + + if (content_auth.content.sender.sender_type() == SenderType::external && + !valid_external_proposal_type(proposal.proposal_type())) { + throw ProtocolError("Invalid external proposal"); + } + + if (!valid(sender_location, proposal)) { + throw ProtocolError("Invalid proposal"); + } + + _pending_proposals.push_back({ + _suite.ref(content_auth), + proposal, + sender_location, + }); +} + +std::optional +State::resolve(const ProposalOrRef& id, + std::optional sender_index) const +{ + if (var::holds_alternative(id.content)) { + return CachedProposal{ + {}, + var::get(id.content), + sender_index, + }; + } + + const auto& ref = var::get(id.content); + for (const auto& cached : _pending_proposals) { + if (cached.ref == ref) { + return cached; + } + } + + return std::nullopt; +} + +std::vector +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) +State::must_resolve(const std::vector& ids, + std::optional sender_index) const +{ + auto must_resolve = [&](const auto& id) { + return opt::get(resolve(id, sender_index)); + }; + return stdx::transform(ids, must_resolve); +} + +std::vector +State::resolve(const std::vector& psks) const +{ + return stdx::transform(psks, [&](const auto& psk_id) { + auto get_secret = overloaded{ + [&](const ExternalPSK& ext_psk) { + if (_external_psks.count(ext_psk.psk_id) == 0) { + throw ProtocolError("Unknown external PSK"); + } + + return _external_psks.at(ext_psk.psk_id); + }, + + [&](const ResumptionPSK& res_psk) { + if (res_psk.psk_epoch == _epoch) { + return _key_schedule.resumption_psk; + } + + auto key = std::make_tuple(res_psk.psk_group_id, res_psk.psk_epoch); + if (_resumption_psks.count(key) == 0) { + throw ProtocolError("Unknown Resumption PSK"); + } + + return _resumption_psks.at(key); + }, + }; + + auto secret = var::visit(get_secret, psk_id.content); + return PSKWithSecret{ psk_id, secret }; + }); +} + +std::vector +State::apply(const std::vector& proposals, + Proposal::Type required_type) +{ + auto locations = std::vector{}; + for (const auto& cached : proposals) { + auto proposal_type = cached.proposal.proposal_type(); + if (proposal_type != required_type) { + continue; + } + + switch (proposal_type) { + case ProposalType::add: { + locations.push_back(apply(var::get(cached.proposal.content))); + break; + } + + case ProposalType::update: { + const auto& update = var::get(cached.proposal.content); + + if (!cached.sender) { + throw ProtocolError("Update without target leaf"); + } + + auto target = opt::get(cached.sender); + if (target != _index) { + apply(target, update); + break; + } + + if (!_cached_update) { + throw ProtocolError("Self-update with no cached secret"); + } + + const auto& cached_update = opt::get(_cached_update); + if (update != cached_update.proposal) { + throw ProtocolError("Self-update does not match cached data"); + } + + apply(target, update, cached_update.update_priv); + locations.push_back(target); + break; + } + + case ProposalType::remove: { + const auto& remove = var::get(cached.proposal.content); + locations.push_back(apply(remove)); + break; + } + + case ProposalType::group_context_extensions: { + const auto& gce = + var::get(cached.proposal.content); + apply(gce); + break; + } + + default: + throw ProtocolError("Unsupported proposal type"); + } + } + + // The cached update needs to be reset after applying proposals, so that it is + // in a clean state for the next epoch. + _cached_update.reset(); + + return locations; +} + +std::tuple, std::vector> +State::apply(const std::vector& proposals) +{ + apply(proposals, ProposalType::update); + apply(proposals, ProposalType::remove); + auto joiner_locations = apply(proposals, ProposalType::add); + apply(proposals, ProposalType::group_context_extensions); + + // Extract the PSK proposals and look up the secrets + // TODO(RLB): Factor this out, and also factor the above methods into + // apply_update, apply_remove, etc. + auto psk_ids = std::vector{}; + for (const auto& cached : proposals) { + if (cached.proposal.proposal_type() != ProposalType::psk) { + continue; + } + + const auto& proposal = var::get(cached.proposal.content); + psk_ids.push_back(proposal.psk); + } + auto psks = resolve(psk_ids); + + _tree.truncate(); + _tree_priv.truncate(_tree.size); + _tree.set_hash_all(); + return { joiner_locations, psks }; +} + +/// +/// Message protection +/// + +MLSMessage +State::protect(const bytes& authenticated_data, + const bytes& pt, + size_t padding_size) +{ + auto msg_opts = MessageOpts{ true, authenticated_data, padding_size }; + return protect_full(ApplicationData{ pt }, msg_opts); +} + +std::tuple +State::unprotect(const MLSMessage& ct) +{ + const auto val_content = unwrap(ct); + const auto& content_auth = val_content.authenticated_content(); + + if (!verify(content_auth)) { + throw InvalidParameterError("Message signature failed to verify"); + } + + if (content_auth.content.content_type() != ContentType::application) { + throw ProtocolError("Unprotect of handshake message"); + } + + if (content_auth.wire_format != WireFormat::mls_private_message) { + throw ProtocolError("Application data not sent as PrivateMessage"); + } + + return { + content_auth.content.authenticated_data, + var::get(content_auth.content.content).data, + }; +} + +/// +/// Properties of a proposal list +/// + +bool +State::valid(const LeafNode& leaf_node, + LeafNodeSource required_source, + std::optional index) const +{ + // Verify that the credential in the LeafNode is valid as described in Section + // 5.3.1. + // XXX(RLB) N/A, no credential validation in the library right now + + // Verify the leaf_node_source field: + const auto correct_source = (leaf_node.source() == required_source); + + // Verify that the signature on the LeafNode is valid using signature_key. + auto binding = std::optional{}; + switch (required_source) { + case LeafNodeSource::commit: + case LeafNodeSource::update: + binding = LeafNode::MemberBinding{ _group_id, opt::get(index) }; + break; + + default: + // Nothing to do + break; + } + + const auto signature_valid = leaf_node.verify(_suite, binding); + + // Verify that the LeafNode is compatible with the group's parameters. If the + // GroupContext has a required_capabilities extension, then the required + // extensions, proposals, and credential types MUST be listed in the + // LeafNode's capabilities field. + const auto supports_group_extensions = + leaf_node.verify_extension_support(_extensions); + + // TODO(RLB) Verify the lifetime field + + // Verify that the credential type is supported by all members of the group, + // as specified by the capabilities field of each member's LeafNode, and that + // the capabilities field of this LeafNode indicates support for all the + // credential types currently in use by other members. + // + // Verify that the following fields are unique among the members of the group: + // signature_key + // encryption_key + // + // Note: Uniqueness of signature and encryption keys is assured by the + // tree operations (add/update), so we do not need to verify those here. + const auto mutual_credential_support = + _tree.all_leaves([&](auto /* i */, const auto& leaf) { + return leaf.capabilities.credential_supported(leaf_node.credential) && + leaf_node.capabilities.credential_supported(leaf.credential); + }); + + // Verify that the extensions in the LeafNode are supported by checking that + // the ID for each extension in the extensions field is listed in the + // capabilities.extensions field of the LeafNode. + auto supports_own_extensions = + leaf_node.verify_extension_support(leaf_node.extensions); + + return (signature_valid && supports_group_extensions && correct_source && + mutual_credential_support && supports_own_extensions); +} + +bool +State::valid(const KeyPackage& key_package) const +{ + // Verify that the ciphersuite and protocol version of the KeyPackage match + // those in the GroupContext. + const auto correct_ciphersuite = (key_package.cipher_suite == _suite); + + // Verify that the signature on the KeyPackage is valid using the public key + // in leaf_node.credential. + const auto valid_signature = key_package.verify(); + + // Verify that the leaf_node of the KeyPackage is valid for a KeyPackage + // according to Section 7.3. + const auto leaf_node_valid = + valid(key_package.leaf_node, LeafNodeSource::key_package, std::nullopt); + + // Verify that the value of leaf_node.encryption_key is different from the + // value of the init_key field. + const auto distinct_keys = + (key_package.init_key != key_package.leaf_node.encryption_key); + + return (correct_ciphersuite && valid_signature && leaf_node_valid && + distinct_keys); +} + +bool +State::valid(const Add& add) const +{ + return valid(add.key_package); +} + +bool +State::valid(LeafIndex sender, const Update& update) const +{ + const auto maybe_leaf = _tree.leaf_node(sender); + if (!maybe_leaf) { + return false; + } + + return valid(update.leaf_node, LeafNodeSource::update, sender); +} + +bool +State::valid(const Remove& remove) const +{ + // We mark self-removes invalid here even though a resync Commit will + // sometimes cause them. This is OK because this method is only called from + // the normal proposal list validation method, not the external commit one. + auto in_tree = remove.removed < _tree.size && _tree.has_leaf(remove.removed); + auto not_me = remove.removed != _index; + return in_tree && not_me; +} + +bool +State::valid(const PreSharedKey& psk) const +{ + // External PSKs are allowed if we have the corresponding secret + if (var::holds_alternative(psk.psk.content)) { + const auto& ext_psk = var::get(psk.psk.content); + return _external_psks.count(ext_psk.psk_id) > 0; + } + + // Resumption PSKs are allowed only with usage 'application', and only if we + // have the corresponding secret. + if (var::holds_alternative(psk.psk.content)) { + const auto& res_psk = var::get(psk.psk.content); + if (res_psk.usage != ResumptionPSKUsage::application) { + return false; + } + + const auto key = std::make_tuple(res_psk.psk_group_id, res_psk.psk_epoch); + return res_psk.psk_epoch == _epoch || _resumption_psks.count(key) > 0; + } + + return false; +} + +bool +State::valid(const ReInit& reinit) +{ + // Check that the version and CipherSuite are ones we support + auto supported_version = (reinit.version == ProtocolVersion::mls10); + auto supported_suite = + stdx::contains(all_supported_suites, reinit.cipher_suite.cipher_suite()); + + return supported_version && supported_suite; +} + +bool +State::valid(const ExternalInit& external_init) const +{ + return external_init.kem_output.size() == _suite.hpke().kem.enc_size; +} + +bool +State::valid(const GroupContextExtensions& gce) const +{ + return extensions_supported(gce.group_context_extensions); +} + +bool +State::valid(std::optional sender, const Proposal& proposal) const +{ + const auto specifically_valid = overloaded{ + [&](const Update& update) { return valid(opt::get(sender), update); }, + [&](const auto& proposal) { return valid(proposal); }, + }; + return var::visit(specifically_valid, proposal.content); +} + +bool +State::valid(const std::vector& proposals, + LeafIndex commit_sender, + const CommitParams& params) const +{ + auto specifically = overloaded{ + [&](const NormalCommitParams& /* unused */) { + return valid_normal(proposals, commit_sender); + }, + [&](const ExternalCommitParams& /* unused */) { + return valid_external(proposals); + }, + [&](const RestartCommitParams& params) { + return valid_restart(proposals, params.allowed_usage); + }, + [&](const ReInitCommitParams& /* unused */) { + return valid_reinit(proposals); + }, + }; + + return var::visit(specifically, params); +} + +bool +// NB(RLB): clang-tidy thinks this can be static, but it can't. +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) +State::valid_normal(const std::vector& proposals, + LeafIndex commit_sender) const +{ + // It contains an individual proposal that is invalid as specified in Section + // 12.1. + const auto has_invalid_proposal = + stdx::any_of(proposals, [&](const auto& cached) { + return !valid(cached.sender, cached.proposal); + }); + + // It contains an Update proposal generated by the committer. + const auto has_self_update = stdx::any_of(proposals, [&](const auto& cached) { + return cached.proposal.proposal_type() == ProposalType::update && + cached.sender == commit_sender; + }); + + // It contains a Remove proposal that removes the committer. + const auto has_self_remove = stdx::any_of(proposals, [&](const auto& cached) { + return cached.proposal.proposal_type() == ProposalType::remove && + var::get(cached.proposal.content).removed == commit_sender; + }); + + // It contains multiple Update and/or Remove proposals that apply to the same + // leaf. If the committer has received multiple such proposals they SHOULD + // prefer any Remove received, or the most recent Update if there are no + // Removes. + auto updated_or_removed = std::set{}; + const auto has_dup_update_remove = + stdx::any_of(proposals, [&](const auto& cached) { + auto index = LeafIndex{ 0 }; + switch (cached.proposal.proposal_type()) { + case ProposalType::update: + index = opt::get(cached.sender); + break; + + case ProposalType::remove: + index = var::get(cached.proposal.content).removed; + break; + + default: + return false; + } + + if (stdx::contains(updated_or_removed, index)) { + return true; + } + + updated_or_removed.insert(index); + return false; + }); + + // It contains multiple Add proposals that contain KeyPackages that represent + // the same client according to the application (for example, identical + // signature keys). + auto signature_keys = std::vector{}; + const auto has_dup_signature_key = + stdx::any_of(proposals, [&](const auto& cached) { + if (cached.proposal.proposal_type() != ProposalType::add) { + return false; + } + + auto key_package = var::get(cached.proposal.content).key_package; + auto signature_key = key_package.leaf_node.signature_key; + if (stdx::contains(signature_keys, signature_key)) { + return true; + } + + signature_keys.push_back(signature_key); + return false; + }); + + // It contains an Add proposal with a KeyPackage that represents a client + // already in the group according to the application, unless there is a Remove + // proposal in the list removing the matching client from the group. + // TODO(RLB) + + // It contains multiple PreSharedKey proposals that reference the same + // PreSharedKeyID. + auto psk_ids = std::vector{}; + const auto has_dup_psk_id = stdx::any_of(proposals, [&](const auto& cached) { + if (cached.proposal.proposal_type() != ProposalType::psk) { + return false; + } + + auto psk_id = var::get(cached.proposal.content).psk; + if (stdx::contains(psk_ids, psk_id)) { + return true; + } + + psk_ids.push_back(psk_id); + return false; + }); + + // It contains multiple GroupContextExtensions proposals. + const auto gce_count = stdx::count_if(proposals, [](const auto& cached) { + return cached.proposal.proposal_type() == + ProposalType::group_context_extensions; + }); + const auto has_multiple_gce = (gce_count > 1); + + // It contains a ReInit proposal together with any other proposal. If the + // committer has received other proposals during the epoch, they SHOULD prefer + // them over the ReInit proposal, allowing the ReInit to be resent and applied + // in a subsequent epoch. + const auto has_reinit = stdx::any_of(proposals, [](const auto& cached) { + return cached.proposal.proposal_type() == ProposalType::reinit; + }); + + // It contains an ExternalInit proposal. + const auto has_external_init = + stdx::any_of(proposals, [](const auto& cached) { + return cached.proposal.proposal_type() == ProposalType::external_init; + }); + + // It contains a proposal with a non-default proposal type that is not + // supported by some members of the group that will process the Commit (i.e., + // members being added or removed by the Commit do not need to support the + // proposal type). + // XXX(RLB): N/A, no non-default proposal types + + // After processing the commit the ratchet tree is invalid, in particular, if + // it contains any leaf node that is invalid according to Section 7.3. + // + // NB(RLB): Leaf nodes are already checked in the individual proposal check at + // the top. So the focus here is key uniqueness. We check this by checking + // uniqueness of encryption keys across the Adds and Updates in this list of + // proposals. The keys have already been checked to be distinct from any keys + // already in the tree. + auto enc_keys = std::vector{}; + const auto has_dup_enc_key = stdx::any_of(proposals, [&](const auto& cached) { + const auto get_enc_key = + overloaded{ [](const Add& add) -> std::optional { + return add.key_package.leaf_node.encryption_key; + }, + [](const Update& update) -> std::optional { + return update.leaf_node.encryption_key; + }, + + [](const auto& /* default */) + -> std::optional { return std::nullopt; } }; + auto maybe_enc_key = var::visit(get_enc_key, cached.proposal.content); + if (!maybe_enc_key) { + return false; + } + + const auto& enc_key = opt::get(maybe_enc_key); + if (stdx::contains(enc_keys, enc_key)) { + return true; + } + + enc_keys.push_back(enc_key); + return false; + }); + + return !(has_invalid_proposal || has_self_update || has_self_remove || + has_dup_update_remove || has_dup_signature_key || has_dup_psk_id || + has_multiple_gce || has_reinit || has_external_init || + has_dup_enc_key); +} + +bool +State::valid_reinit(const std::vector& proposals) +{ + // Check that the list contains a ReInit proposal + const auto has_reinit = stdx::any_of(proposals, [](const auto& cached) { + return cached.proposal.proposal_type() == ProposalType::reinit; + }); + + // Check whether the list contains any disallowed proposals + const auto has_disallowed = stdx::any_of(proposals, [](const auto& cached) { + return cached.proposal.proposal_type() != ProposalType::reinit; + }); + + return has_reinit && !has_disallowed; +} + +bool +State::valid_restart(const std::vector& proposals, + ResumptionPSKUsage allowed_usage) +{ + // Check that the list has exactly one resumption PSK proposal with the + // allowed usage and any other PSKs are external + auto found_allowed = false; + const auto acceptable_psks = stdx::all_of(proposals, [&](const auto& cached) { + if (cached.proposal.proposal_type() != ProposalType::psk) { + return true; + } + + const auto& psk = var::get(cached.proposal.content); + if (var::holds_alternative(psk.psk.content)) { + return true; + } + + const auto& res_psk = var::get(psk.psk.content); + const auto allowed = res_psk.usage == allowed_usage; + if (found_allowed && allowed) { + return false; + } + + found_allowed = found_allowed || allowed; + return true; + }); + + return acceptable_psks && found_allowed; +} + +bool +State::valid_external_proposal_type(const Proposal::Type proposal_type) +{ + switch (proposal_type) { + case ProposalType::add: + case ProposalType::remove: + case ProposalType::psk: + case ProposalType::reinit: + case ProposalType::group_context_extensions: + return true; + + default: + return false; + } +} + +bool +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) +State::valid_external(const std::vector& proposals) const +{ + // Exactly one ExternalInit + auto ext_init_count = stdx::count_if(proposals, [](const auto& cached) { + return cached.proposal.proposal_type() == ProposalType::external_init; + }); + auto one_ext_init = (ext_init_count == 1); + + // At most one Remove proposal, with which the joiner removes an old version + // of themselves. If a Remove proposal is present, then the LeafNode in the + // path field of the external commit MUST meet the same criteria as would the + // LeafNode in an Update for the removed leaf (see Section 12.1.2). In + // particular, the credential in the LeafNode MUST present a set of + // identifiers that is acceptable to the application for the removed + // participant. + // TODO(RLB) Verify that Remove is properly formed + auto remove_count = stdx::count_if(proposals, [](const auto& cached) { + return cached.proposal.proposal_type() == ProposalType::remove; + }); + auto no_dup_remove = (remove_count <= 1); + + // Zero or more PreSharedKey proposals. + // No other proposals. + auto no_disallowed = stdx::all_of(proposals, [&](const auto& cached) { + switch (cached.proposal.proposal_type()) { + case ProposalType::external_init: + case ProposalType::remove: + return true; + + case ProposalType::psk: + return valid(var::get(cached.proposal.content)); + + default: + return false; + } + }); + + return one_ext_init && no_dup_remove && no_disallowed; +} + +State::CommitParams +State::infer_commit_type( + const std::optional& sender, + const std::vector& proposals, + const std::optional& expected_params) const +{ + // If an expected type was provided, validate against it + if (expected_params) { + const auto& expected = opt::get(expected_params); + + auto specifically = overloaded{ + [&](const NormalCommitParams& /* unused */) { + return sender && valid_normal(proposals, opt::get(sender)); + }, + [&](const ExternalCommitParams& /* unused */) { + return !sender && valid_external(proposals); + }, + [&](const RestartCommitParams& params) { + return sender && valid_restart(proposals, params.allowed_usage); + }, + [&](const ReInitCommitParams& /* unused */) { + return sender && valid_reinit(proposals); + }, + }; + + if (!var::visit(specifically, expected)) { + throw ProtocolError("Invalid proposal list"); + } + + return expected; + } + + // Otherwise, check to see if this is a valid external or normal commit + if (!sender && valid_external(proposals)) { + return ExternalCommitParams{}; + } + + if (sender && valid_normal(proposals, opt::get(sender))) { + return NormalCommitParams{}; + } + + throw ProtocolError("Invalid proposal list"); +} + +bool +State::path_required(const std::vector& proposals) +{ + static const auto path_required_types = std::set{ + ProposalType::update, + ProposalType::remove, + ProposalType::external_init, + ProposalType::group_context_extensions, + }; + + if (proposals.empty()) { + return true; + } + + return stdx::any_of(proposals, [](const auto& cp) { + return path_required_types.count(cp.proposal.proposal_type()) != 0; + }); +} + +/// +/// Inner logic and convenience functions +/// + +bool +operator==(const State& lhs, const State& rhs) +{ + auto suite = (lhs._suite == rhs._suite); + auto group_id = (lhs._group_id == rhs._group_id); + auto epoch = (lhs._epoch == rhs._epoch); + auto tree = (lhs._tree == rhs._tree); + auto transcript_hash = (lhs._transcript_hash == rhs._transcript_hash); + auto key_schedule = (lhs._key_schedule == rhs._key_schedule); + auto extensions = (lhs._extensions == rhs._extensions); + + return suite && group_id && epoch && tree && transcript_hash && + key_schedule && extensions; +} + +bool +operator!=(const State& lhs, const State& rhs) +{ + return !(lhs == rhs); +} + +void +State::update_epoch_secrets(const bytes& commit_secret, + const std::vector& psks, + const std::optional& force_init_secret) +{ + auto ctx = tls::marshal(GroupContext{ + _suite, + _group_id, + _epoch, + _tree.root_hash(), + _transcript_hash.confirmed, + _extensions, + }); + _key_schedule = + _key_schedule.next(commit_secret, psks, force_init_secret, ctx); + _keys = _key_schedule.encryption_keys(_tree.size); +} + +/// +/// Message encryption and decryption +/// +bool +State::verify_internal(const AuthenticatedContent& content_auth) const +{ + const auto& sender = + var::get(content_auth.content.sender.sender).sender; + auto maybe_leaf = _tree.leaf_node(sender); + if (!maybe_leaf) { + throw InvalidParameterError("Signature from blank node"); + } + + const auto& pub = opt::get(maybe_leaf).signature_key; + return content_auth.verify(_suite, pub, group_context()); +} + +bool +State::verify_external(const AuthenticatedContent& content_auth) const +{ + const auto& ext_sender = + var::get(content_auth.content.sender.sender); + const auto senders_ext = _extensions.find(); + const auto& senders = opt::get(senders_ext).senders; + const auto& pub = senders.at(ext_sender.sender_index).signature_key; + return content_auth.verify(_suite, pub, group_context()); +} + +bool +State::verify_new_member_proposal( + const AuthenticatedContent& content_auth) const +{ + const auto& proposal = var::get(content_auth.content.content); + const auto& add = var::get(proposal.content); + const auto& pub = add.key_package.leaf_node.signature_key; + return content_auth.verify(_suite, pub, group_context()); +} + +bool +State::verify_new_member_commit(const AuthenticatedContent& content_auth) const +{ + const auto& commit = var::get(content_auth.content.content); + const auto& path = opt::get(commit.path); + const auto& pub = path.leaf_node.signature_key; + return content_auth.verify(_suite, pub, group_context()); +} + +bool +State::verify(const AuthenticatedContent& content_auth) const +{ + switch (content_auth.content.sender.sender_type()) { + case SenderType::member: + return verify_internal(content_auth); + + case SenderType::external: + return verify_external(content_auth); + + case SenderType::new_member_proposal: + return verify_new_member_proposal(content_auth); + + case SenderType::new_member_commit: + return verify_new_member_commit(content_auth); + + default: + throw ProtocolError("Invalid sender type"); + } +} + +void +State::add_resumption_psk(const bytes& group_id, epoch_t epoch, bytes secret) +{ + _resumption_psks.insert_or_assign({ group_id, epoch }, std::move(secret)); +} + +void +State::remove_resumption_psk(const bytes& group_id, epoch_t epoch) +{ + _resumption_psks.erase({ group_id, epoch }); +} + +void +State::add_external_psk(const bytes& id, const bytes& secret) +{ + _external_psks.insert_or_assign(id, secret); +} + +void +State::remove_external_psk(const bytes& id) +{ + _external_psks.erase(id); +} + +bytes +State::do_export(const std::string& label, + const bytes& context, + size_t size) const +{ + return _key_schedule.do_export(label, context, size); +} + +GroupInfo +State::group_info(bool inline_tree) const +{ + auto group_info = GroupInfo{ + { + _suite, + _group_id, + _epoch, + _tree.root_hash(), + _transcript_hash.confirmed, + _extensions, + }, + { /* No other extensions */ }, + _key_schedule.confirmation_tag(_transcript_hash.confirmed), + }; + + group_info.extensions.add( + ExternalPubExtension{ _key_schedule.external_priv.public_key }); + + if (inline_tree) { + group_info.extensions.add(RatchetTreeExtension{ _tree }); + } + + group_info.sign(_tree, _index, _identity_priv); + return group_info; +} + +std::vector +State::roster() const +{ + auto leaves = std::vector{}; + leaves.reserve(_tree.size.val); + + _tree.all_leaves([&](auto /* i */, auto leaf) { + leaves.push_back(leaf); + return true; + }); + + return leaves; +} + +bytes +State::epoch_authenticator() const +{ + return _key_schedule.epoch_authenticator; +} + +LeafIndex +State::leaf_for_roster_entry(RosterIndex index) const +{ + auto visited = RosterIndex{ 0 }; + auto found = std::optional{}; + _tree.all_leaves([&](auto i, const auto& /* leaf_node */) { + if (visited == index) { + found = i; + return false; + } + + visited.val += 1; + return true; + }); + + return opt::get(found); +} + +State +State::successor() const +{ + // Copy everything, then clear things that shouldn't be copied + auto next = *this; + next._pending_proposals.clear(); + + // Copy forward a resumption PSK + next.add_resumption_psk(_group_id, _epoch, _key_schedule.resumption_psk); + + return next; +} + +} // namespace mlspp diff --git a/mlspp/src/tree_math.cpp b/mlspp/src/tree_math.cpp new file mode 100755 index 0000000000..eef9423889 --- /dev/null +++ b/mlspp/src/tree_math.cpp @@ -0,0 +1,223 @@ +#include "mls/tree_math.h" +#include "mls/common.h" + +#include + +static const uint32_t one = 0x01; + +static uint32_t +log2(uint32_t x) +{ + if (x == 0) { + return 0; + } + + uint32_t k = 0; + while ((x >> k) > 0) { + k += 1; + } + return k - 1; +} + +namespace mlspp { + +LeafCount::LeafCount(const NodeCount w) +{ + if (w.val == 0) { + val = 0; + return; + } + + if ((w.val & one) == 0) { + throw InvalidParameterError("Only odd node counts describe trees"); + } + + val = (w.val >> one) + 1; +} + +LeafCount +LeafCount::full(const LeafCount n) +{ + auto w = uint32_t(1); + while (w < n.val) { + w <<= 1U; + } + return LeafCount{ w }; +} + +NodeCount::NodeCount(const LeafCount n) + : UInt32(2 * (n.val - 1) + 1) +{ +} + +LeafIndex::LeafIndex(NodeIndex x) + : UInt32(0) +{ + if (x.val % 2 == 1) { + throw InvalidParameterError("Only even node indices describe leaves"); + } + + val = x.val >> 1; // NOLINT(hicpp-signed-bitwise) +} + +NodeIndex +LeafIndex::ancestor(LeafIndex other) const +{ + auto ln = NodeIndex(*this); + auto rn = NodeIndex(other); + if (ln == rn) { + return ln; + } + + uint8_t k = 0; + while (ln != rn) { + ln.val = ln.val >> 1U; + rn.val = rn.val >> 1U; + k += 1; + } + + const uint32_t prefix = ln.val << k; + const uint32_t stop = (1U << uint8_t(k - 1)); + return NodeIndex{ prefix + (stop - 1) }; +} + +NodeIndex::NodeIndex(LeafIndex x) + : UInt32(2 * x.val) +{ +} + +NodeIndex +NodeIndex::root(LeafCount n) +{ + if (n.val == 0) { + throw std::runtime_error("Root for zero-size tree is undefined"); + } + + auto w = NodeCount(n); + return NodeIndex{ (one << log2(w.val)) - 1 }; +} + +bool +NodeIndex::is_leaf() const +{ + return val % 2 == 0; +} + +bool +NodeIndex::is_below(NodeIndex other) const +{ + auto lx = level(); + auto ly = other.level(); + return lx <= ly && (val >> (ly + 1) == other.val >> (ly + 1)); +} + +NodeIndex +NodeIndex::left() const +{ + if (is_leaf()) { + return *this; + } + + // The clang analyzer doesn't realize that is_leaf() assures that level >= 1 + // NOLINTNEXTLINE(clang-analyzer-core.UndefinedBinaryOperatorResult) + return NodeIndex{ val ^ (one << (level() - 1)) }; +} + +NodeIndex +NodeIndex::right() const +{ + if (is_leaf()) { + return *this; + } + + return NodeIndex{ val ^ (uint32_t(0x03) << (level() - 1)) }; +} + +NodeIndex +NodeIndex::parent() const +{ + auto k = level(); + return NodeIndex{ (val | (one << k)) & ~(one << (k + 1)) }; +} + +NodeIndex +NodeIndex::sibling() const +{ + return sibling(parent()); +} + +NodeIndex +NodeIndex::sibling(NodeIndex ancestor) const +{ + if (!is_below(ancestor)) { + throw InvalidParameterError("Node is not below claimed ancestor"); + } + + auto l = ancestor.left(); + auto r = ancestor.right(); + + if (is_below(l)) { + return r; + } + + return l; +} + +std::vector +NodeIndex::dirpath(LeafCount n) +{ + if (val >= NodeCount(n).val) { + throw InvalidParameterError("Request for dirpath outside of tree"); + } + + auto d = std::vector{}; + + auto r = root(n); + if (*this == r) { + return d; + } + + auto p = parent(); + while (p.val != r.val) { + d.push_back(p); + p = p.parent(); + } + + // Include the root except in a one-member tree + if (val != r.val) { + d.push_back(p); + } + + return d; +} + +std::vector +NodeIndex::copath(LeafCount n) +{ + auto d = dirpath(n); + if (d.empty()) { + return {}; + } + + // Prepend leaf; omit root + d.insert(d.begin(), *this); + d.pop_back(); + + return stdx::transform(d, [](auto x) { return x.sibling(); }); +} + +uint32_t +NodeIndex::level() const +{ + if ((val & one) == 0) { + return 0; + } + + uint32_t k = 0; + while (((val >> k) & one) == 1) { + k += 1; + } + return k; +} + +} // namespace mlspp diff --git a/mlspp/src/treekem.cpp b/mlspp/src/treekem.cpp new file mode 100755 index 0000000000..fe3aebc2a2 --- /dev/null +++ b/mlspp/src/treekem.cpp @@ -0,0 +1,1127 @@ +#include + +#if ENABLE_TREE_DUMP +#include +#endif + +namespace mlspp { + +// Utility method used for removing leaves from a resolution +static void +remove_leaves(std::vector& res, const std::vector& except) +{ + for (const auto& leaf : except) { + auto it = std::find(res.begin(), res.end(), NodeIndex(leaf)); + if (it == res.end()) { + continue; + } + + res.erase(it); + } +} + +/// +/// Node +/// + +const HPKEPublicKey& +Node::public_key() const +{ + const auto get_key = overloaded{ + [](const LeafNode& n) -> const HPKEPublicKey& { return n.encryption_key; }, + [](const ParentNode& n) -> const HPKEPublicKey& { return n.public_key; }, + }; + return var::visit(get_key, node); +} + +std::optional +Node::parent_hash() const +{ + const auto get_leaf_ph = overloaded{ + [](const ParentHash& ph) -> std::optional { return ph.parent_hash; }, + [](const auto& /* other */) -> std::optional { + return std::nullopt; + }, + }; + + const auto get_ph = overloaded{ + [&](const LeafNode& node) -> std::optional { + return var::visit(get_leaf_ph, node.content); + }, + [](const ParentNode& node) -> std::optional { + return node.parent_hash; + }, + }; + + return var::visit(get_ph, node); +} + +/// +/// TreeKEMPrivateKey +/// + +TreeKEMPrivateKey +TreeKEMPrivateKey::solo(CipherSuite suite, + LeafIndex index, + HPKEPrivateKey leaf_priv) +{ + auto priv = TreeKEMPrivateKey{ suite, index, {}, {}, {} }; + priv.private_key_cache.insert({ NodeIndex(index), std::move(leaf_priv) }); + return priv; +} + +TreeKEMPrivateKey +TreeKEMPrivateKey::create(const TreeKEMPublicKey& pub, + LeafIndex from, + const bytes& leaf_secret) +{ + auto priv = TreeKEMPrivateKey{ pub.suite, from, {}, {}, {} }; + priv.implant(pub, NodeIndex(from), leaf_secret); + return priv; +} + +TreeKEMPrivateKey +TreeKEMPrivateKey::joiner(const TreeKEMPublicKey& pub, + LeafIndex index, + HPKEPrivateKey leaf_priv, + NodeIndex intersect, + const std::optional& path_secret) +{ + auto priv = TreeKEMPrivateKey{ pub.suite, index, {}, {}, {} }; + priv.private_key_cache.insert({ NodeIndex(index), std::move(leaf_priv) }); + if (path_secret) { + priv.implant(pub, intersect, opt::get(path_secret)); + } + return priv; +} + +void +TreeKEMPrivateKey::implant(const TreeKEMPublicKey& pub, + NodeIndex start, + const bytes& path_secret) +{ + const auto fdp = pub.filtered_direct_path(start); + auto secret = path_secret; + + path_secrets.insert_or_assign(start, secret); + private_key_cache.erase(start); + + for (const auto& [n, _res] : fdp) { + secret = pub.suite.derive_secret(secret, "path"); + path_secrets.insert_or_assign(n, secret); + private_key_cache.erase(n); + } + + update_secret = pub.suite.derive_secret(secret, "path"); +} + +std::optional +TreeKEMPrivateKey::private_key(NodeIndex n) const +{ + auto pki = private_key_cache.find(n); + if (pki != private_key_cache.end()) { + return pki->second; + } + + auto i = path_secrets.find(n); + if (i == path_secrets.end()) { + return std::nullopt; + } + + auto node_secret = suite.derive_secret(i->second, "node"); + return HPKEPrivateKey::derive(suite, node_secret); +} + +bool +TreeKEMPrivateKey::have_private_key(NodeIndex n) const +{ + auto path_secret = path_secrets.find(n) != path_secrets.end(); + auto cached_priv = private_key_cache.find(n) != private_key_cache.end(); + return path_secret || cached_priv; +} + +std::optional +TreeKEMPrivateKey::private_key(NodeIndex n) +{ + auto priv = static_cast(*this).private_key(n); + if (priv) { + private_key_cache.insert_or_assign(n, opt::get(priv)); + } + return priv; +} + +void +TreeKEMPrivateKey::set_leaf_priv(HPKEPrivateKey priv) +{ + auto n = NodeIndex(index); + path_secrets.erase(n); + private_key_cache.insert_or_assign(n, std::move(priv)); +} + +std::tuple +TreeKEMPrivateKey::shared_path_secret(LeafIndex to) const +{ + auto n = index.ancestor(to); + auto i = path_secrets.find(n); + if (i == path_secrets.end()) { + return std::make_tuple(n, bytes{}, false); + } + + return std::make_tuple(n, i->second, true); +} + +#if ENABLE_TREE_DUMP +// XXX(RLB) This should ultimately be deleted, but it is handy for interop +// debugging, so I'm keeping it around for now. If re-enabled, you'll also need +// to add the appropriate declarations to treekem.h and include + +void +TreeKEMPrivateKey::dump() const +{ + for (const auto& [node, _] : path_secrets) { + private_key(node); + } + + std::cout << "Tree (priv):" << std::endl; + std::cout << " Index: " << NodeIndex(index).val << std::endl; + + std::cout << " Secrets: " << std::endl; + for (const auto& [n, path_secret] : path_secrets) { + auto node_secret = suite.derive_secret(path_secret, "node"); + auto sk = HPKEPrivateKey::derive(suite, node_secret); + + auto psm = to_hex(path_secret).substr(0, 8); + auto pkm = to_hex(sk.public_key.data).substr(0, 8); + std::cout << " " << n.val << " => " << psm << " => " << pkm << std::endl; + } + + std::cout << " Cached key pairs: " << std::endl; + for (const auto& [n, sk] : private_key_cache) { + auto pkm = to_hex(sk.public_key.data).substr(0, 8); + std::cout << " " << n.val << " => " << pkm << std::endl; + } +} + +void +TreeKEMPublicKey::dump() const +{ + std::cout << "Tree:" << std::endl; + auto width = NodeCount(size); + for (auto i = NodeIndex{ 0 }; i.val < width.val; i.val++) { + printf(" %03d : ", i.val); // NOLINT + if (!node_at(i).blank()) { + auto pkRm = to_hex(opt::get(node_at(i).node).public_key().data); + std::cout << pkRm.substr(0, 8); + } else { + std::cout << " "; + } + + std::cout << " | "; + for (uint32_t j = 0; j < i.level(); j++) { + std::cout << " "; + } + + if (!node_at(i).blank()) { + std::cout << "X"; + + if (!i.is_leaf()) { + auto parent = node_at(i).parent_node(); + std::cout << " ["; + for (const auto u : parent.unmerged_leaves) { + std::cout << u.val << ", "; + } + std::cout << "]"; + } + + } else { + std::cout << "_"; + } + + std::cout << std::endl; + } +} +#endif + +void +TreeKEMPrivateKey::decap(LeafIndex from, + const TreeKEMPublicKey& pub, + const bytes& context, + const UpdatePath& path, + const std::vector& except) +{ + // Identify which node in the path secret we will be decrypting + auto ni = NodeIndex(index); + auto dp = pub.filtered_direct_path(NodeIndex(from)); + if (dp.size() != path.nodes.size()) { + throw ProtocolError("Malformed direct path"); + } + + size_t dpi = 0; + auto overlap_node = NodeIndex{}; + auto res = std::vector{}; + for (dpi = 0; dpi < dp.size(); dpi++) { + const auto [dpn, dpres] = dp[dpi]; + if (ni.is_below(dpn)) { + overlap_node = dpn; + res = dpres; + break; + } + } + + if (dpi == dp.size()) { + throw ProtocolError("No overlap in path"); + } + + // Identify which node in the resolution of the copath we will use to decrypt + remove_leaves(res, except); + if (res.size() != path.nodes[dpi].encrypted_path_secret.size()) { + throw ProtocolError("Malformed direct path node"); + } + + size_t resi = 0; + const NodeIndex res_overlap_node; + for (resi = 0; resi < res.size(); resi++) { + if (have_private_key(res[resi])) { + break; + } + } + + if (resi == res.size()) { + throw ProtocolError("No private key to decrypt path secret"); + } + + // Decrypt and implant + auto priv = opt::get(private_key(res[resi])); + auto path_secret = priv.decrypt(suite, + encrypt_label::update_path_node, + context, + path.nodes[dpi].encrypted_path_secret[resi]); + implant(pub, overlap_node, path_secret); + + // Check that the resulting state is consistent with the public key + if (!consistent(pub)) { + throw ProtocolError("TreeKEMPublicKey inconsistent with TreeKEMPrivateKey"); + } +} + +void +TreeKEMPrivateKey::truncate(LeafCount size) +{ + auto ni = NodeIndex(LeafIndex{ size.val - 1 }); + auto to_remove = std::vector{}; + for (const auto& entry : path_secrets) { + if (entry.first.val > ni.val) { + to_remove.push_back(entry.first); + } + } + + for (auto n : to_remove) { + path_secrets.erase(n); + private_key_cache.erase(n); + } +} + +bool +TreeKEMPrivateKey::consistent(const TreeKEMPrivateKey& other) const +{ + if (suite != other.suite) { + return false; + } + + if (update_secret != other.update_secret) { + return false; + } + + const auto match_if_present = [&](const auto& entry) { + auto other_entry = other.path_secrets.find(entry.first); + if (other_entry == other.path_secrets.end()) { + return true; + } + + return entry.second == other_entry->second; + }; + return stdx::all_of(path_secrets, match_if_present); +} + +bool +TreeKEMPrivateKey::consistent(const TreeKEMPublicKey& other) const +{ + if (suite != other.suite) { + return false; + } + + for (const auto& [node, _] : path_secrets) { + private_key(node); + } + + return stdx::all_of(private_key_cache, [other](const auto& entry) { + const auto& [node, priv] = entry; + const auto& opt_node = other.node_at(node).node; + if (!opt_node) { + // It's OK for a TreeKEMPrivateKey to have private keys + // for nodes that are blank in the TreeKEMPublicKey. + // This will happen traniently during Commit + // processing, since proposals will be applied in the + // public tree and not in the private tree. + return true; + } + + const auto& pub = opt::get(opt_node).public_key(); + return priv.public_key == pub; + }); +} + +/// +/// TreeKEMPublicKey +/// + +TreeKEMPublicKey::TreeKEMPublicKey(CipherSuite suite_in) + : suite(suite_in) +{ +} + +LeafIndex +TreeKEMPublicKey::allocate_leaf() +{ + // Find the leftmost blank leaf node + auto index = LeafIndex(0); + while (index.val < size.val && !node_at(index).blank()) { + index.val++; + } + + // Extend the tree if necessary + if (index.val >= size.val) { + if (size.val == 0) { + size.val = 1; + nodes.resize(1); + } else { + size.val *= 2; + nodes.resize(2 * nodes.size() + 1); + } + } + + return index; +} + +LeafIndex +TreeKEMPublicKey::add_leaf(const LeafNode& leaf) +{ + // Check that the leaf node's keys are not already present in the tree + if (exists_in_tree(leaf.encryption_key, std::nullopt)) { + throw InvalidParameterError("Duplicate encryption key"); + } + + if (exists_in_tree(leaf.signature_key, std::nullopt)) { + throw InvalidParameterError("Duplicate signature key"); + } + + // Allocate a blank leaf for this node + const auto index = allocate_leaf(); + + // Set the leaf + node_at(index).node = Node{ leaf }; + + // Update the unmerged list + for (auto& n : NodeIndex(index).dirpath(size)) { + if (!node_at(n).node) { + continue; + } + + auto& parent = var::get(opt::get(node_at(n).node).node); + + // Insert into unmerged leaves while maintaining order + const auto insert_point = stdx::upper_bound(parent.unmerged_leaves, index); + parent.unmerged_leaves.insert(insert_point, index); + } + + clear_hash_path(index); + return index; +} + +void +TreeKEMPublicKey::update_leaf(LeafIndex index, const LeafNode& leaf) +{ + // Check that the leaf node's keys are not already present in the tree, except + // for the signature key, which is allowed to repeat. + if (exists_in_tree(leaf.encryption_key, std::nullopt)) { + throw InvalidParameterError("Duplicate encryption key"); + } + + if (exists_in_tree(leaf.signature_key, index)) { + throw InvalidParameterError("Duplicate signature key"); + } + + blank_path(index); + node_at(NodeIndex(index)).node = Node{ leaf }; + clear_hash_path(index); +} + +void +TreeKEMPublicKey::blank_path(LeafIndex index) +{ + if (nodes.empty()) { + return; + } + + auto ni = NodeIndex(index); + node_at(ni).node.reset(); + for (auto n : ni.dirpath(size)) { + node_at(n).node.reset(); + } + + clear_hash_path(index); +} + +void +TreeKEMPublicKey::merge(LeafIndex from, const UpdatePath& path) +{ + update_leaf(from, path.leaf_node); + + auto dp = filtered_direct_path(NodeIndex(from)); + if (dp.size() != path.nodes.size()) { + throw ProtocolError("Malformed direct path"); + } + + auto ph = parent_hashes(from, dp, path.nodes); + for (size_t i = 0; i < dp.size(); i++) { + auto [n, _res] = dp[i]; + + auto parent_hash = bytes{}; + if (i < dp.size() - 1) { + parent_hash = ph[i + 1]; + } + + node_at(n).node = + Node{ ParentNode{ path.nodes[i].public_key, parent_hash, {} } }; + } + + set_hash_all(); +} + +void +TreeKEMPublicKey::set_hash_all() +{ + auto r = NodeIndex::root(size); + get_hash(r); +} + +bytes +TreeKEMPublicKey::root_hash() const +{ + auto r = NodeIndex::root(size); + if (hashes.count(r) == 0) { + throw InvalidParameterError("Root hash not set"); + } + + return hashes.at(r); +} + +bool +TreeKEMPublicKey::has_parent_hash(NodeIndex child, const bytes& target_ph) const +{ + const auto res = resolve(child); + return stdx::any_of(res, [&](auto nr) { + return opt::get(node_at(nr).node).parent_hash() == target_ph; + }); +} + +bool +TreeKEMPublicKey::parent_hash_valid() const +{ + auto cache = TreeHashCache{}; + + auto width = NodeCount(size); + auto height = NodeIndex::root(size).level(); + for (auto level = uint32_t(1); level <= height; level++) { + auto stride = uint32_t(2) << level; + auto start = NodeIndex{ (stride >> 1U) - 1 }; + + for (auto p = start; p.val < width.val; p.val += stride) { + if (node_at(p).blank()) { + continue; + } + + auto l = p.left(); + auto r = p.right(); + + auto lh = original_parent_hash(cache, p, r); + auto rh = original_parent_hash(cache, p, l); + + if (!has_parent_hash(l, lh) && !has_parent_hash(r, rh)) { + dump(); + return false; + } + } + } + return true; +} + +std::vector +TreeKEMPublicKey::resolve(NodeIndex index) const +{ + auto at_leaf = (index.level() == 0); + if (!node_at(index).blank()) { + auto out = std::vector{ index }; + if (index.is_leaf()) { + return out; + } + + const auto& node = node_at(index); + auto unmerged = + stdx::transform(node.parent_node().unmerged_leaves, + [](LeafIndex x) { return NodeIndex(x); }); + + out.insert(out.end(), unmerged.begin(), unmerged.end()); + return out; + } + + if (at_leaf) { + return {}; + } + + auto l = resolve(index.left()); + auto r = resolve(index.right()); + l.insert(l.end(), r.begin(), r.end()); + return l; +} + +TreeKEMPublicKey::FilteredDirectPath +TreeKEMPublicKey::filtered_direct_path(NodeIndex index) const +{ + auto fdp = FilteredDirectPath{}; + + const auto cp = index.copath(size); + auto last = index; + for (auto n : cp) { + const auto p = n.parent(); + const auto res = resolve(n); + last = p; + if (res.empty()) { + continue; + } + + fdp.emplace_back(p, res); + } + + return fdp; +} + +bool +TreeKEMPublicKey::has_leaf(LeafIndex index) const +{ + return !node_at(index).blank(); +} + +std::optional +TreeKEMPublicKey::find(const LeafNode& leaf) const +{ + for (LeafIndex i{ 0 }; i < size; i.val++) { + const auto& node = node_at(i); + if (!node.blank() && node.leaf_node() == leaf) { + return i; + } + } + + return std::nullopt; +} + +std::optional +TreeKEMPublicKey::leaf_node(LeafIndex index) const +{ + const auto& node = node_at(index); + if (node.blank()) { + return std::nullopt; + } + + return node.leaf_node(); +} + +TreeKEMPrivateKey +TreeKEMPublicKey::update(LeafIndex from, + const bytes& leaf_secret, + const bytes& group_id, + const SignaturePrivateKey& sig_priv, + const LeafNodeOptions& opts) +{ + // Grab information about the sender + const auto& leaf_node = node_at(from); + if (leaf_node.blank()) { + throw InvalidParameterError("Cannot update from blank node"); + } + + // Generate path secrets + auto priv = TreeKEMPrivateKey::create(*this, from, leaf_secret); + auto dp = filtered_direct_path(NodeIndex(from)); + + // Encrypt path secrets to the copath, forming a stub UpdatePath with no + // encryptions + auto path_nodes = stdx::transform(dp, [&](const auto& dpn) { + auto [n, _res] = dpn; + + auto path_secret = priv.path_secrets.at(n); + auto node_priv = opt::get(priv.private_key(n)); + + return UpdatePathNode{ node_priv.public_key, {} }; + }); + + // Update and re-sign the leaf_node + auto ph = parent_hashes(from, dp, path_nodes); + auto ph0 = bytes{}; + if (!ph.empty()) { + ph0 = ph[0]; + } + + auto leaf_pub = opt::get(priv.private_key(NodeIndex(from))).public_key; + auto new_leaf = leaf_node.leaf_node().for_commit( + suite, group_id, from, leaf_pub, ph0, opts, sig_priv); + + // Merge the changes into the tree + merge(from, UpdatePath{ std::move(new_leaf), std::move(path_nodes) }); + + return priv; +} + +UpdatePath +TreeKEMPublicKey::encap(const TreeKEMPrivateKey& priv, + const bytes& context, + const std::vector& except) const +{ + auto dp = filtered_direct_path(NodeIndex(priv.index)); + + // Encrypt path secrets to the copath + auto path_nodes = stdx::transform(dp, [&](const auto& dpn) { + // We need the copy here so that we can modify the resolution. + // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) + auto [n, res] = dpn; + remove_leaves(res, except); + + auto path_secret = priv.path_secrets.at(n); + auto node_priv = opt::get(priv.private_key(n)); + + auto ct = stdx::transform(res, [&](auto nr) { + const auto& node_pub = opt::get(node_at(nr).node).public_key(); + return node_pub.encrypt( + suite, encrypt_label::update_path_node, context, path_secret); + }); + + return UpdatePathNode{ node_priv.public_key, std::move(ct) }; + }); + + // Package everything into an UpdatePath + auto new_leaf = opt::get(leaf_node(priv.index)); + auto path = UpdatePath{ new_leaf, std::move(path_nodes) }; + + return path; +} + +void +TreeKEMPublicKey::truncate() +{ + if (size.val == 0) { + return; + } + + // Clear the parent hashes across blank leaves before truncating + auto index = LeafIndex{ size.val - 1 }; + for (; index.val > 0; index.val--) { + if (!node_at(index).blank()) { + break; + } + clear_hash_path(index); + } + + if (node_at(index).blank()) { + nodes.clear(); + return; + } + + // Remove the right subtree until the tree is of minimal size + while (size.val / 2 > index.val) { + nodes.resize(nodes.size() / 2); + size.val /= 2; + } +} + +OptionalNode& +TreeKEMPublicKey::node_at(NodeIndex n) +{ + auto width = NodeCount(size); + if (n.val >= width.val) { + throw InvalidParameterError("Node index not in tree"); + } + + if (n.val >= nodes.size()) { + return blank_node; + } + + return nodes.at(n.val); +} + +const OptionalNode& +TreeKEMPublicKey::node_at(NodeIndex n) const +{ + auto width = NodeCount(size); + if (n.val >= width.val) { + throw InvalidParameterError("Node index not in tree"); + } + + if (n.val >= nodes.size()) { + return blank_node; + } + + return nodes.at(n.val); +} + +OptionalNode& +TreeKEMPublicKey::node_at(LeafIndex n) +{ + return node_at(NodeIndex(n)); +} + +const OptionalNode& +TreeKEMPublicKey::node_at(LeafIndex n) const +{ + return node_at(NodeIndex(n)); +} + +void +TreeKEMPublicKey::clear_hash_all() +{ + hashes.clear(); +} + +void +TreeKEMPublicKey::clear_hash_path(LeafIndex index) +{ + auto dp = NodeIndex(index).dirpath(size); + hashes.erase(NodeIndex(index)); + for (auto n : dp) { + hashes.erase(n); + } +} + +struct LeafNodeHashInput +{ + LeafIndex leaf_index; + std::optional leaf_node; + TLS_SERIALIZABLE(leaf_index, leaf_node) +}; + +struct ParentNodeHashInput +{ + std::optional parent_node; + const bytes& left_hash; + const bytes& right_hash; + TLS_SERIALIZABLE(parent_node, left_hash, right_hash) +}; + +struct TreeHashInput +{ + var::variant node; + TLS_SERIALIZABLE(node); + TLS_TRAITS(tls::variant) +}; + +const bytes& +TreeKEMPublicKey::get_hash(NodeIndex index) +{ + if (hashes.count(index) > 0) { + return hashes.at(index); + } + + auto hash_input = bytes{}; + const auto& node = node_at(index); + if (index.level() == 0) { + auto input = LeafNodeHashInput{ LeafIndex(index), {} }; + if (!node.blank()) { + input.leaf_node = node.leaf_node(); + } + + hash_input = tls::marshal(TreeHashInput{ input }); + } else { + auto input = ParentNodeHashInput{ + {}, + get_hash(index.left()), + get_hash(index.right()), + }; + + if (!node.blank()) { + input.parent_node = node.parent_node(); + } + + hash_input = tls::marshal(TreeHashInput{ input }); + } + + auto hash = suite.digest().hash(hash_input); + hashes.insert_or_assign(index, hash); + return hashes.at(index); +} + +// struct { +// HPKEPublicKey encryption_key; +// opaque parent_hash; +// opaque original_sibling_tree_hash; +// } ParentHashInput; +struct ParentHashInput +{ + const HPKEPublicKey& public_key; + const bytes& parent_hash; + const bytes& original_child_resolution; + + TLS_SERIALIZABLE(public_key, parent_hash, original_child_resolution) +}; + +bytes +TreeKEMPublicKey::parent_hash(const ParentNode& parent, + NodeIndex copath_child) const +{ + if (hashes.count(copath_child) == 0) { + throw InvalidParameterError("Child hash not set"); + } + + auto hash_input = ParentHashInput{ + parent.public_key, + parent.parent_hash, + hashes.at(copath_child), + }; + + return suite.digest().hash(tls::marshal(hash_input)); +} + +std::vector +TreeKEMPublicKey::parent_hashes( + LeafIndex from, + const FilteredDirectPath& fdp, + const std::vector& path_nodes) const +{ + // An empty filtered direct path indicates a one-member tree, since there's + // nobody else there to encrypt with. In this special case, there's no + // parent hashing to be done. + if (fdp.empty()) { + return {}; + } + + // The list of nodes for whom parent hashes are computed, namely: Direct path + // excluding the last entry, including leaf + auto from_node = NodeIndex(from); + auto dp = fdp; + auto [last, _res_last] = dp.back(); + dp.pop_back(); + dp.insert(dp.begin(), { from_node, {} }); + + if (dp.size() != path_nodes.size()) { + throw ProtocolError("Malformed UpdatePath"); + } + + // Parent hash for all the parents, starting from the last entry of the + // filtered direct path + auto last_hash = bytes{}; + auto ph = std::vector(dp.size()); + for (int i = static_cast(dp.size()) - 1; i >= 0; i--) { + auto [n, _res] = dp[i]; + auto s = n.sibling(last); + + auto parent_node = ParentNode{ path_nodes[i].public_key, last_hash, {} }; + last_hash = parent_hash(parent_node, s); + ph[i] = last_hash; + + last = n; + } + + return ph; +} + +const bytes& +TreeKEMPublicKey::original_tree_hash(TreeHashCache& cache, + NodeIndex index, + std::vector parent_except) const +{ + // Scope the unmerged leaves list down to this subtree + auto except = std::vector{}; + std::copy_if(parent_except.begin(), + parent_except.end(), + std::back_inserter(except), + [&](auto i) { return NodeIndex(i).is_below(index); }); + + auto have_local_changes = !except.empty(); + + // If there are no local changes, then we can use the cached tree hash + if (!have_local_changes) { + return hashes.at(index); + } + + // If this method has been called before with the same number of excluded + // leaves (which implies the same set), then use the cached value. + if (auto it = cache.find(index); it != cache.end()) { + const auto& [key, value] = *it; + const auto& [except_size, hash] = value; + if (except_size == except.size()) { + return hash; + } + } + + // If there is no entry in either cache, recompute the value + auto hash = bytes{}; + if (index.is_leaf()) { + // A leaf node with local changes is by definition excluded from the parent + // hash. So we return the hash of an empty leaf. + auto leaf_hash_input = LeafNodeHashInput{ LeafIndex(index), std::nullopt }; + hash = suite.digest().hash(tls::marshal(TreeHashInput{ leaf_hash_input })); + } else { + // If there is no cached value, recalculate the child hashes with the + // specified `except` list, removing the `except` list from + // `unmerged_leaves`. + auto parent_hash_input = ParentNodeHashInput{ + std::nullopt, + original_tree_hash(cache, index.left(), except), + original_tree_hash(cache, index.right(), except), + }; + + if (!node_at(index).blank()) { + parent_hash_input.parent_node = node_at(index).parent_node(); + auto& unmerged_leaves = + opt::get(parent_hash_input.parent_node).unmerged_leaves; + auto end = std::remove_if( + unmerged_leaves.begin(), unmerged_leaves.end(), [&](auto leaf) { + return std::count(except.begin(), except.end(), leaf) != 0; + }); + unmerged_leaves.erase(end, unmerged_leaves.end()); + } + + hash = + suite.digest().hash(tls::marshal(TreeHashInput{ parent_hash_input })); + } + + cache.insert_or_assign(index, std::make_pair(except.size(), hash)); + return cache.at(index).second; +} + +bytes +TreeKEMPublicKey::original_parent_hash(TreeHashCache& cache, + NodeIndex parent, + NodeIndex sibling) const +{ + const auto& parent_node = node_at(parent).parent_node(); + const auto& unmerged = parent_node.unmerged_leaves; + const auto& sibling_hash = original_tree_hash(cache, sibling, unmerged); + + return suite.digest().hash(tls::marshal(ParentHashInput{ + parent_node.public_key, + parent_node.parent_hash, + sibling_hash, + })); +} + +bool +TreeKEMPublicKey::parent_hash_valid(LeafIndex from, + const UpdatePath& path) const +{ + auto fdp = filtered_direct_path(NodeIndex(from)); + auto hash_chain = parent_hashes(from, fdp, path.nodes); + auto leaf_ph = + var::visit(overloaded{ + [](const ParentHash& ph) -> std::optional { + return ph.parent_hash; + }, + [](const auto& /* other */) -> std::optional { + return std::nullopt; + }, + }, + path.leaf_node.content); + + // If there are no nodes to hash, then ParentHash MUST be omitted + if (hash_chain.empty()) { + return !leaf_ph; + } + + return leaf_ph && opt::get(leaf_ph) == hash_chain[0]; +} + +bool +TreeKEMPublicKey::exists_in_tree(const HPKEPublicKey& key, + std::optional except) const +{ + return any_leaf([&](auto i, const auto& node) { + return i != except && node.encryption_key == key; + }); +} + +bool +TreeKEMPublicKey::exists_in_tree(const SignaturePublicKey& key, + std::optional except) const +{ + return any_leaf([&](auto i, const auto& node) { + return i != except && node.signature_key == key; + }); +} + +tls::ostream& +operator<<(tls::ostream& str, const TreeKEMPublicKey& obj) +{ + // Empty tree + if (obj.size.val == 0) { + return str << std::vector{}; + } + + LeafIndex cut = LeafIndex{ obj.size.val - 1 }; + while (cut.val > 0 && obj.node_at(cut).blank()) { + cut.val -= 1; + } + + const auto begin = obj.nodes.begin(); + const auto end = begin + NodeIndex(cut).val + 1; + const auto view = std::vector(begin, end); + return str << view; +} + +tls::istream& +operator>>(tls::istream& str, TreeKEMPublicKey& obj) +{ + // Read the node list + str >> obj.nodes; + if (obj.nodes.empty()) { + return str; + } + + // Verify that the tree is well-formed and minimal + if (obj.nodes.size() % 2 == 0) { + throw ProtocolError("Malformed ratchet tree: even number of nodes"); + } + + if (obj.nodes.back().blank()) { + throw ProtocolError("Ratchet tree does not use minimal encoding"); + } + + // Adjust the size value to fit the non-blank nodes + obj.size.val = 1; + while (NodeCount(obj.size).val < obj.nodes.size()) { + obj.size.val *= 2; + } + + // Add blank nodes to the end + obj.nodes.resize(NodeCount(obj.size).val); + + // Verify the basic structure of the tree is sane + for (size_t i = 0; i < obj.nodes.size(); i++) { + if (obj.nodes[i].blank()) { + continue; + } + + const auto& node = opt::get(obj.nodes[i].node).node; + auto at_leaf = (i % 2 == 0); + auto holds_leaf = var::holds_alternative(node); + auto holds_parent = var::holds_alternative(node); + + if (at_leaf && !holds_leaf) { + throw InvalidParameterError("Parent node in leaf node position"); + } + + if (!at_leaf && !holds_parent) { + throw InvalidParameterError("Leaf node in parent node position"); + } + } + + return str; +} + +} // namespace mlspp diff --git a/src/davetest/dave.cpp b/src/davetest/dave.cpp new file mode 100644 index 0000000000..94812e0d89 --- /dev/null +++ b/src/davetest/dave.cpp @@ -0,0 +1,88 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ +#include +#include +#include + +std::string get_testdata_dir() { + char *env_var = getenv("TEST_DATA_DIR"); + return (env_var ? env_var : "../../testdata/"); +} + +std::vector load_test_audio() { + std::vector testaudio; + std::string dir = get_testdata_dir(); + std::ifstream input (dir + "Robot.pcm", std::ios::in|std::ios::binary|std::ios::ate); + if (input.is_open()) { + size_t testaudio_size = input.tellg(); + testaudio.resize(testaudio_size); + input.seekg(0, std::ios::beg); + input.read((char*)testaudio.data(), testaudio_size); + input.close(); + } + else { + std::cout << "ERROR: Can't load " + dir + "Robot.pcm\n"; + exit(1); + } + return testaudio; +} + +int main() { + + using namespace std::chrono_literals; + char* t = getenv("DPP_UNIT_TEST_TOKEN"); + if (t == nullptr || getenv("TEST_GUILD_ID") == nullptr || getenv("TEST_VC_ID") == nullptr) { + std::cerr << "Missing unit test environment. Set DPP_UNIT_TEST_TOKEN, TEST_GUILD_ID, and TEST_VC_ID\n"; + exit(1); + } + dpp::snowflake TEST_GUILD_ID(std::string(getenv("TEST_GUILD_ID"))); + dpp::snowflake TEST_VC_ID(std::string(getenv("TEST_VC_ID"))); + std::cout << "Test Guild ID: " << TEST_GUILD_ID << " Test VC ID: " << TEST_VC_ID << "\n\n"; + dpp::cluster dave_test(t, dpp::i_default_intents, 1, 0, 1, false, dpp::cache_policy_t{ dpp::cp_none, dpp::cp_none, dpp::cp_none, dpp::cp_none, dpp::cp_none }); + + dave_test.on_log([&](const dpp::log_t& log) { + std::cout << "[" << dpp::utility::current_date_time() << "] " << dpp::utility::loglevel(log.severity) << ": " << log.message << std::endl; + }); + + std::vector testaudio = load_test_audio(); + + dave_test.on_voice_ready([&](const dpp::voice_ready_t & event) { + dave_test.log(dpp::ll_info, "Voice channel ready, sending audio..."); + dpp::discord_voice_client* v = event.voice_client; + if (v && v->is_ready()) { + v->send_audio_raw((uint16_t*)testaudio.data(), testaudio.size()); + } + dave_test.start_timer([v, &testaudio](auto) { + v->send_audio_raw((uint16_t*)testaudio.data(), testaudio.size()); + }, 15); + }); + + + dave_test.on_guild_create([&](const dpp::guild_create_t & event) { + if (event.created->id == TEST_GUILD_ID) { + dpp::discord_client* s = dave_test.get_shard(0); + bool muted = false, deaf = false, enable_dave = true; + s->connect_voice(TEST_GUILD_ID, TEST_VC_ID, muted, deaf, enable_dave); + } + }); + dave_test.start(false); +} diff --git a/src/dpp/application.cpp b/src/dpp/application.cpp index fc78a08842..ae8ca1c4f7 100644 --- a/src/dpp/application.cpp +++ b/src/dpp/application.cpp @@ -22,10 +22,7 @@ #include #include #include -#include -#include #include -#include namespace dpp { @@ -110,6 +107,7 @@ application& application::fill_from_json_impl(nlohmann::json* j) { set_iconhash_not_null(j, "cover_image", cover_image); set_int32_not_null(j, "flags", flags); set_int64_not_null(j, "approximate_guild_count", approximate_guild_count); + set_int64_not_null(j, "approximate_user_install_count", approximate_user_install_count); if (j->contains("redirect_uris")) { for (const auto& uri : (*j)["redirect_uris"]) { @@ -187,5 +185,4 @@ std::string application::get_icon_url(uint16_t size, const image_type format) co return ""; } -} // namespace dpp - +} diff --git a/src/dpp/auditlog.cpp b/src/dpp/auditlog.cpp index 9abc95f121..2dcf8fb99f 100644 --- a/src/dpp/auditlog.cpp +++ b/src/dpp/auditlog.cpp @@ -74,5 +74,5 @@ auditlog& auditlog::fill_from_json_impl(nlohmann::json* j) { return *this; } -} // namespace dpp +} diff --git a/src/dpp/automod.cpp b/src/dpp/automod.cpp index 219d347859..0492f49a97 100644 --- a/src/dpp/automod.cpp +++ b/src/dpp/automod.cpp @@ -186,5 +186,5 @@ json automod_rule::to_json_impl(bool with_id) const { return j; } -} // namespace dpp +} diff --git a/src/dpp/ban.cpp b/src/dpp/ban.cpp index aee056b68e..a697b35c70 100644 --- a/src/dpp/ban.cpp +++ b/src/dpp/ban.cpp @@ -40,5 +40,5 @@ ban& ban::fill_from_json_impl(nlohmann::json* j) { return *this; } -} // namespace dpp +} diff --git a/src/dpp/cache.cpp b/src/dpp/cache.cpp index 715b829dd8..256e4e16f0 100644 --- a/src/dpp/cache.cpp +++ b/src/dpp/cache.cpp @@ -21,10 +21,8 @@ ************************************************************************************/ #include #include -#include #include #include -#include namespace dpp { @@ -85,4 +83,4 @@ cache_helper(role, role_cache, find_role, get_role_cache, get_role_count); cache_helper(guild, guild_cache, find_guild, get_guild_cache, get_guild_count); cache_helper(emoji, emoji_cache, find_emoji, get_emoji_cache, get_emoji_count); -} // namespace dpp +} diff --git a/src/dpp/channel.cpp b/src/dpp/channel.cpp index 4903b60f81..a12be0f59d 100644 --- a/src/dpp/channel.cpp +++ b/src/dpp/channel.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -580,4 +579,4 @@ forum_layout_type channel::get_default_forum_layout() const { } -} // namespace dpp +} diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index 46aec35a5d..90d4cb12c0 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -21,17 +21,9 @@ #include #include #include -#include -#include -#include -#include -#include -#include #include #include #include -#include -#include namespace dpp { @@ -120,6 +112,40 @@ cluster::cluster(const std::string &_token, uint32_t _intents, uint32_t _shards, i_message_content, "You have attached an event to cluster::on_message_update() but have not specified the privileged intent dpp::i_message_content. Message content, embeds, attachments, and components on received guild messages will be empty.") ); + + /* Add slashcommand callback for named commands. */ +#ifdef DPP_CORO + on_slashcommand([this](const slashcommand_t& event) -> task { + slashcommand_handler_variant copy; + { + std::shared_lock lk(named_commands_mutex); + auto it = named_commands.find(event.command.get_command_name()); + if (it == named_commands.end()) { + co_return; + } + copy = it->second; + } + if (std::holds_alternative(copy)) { + co_await std::get(copy)(event); + } else if (std::holds_alternative(copy)) { + std::get(copy)(event); + } + co_return; + }); +#else + on_slashcommand([this](const slashcommand_t& event) { + slashcommand_handler_t copy; + { + std::shared_lock lk(named_commands_mutex); + auto it = named_commands.find(event.command.get_command_name()); + if (it == named_commands.end()) { + return; + } + copy = it->second; + } + copy(event); + }); +#endif } cluster::~cluster() @@ -154,6 +180,11 @@ void cluster::log(dpp::loglevel severity, const std::string &msg) const { dpp::log_t logmsg(nullptr, msg); logmsg.severity = severity; logmsg.message = msg; + size_t pos{0}; + while ((pos = logmsg.message.find(token, pos)) != std::string::npos) { + logmsg.message.replace(pos, token.length(), "*****"); + pos += 5; + } on_log.call(logmsg); } } @@ -461,4 +492,15 @@ cluster& cluster::set_request_timeout(uint16_t timeout) { return *this; } +bool cluster::register_command(const std::string &name, const slashcommand_handler_t handler) { + std::unique_lock lk(named_commands_mutex); + auto [_, inserted] = named_commands.try_emplace(name, handler); + return inserted; +} + +bool cluster::unregister_command(const std::string &name) { + std::unique_lock lk(named_commands_mutex); + return named_commands.erase(name) == 1; +} + }; diff --git a/src/dpp/cluster/appcommand.cpp b/src/dpp/cluster/appcommand.cpp index 80c3308447..ae131da0ab 100644 --- a/src/dpp/cluster/appcommand.cpp +++ b/src/dpp/cluster/appcommand.cpp @@ -191,4 +191,4 @@ void cluster::interaction_followup_get_original(const std::string &token, comman rest_request(this, API_PATH "/webhooks",std::to_string(me.id), utility::url_encode(token) + "/messages/@original", m_get, "", callback); } -} // namespace dpp +} diff --git a/src/dpp/cluster/automod.cpp b/src/dpp/cluster/automod.cpp index f4584baab7..c0d2d534aa 100644 --- a/src/dpp/cluster/automod.cpp +++ b/src/dpp/cluster/automod.cpp @@ -43,4 +43,4 @@ void cluster::automod_rule_delete(snowflake guild_id, snowflake rule_id, command rest_request(this, API_PATH "/guilds", std::to_string(guild_id), "/auto-moderation/rules/" + std::to_string(rule_id), m_delete, "", callback); } -} // namespace dpp +} diff --git a/src/dpp/cluster/channel.cpp b/src/dpp/cluster/channel.cpp index 4e0e41cc80..83e317f71c 100644 --- a/src/dpp/cluster/channel.cpp +++ b/src/dpp/cluster/channel.cpp @@ -101,4 +101,4 @@ void cluster::channel_set_voice_status(snowflake channel_id, const std::string& rest_request(this, API_PATH "/channels", std::to_string(channel_id), "voice-status", m_put, j.dump(-1, ' ', false, json::error_handler_t::replace), callback); } -} // namespace dpp +} diff --git a/src/dpp/cluster/confirmation.cpp b/src/dpp/cluster/confirmation.cpp index 07072ccaf2..6e48b138a2 100644 --- a/src/dpp/cluster/confirmation.cpp +++ b/src/dpp/cluster/confirmation.cpp @@ -161,4 +161,4 @@ error_info confirmation_callback_t::get_error() const { return {}; } -} // namespace dpp +} diff --git a/src/dpp/cluster/dm.cpp b/src/dpp/cluster/dm.cpp index cba8af9654..be895f7690 100644 --- a/src/dpp/cluster/dm.cpp +++ b/src/dpp/cluster/dm.cpp @@ -63,4 +63,4 @@ void cluster::gdm_remove(snowflake channel_id, snowflake user_id, command_comple rest_request(this, API_PATH "/channels", std::to_string(channel_id), "recipients/" + std::to_string(user_id), m_delete, "", callback); } -} // namespace dpp +} diff --git a/src/dpp/cluster/emoji.cpp b/src/dpp/cluster/emoji.cpp index 626707d13c..9e2f2b8461 100644 --- a/src/dpp/cluster/emoji.cpp +++ b/src/dpp/cluster/emoji.cpp @@ -81,4 +81,4 @@ void cluster::application_emoji_delete(snowflake emoji_id, command_completion_ev rest_request(this, API_PATH "/applications", me.id.str(), "emojis/" + emoji_id.str(), m_delete, "", callback); } -} // namespace dpp +} diff --git a/src/dpp/cluster/entitlement.cpp b/src/dpp/cluster/entitlement.cpp index d2b120772e..fa96e4c2b1 100644 --- a/src/dpp/cluster/entitlement.cpp +++ b/src/dpp/cluster/entitlement.cpp @@ -81,4 +81,4 @@ void cluster::entitlement_consume(const class snowflake entitlement_id, command_ rest_request(this, API_PATH "/applications", me.id.str(), "entitlements/" + entitlement_id.str() + "/consume", m_post, "", callback); } -} // namespace dpp +} diff --git a/src/dpp/cluster/gateway.cpp b/src/dpp/cluster/gateway.cpp index 7da7a81dd4..85bcfd6ca1 100644 --- a/src/dpp/cluster/gateway.cpp +++ b/src/dpp/cluster/gateway.cpp @@ -26,4 +26,4 @@ void cluster::get_gateway_bot(command_completion_event_t callback) { rest_request(this, API_PATH "/gateway", "bot", "", m_get, "", callback); } -} // namespace dpp +} diff --git a/src/dpp/cluster/guild.cpp b/src/dpp/cluster/guild.cpp index bf58372f69..75396c907b 100644 --- a/src/dpp/cluster/guild.cpp +++ b/src/dpp/cluster/guild.cpp @@ -169,4 +169,4 @@ void cluster::guild_edit_welcome_screen(snowflake guild_id, const struct welcome } -} // namespace dpp +} diff --git a/src/dpp/cluster/guild_member.cpp b/src/dpp/cluster/guild_member.cpp index 28e3b953b0..8ab690ea94 100644 --- a/src/dpp/cluster/guild_member.cpp +++ b/src/dpp/cluster/guild_member.cpp @@ -152,4 +152,4 @@ void cluster::guild_search_members(snowflake guild_id, const std::string& query, }); } -} // namespace dpp +} diff --git a/src/dpp/cluster/invite.cpp b/src/dpp/cluster/invite.cpp index 5d73f6aed7..1e03ed6555 100644 --- a/src/dpp/cluster/invite.cpp +++ b/src/dpp/cluster/invite.cpp @@ -35,4 +35,4 @@ void cluster::invite_get(const std::string &invite_code, command_completion_even rest_request(this, API_PATH "/invites", utility::url_encode(invite_code) + "?with_counts=true&with_expiration=true", "", m_get, "", callback); } -} // namespace dpp +} diff --git a/src/dpp/cluster/json_interface.cpp b/src/dpp/cluster/json_interface.cpp index 76863321ce..3af8435841 100644 --- a/src/dpp/cluster/json_interface.cpp +++ b/src/dpp/cluster/json_interface.cpp @@ -25,4 +25,4 @@ namespace dpp { -} // namespace dpp +} diff --git a/src/dpp/cluster/message.cpp b/src/dpp/cluster/message.cpp index e1d4943b59..b1922902a1 100644 --- a/src/dpp/cluster/message.cpp +++ b/src/dpp/cluster/message.cpp @@ -208,4 +208,4 @@ void cluster::channel_pins_get(snowflake channel_id, command_completion_event_t rest_request_list(this, API_PATH "/channels", std::to_string(channel_id), "pins", m_get, "", callback); } -} // namespace dpp +} diff --git a/src/dpp/cluster/role.cpp b/src/dpp/cluster/role.cpp index 2a2065a8f2..371c6256bb 100644 --- a/src/dpp/cluster/role.cpp +++ b/src/dpp/cluster/role.cpp @@ -70,4 +70,4 @@ void cluster::user_application_role_connection_update(snowflake application_id, rest_request(this, API_PATH "/users/@me/applications", std::to_string(application_id), "role-connection", m_put, connection.build_json(), callback); } -} // namespace dpp +} diff --git a/src/dpp/cluster/scheduled_event.cpp b/src/dpp/cluster/scheduled_event.cpp index ddc7ea16c6..fbf91ca18c 100644 --- a/src/dpp/cluster/scheduled_event.cpp +++ b/src/dpp/cluster/scheduled_event.cpp @@ -68,4 +68,4 @@ void cluster::guild_event_get(snowflake guild_id, snowflake event_id, command_co rest_request(this, API_PATH "/guilds", std::to_string(guild_id), "/scheduled-events/" + std::to_string(event_id) + "?with_user_count=true", m_get, "", callback); } -} // namespace dpp +} diff --git a/src/dpp/cluster/sku.cpp b/src/dpp/cluster/sku.cpp index 20f23f3e77..2c44c30c01 100644 --- a/src/dpp/cluster/sku.cpp +++ b/src/dpp/cluster/sku.cpp @@ -27,4 +27,4 @@ void cluster::skus_get(command_completion_event_t callback) { rest_request_list(this, API_PATH "/applications", me.id.str(), "entitlements", m_get, "", callback); } -} // namespace dpp +} diff --git a/src/dpp/cluster/stage_instance.cpp b/src/dpp/cluster/stage_instance.cpp index 9257f90a6d..ac9146d568 100644 --- a/src/dpp/cluster/stage_instance.cpp +++ b/src/dpp/cluster/stage_instance.cpp @@ -39,4 +39,4 @@ void cluster::stage_instance_delete(const snowflake channel_id, command_completi rest_request(this, API_PATH "/stage-instances", std::to_string(channel_id), "", m_delete, "", callback); } -} // namespace dpp +} diff --git a/src/dpp/cluster/sticker.cpp b/src/dpp/cluster/sticker.cpp index 9f61521a44..bc5ba5a0b5 100644 --- a/src/dpp/cluster/sticker.cpp +++ b/src/dpp/cluster/sticker.cpp @@ -55,4 +55,4 @@ void cluster::sticker_packs_get(command_completion_event_t callback) { rest_request_list(this, API_PATH "/sticker-packs", "", "", m_get, "", callback); } -} // namespace dpp +} diff --git a/src/dpp/cluster/template.cpp b/src/dpp/cluster/template.cpp index f0503ff83f..bf01b136a2 100644 --- a/src/dpp/cluster/template.cpp +++ b/src/dpp/cluster/template.cpp @@ -60,4 +60,4 @@ void cluster::template_get(const std::string &code, command_completion_event_t c rest_request(this, API_PATH "/guilds", "templates", code, m_get, "", callback); } -} // namespace dpp +} diff --git a/src/dpp/cluster/thread.cpp b/src/dpp/cluster/thread.cpp index b3c109406e..bf2205b51b 100644 --- a/src/dpp/cluster/thread.cpp +++ b/src/dpp/cluster/thread.cpp @@ -191,4 +191,4 @@ void cluster::thread_get(snowflake thread_id, command_completion_event_t callbac rest_request(this, API_PATH "/channels", std::to_string(thread_id), "", m_get, "", callback); } -} // namespace dpp +} diff --git a/src/dpp/cluster/timer.cpp b/src/dpp/cluster/timer.cpp index e6d7df1814..a6520306f2 100644 --- a/src/dpp/cluster/timer.cpp +++ b/src/dpp/cluster/timer.cpp @@ -146,4 +146,4 @@ oneshot_timer::~oneshot_timer() { cancel(); } -} // namespace dpp +} diff --git a/src/dpp/cluster/user.cpp b/src/dpp/cluster/user.cpp index 671fdb64a9..616c5c101f 100644 --- a/src/dpp/cluster/user.cpp +++ b/src/dpp/cluster/user.cpp @@ -81,6 +81,10 @@ void cluster::current_user_set_voice_state(snowflake guild_id, snowflake channel rest_request(this, API_PATH "/guilds", std::to_string(guild_id), "/voice-states/@me", m_patch, j.dump(-1, ' ', false, json::error_handler_t::replace), callback); } +void cluster::current_user_get_voice_state(snowflake guild_id, command_completion_event_t callback) { + rest_request(this, API_PATH "/guilds", std::to_string(guild_id), "/voice-states/@me", m_get, "", callback); +} + void cluster::user_set_voice_state(snowflake user_id, snowflake guild_id, snowflake channel_id, bool suppress, command_completion_event_t callback) { json j({ {"channel_id", channel_id}, @@ -89,6 +93,10 @@ void cluster::user_set_voice_state(snowflake user_id, snowflake guild_id, snowfl rest_request(this, API_PATH "/guilds", std::to_string(guild_id), "/voice-states/" + std::to_string(user_id), m_patch, j.dump(-1, ' ', false, json::error_handler_t::replace), callback); } +void cluster::user_get_voice_state(snowflake guild_id, snowflake user_id, command_completion_event_t callback) { + rest_request(this, API_PATH "/guilds", std::to_string(guild_id), "/voice-states/" + std::to_string(user_id), m_get, "", callback); +} + void cluster::current_user_connections_get(command_completion_event_t callback) { rest_request_list(this, API_PATH "/users", "@me", "connections", m_get, "", callback); } @@ -121,4 +129,4 @@ void cluster::user_get_cached(snowflake user_id, command_completion_event_t call rest_request(this, API_PATH "/users", std::to_string(user_id), "", m_get, "", callback); } -} // namespace dpp +} diff --git a/src/dpp/cluster/voice.cpp b/src/dpp/cluster/voice.cpp index ea78a487a4..c99bc45303 100644 --- a/src/dpp/cluster/voice.cpp +++ b/src/dpp/cluster/voice.cpp @@ -32,4 +32,4 @@ void cluster::guild_get_voice_regions(snowflake guild_id, command_completion_eve rest_request_list(this, API_PATH "/guilds", std::to_string(guild_id), "regions", m_get, "", callback); } -} // namespace dpp +} diff --git a/src/dpp/cluster/webhook.cpp b/src/dpp/cluster/webhook.cpp index 5231d19e40..8ebf9370e4 100644 --- a/src/dpp/cluster/webhook.cpp +++ b/src/dpp/cluster/webhook.cpp @@ -117,4 +117,4 @@ void cluster::get_webhook_with_token(snowflake webhook_id, const std::string &to rest_request(this, API_PATH "/webhooks", std::to_string(webhook_id), utility::url_encode(token), m_get, "", callback); } -} // namespace dpp +} diff --git a/src/dpp/cluster_coro_calls.cpp b/src/dpp/cluster_coro_calls.cpp index d56b3b1013..3ec3aeff93 100644 --- a/src/dpp/cluster_coro_calls.cpp +++ b/src/dpp/cluster_coro_calls.cpp @@ -783,10 +783,18 @@ async cluster::co_current_user_set_voice_state(snowflak return async{ this, static_cast(&cluster::current_user_set_voice_state), guild_id, channel_id, suppress, request_to_speak_timestamp }; } +async cluster::co_current_user_get_voice_state(snowflake guild_id) { + return async{ this, static_cast(&cluster::current_user_get_voice_state), guild_id }; +} + async cluster::co_user_set_voice_state(snowflake user_id, snowflake guild_id, snowflake channel_id, bool suppress) { return async{ this, static_cast(&cluster::user_set_voice_state), user_id, guild_id, channel_id, suppress }; } +async cluster::co_user_get_voice_state(snowflake guild_id, snowflake user_id) { + return async{ this, static_cast(&cluster::user_get_voice_state), guild_id, user_id }; +} + async cluster::co_current_user_connections_get() { return async{ this, static_cast(&cluster::current_user_connections_get) }; } diff --git a/src/dpp/cluster_sync_calls.cpp b/src/dpp/cluster_sync_calls.cpp index 8aa69aba77..c49c9d9a3e 100644 --- a/src/dpp/cluster_sync_calls.cpp +++ b/src/dpp/cluster_sync_calls.cpp @@ -781,10 +781,18 @@ confirmation cluster::current_user_set_voice_state_sync(snowflake guild_id, snow return dpp::sync(this, static_cast(&cluster::current_user_set_voice_state), guild_id, channel_id, suppress, request_to_speak_timestamp); } +voicestate cluster::current_user_get_voice_state_sync(snowflake guild_id) { + return dpp::sync(this, static_cast(&cluster::current_user_get_voice_state), guild_id); +} + confirmation cluster::user_set_voice_state_sync(snowflake user_id, snowflake guild_id, snowflake channel_id, bool suppress) { return dpp::sync(this, static_cast(&cluster::user_set_voice_state), user_id, guild_id, channel_id, suppress); } +voicestate cluster::user_get_voice_state_sync(snowflake guild_id, snowflake user_id) { + return dpp::sync(this, static_cast(&cluster::user_get_voice_state), guild_id, user_id); +} + connection_map cluster::current_user_connections_get_sync() { return dpp::sync(this, static_cast(&cluster::current_user_connections_get)); } diff --git a/src/dpp/commandhandler.cpp b/src/dpp/commandhandler.cpp index e337b0eee9..9101bbd477 100644 --- a/src/dpp/commandhandler.cpp +++ b/src/dpp/commandhandler.cpp @@ -20,7 +20,6 @@ ************************************************************************************/ #include #include -#include #include #include #include @@ -437,4 +436,4 @@ void commandhandler::thonk(command_source source, command_completion_event_t cal thinking(source, callback); } -} // namespace dpp +} diff --git a/src/dpp/dave/array_view.h b/src/dpp/dave/array_view.h new file mode 100755 index 0000000000..e1e9c18c80 --- /dev/null +++ b/src/dpp/dave/array_view.h @@ -0,0 +1,119 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#pragma once + +#include + +namespace dpp::dave { + +/** + * @brief This is a simple wrapper around a range of values, e.g. a vector or array. + * It is only constant if type T is constant. + * @tparam T Type in array or vector + */ +template class array_view { +public: + /** + * @brief Default constructor + */ + array_view() = default; + + /** + * @brief Construct array_view with data and size of data + * @param data data pointer to array + * @param size size of array + */ + array_view(T* data, size_t size) : array(data), array_size(size) { + } + + /** + * @brief Get size of view + * @return size + */ + size_t size() const { + return array_size; + } + + /** + * @brief Get data of view from first element + * @return data + */ + T* data() const { + return array; + } + + /** + * @brief Get start of view, first element + * @return first element + */ + T* begin() const { + return array; + } + + /** + * @brief Get ending iterator of view, 1+last element + * @return end of view + */ + T* end() const { + return array + array_size; + } + +private: + /** + * @brief array data + */ + T* array = nullptr; + /** + * @brief Array size + */ + size_t array_size = 0; +}; + +/** + * @brief Construct new array view from C style array + * @tparam T array member type + * @param data pointer to array + * @param size size of array + * @return array_view + */ +template +inline array_view make_array_view(T* data, size_t size) +{ + return array_view(data, size); +} + +/** + * @brief Construct new array view from vector + * @tparam T vector member type + * @param data vector + * @return array_view + */ +template +inline array_view make_array_view(std::vector& data) +{ + return array_view(data.data(), data.size()); +} + +} diff --git a/src/dpp/dave/cipher_interface.cpp b/src/dpp/dave/cipher_interface.cpp new file mode 100755 index 0000000000..58784c51bc --- /dev/null +++ b/src/dpp/dave/cipher_interface.cpp @@ -0,0 +1,37 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#include "cipher_interface.h" +#include "openssl_aead_cipher.h" +#include + +namespace dpp::dave { + +std::unique_ptr create_cipher(dpp::cluster& cl, const encryption_key& key) +{ + auto cipher = std::make_unique(cl, key); + return cipher->is_valid() ? std::move(cipher) : nullptr; +} + +} diff --git a/src/dpp/dave/cipher_interface.h b/src/dpp/dave/cipher_interface.h new file mode 100755 index 0000000000..44004883e8 --- /dev/null +++ b/src/dpp/dave/cipher_interface.h @@ -0,0 +1,102 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ + #pragma once + +#include + +#include "common.h" +#include "array_view.h" + +namespace dpp { + class cluster; +} + +namespace dpp::dave { + +/** + * @brief An array_view constant array of bytes + */ +using const_byte_view = array_view; + +/** + * @brief An array_view non-constant array of bytes + */ +using byte_view = array_view; + +/** + * @brief Represents a block cipher with AEAD used to encrypt or decrypt + * audio and video frames in the DAVE protocol. + */ +class cipher_interface { // NOLINT +public: + /** + * @brief Create cipher interface + * @param _creator Creating cluster + */ + cipher_interface(dpp::cluster& _creator) : creator(_creator) { }; + + /** + * @brief Default destructor + */ + virtual ~cipher_interface() = default; + + /** + * @brief Encrypt audio or video + * @param ciphertext_buffer_out Output buffer of ciphertext + * @param plaintext_buffer Input buffer for plaintext + * @param nonce_buffer Input nonce/IV + * @param additional_data Additional data for GCM AEAD encryption + * @param tag_buffer_out AEAD Tag for verification + * @return true if encryption succeeded, false if it failed + */ + virtual bool encrypt(byte_view ciphertext_buffer_out, const_byte_view plaintext_buffer, const_byte_view nonce_buffer, const_byte_view additional_data, byte_view tag_buffer_out) = 0; + + /** + * @brief Decrypt audio or video + * @param plaintext_buffer_out Output buffer for plaintext + * @param ciphertext_buffer Input buffer for ciphetext + * @param tag_buffer AEAD Tag for verification + * @param nonce_buffer Nonce/IV + * @param additional_data Additional data for GCM AEAD encryption + * @return true if decryption succeeded, false if it failed + */ + virtual bool decrypt(byte_view plaintext_buffer_out, const_byte_view ciphertext_buffer, const_byte_view tag_buffer, const_byte_view nonce_buffer, const_byte_view additional_data) = 0; + +protected: + + /** + * @brief DPP Cluster, used for logging + */ + dpp::cluster& creator; +}; + +/** + * @brief Factory function to create new cipher interface of the best supported type for DAVE + * @param key encryption key + * @return an instance of a class derived from cipher_interface + */ +std::unique_ptr create_cipher(dpp::cluster& cl, const encryption_key& key); + +} diff --git a/src/dpp/dave/clock.h b/src/dpp/dave/clock.h new file mode 100755 index 0000000000..db93afd914 --- /dev/null +++ b/src/dpp/dave/clock.h @@ -0,0 +1,77 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#pragma once + +#include + +namespace dpp::dave { + +/** + * @brief An interface for a wrapper around chrono clocks + */ +class clock_interface { +public: + /** + * @brief chrono steady clock + */ + using base_clock = std::chrono::steady_clock; + + /** + * @brief time point on a steady clock + */ + using time_point = base_clock::time_point; + + /** + * @brief duration on a steady clock + */ + using clock_duration = base_clock::duration; + + /** + * @brief Default destructor + */ + virtual ~clock_interface() = default; + + /** + * @brief Get current time + * @return current time + */ + virtual time_point now() const = 0; +}; + +/** + * @brief Chrono clock class + */ +class clock : public clock_interface { +public: + /** + * @brief Get current time + * @return current time + */ + time_point now() const override { + return base_clock::now(); + } +}; + +} diff --git a/src/dpp/dave/codec_utils.cpp b/src/dpp/dave/codec_utils.cpp new file mode 100755 index 0000000000..3cae886066 --- /dev/null +++ b/src/dpp/dave/codec_utils.cpp @@ -0,0 +1,418 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#include "codec_utils.h" + +#include +#include +#include +#include "leb128.h" + +namespace dpp::dave::codec_utils { + +unencrypted_frame_header_size bytes_covering_h264_pps(const uint8_t* payload, const uint64_t size_remaining) { + // the payload starts with three exponential golomb encoded values + // (first_mb_in_slice, sps_id, pps_id) + // the depacketizer needs the pps_id unencrypted + // and the payload has RBSP encoding that we need to work around + + constexpr uint8_t emulation_prevention_byte = 0x03; + + uint64_t payload_bit_index = 0; + auto zero_bit_count = 0; + auto parsed_exp_golomb_values = 0; + + while (payload_bit_index < size_remaining * 8 && parsed_exp_golomb_values < 3) { + auto bit_index = payload_bit_index % 8; + auto byte_index = payload_bit_index / 8; + auto payload_byte = payload[byte_index]; + + // if we're starting a new byte + // check if this is an emulation prevention byte + // which we skip over + if (bit_index == 0) { + if (byte_index >= 2 && payload_byte == emulation_prevention_byte && payload[byte_index - 1] == 0 && payload[byte_index - 2] == 0) { + payload_bit_index += 8; + continue; + } + } + + if ((payload_byte & (1 << (7 - bit_index))) == 0) { + // still in the run of leading zero bits + ++zero_bit_count; + ++payload_bit_index; + + if (zero_bit_count >= 32) { + throw dpp::length_exception("Unexpectedly large exponential golomb encoded value"); + } + } else { + // we hit a one + // skip forward the number of bits dictated by the leading number of zeroes + parsed_exp_golomb_values += 1; + payload_bit_index += 1 + zero_bit_count; + zero_bit_count = 0; + } + } + + // return the number of bytes that covers the last exp golomb encoded value + auto result = (payload_bit_index / 8) + 1; + if (result > std::numeric_limits::max()) { + // bytes covering H264 PPS result cannot fit into unencrypted frame header size + return 0; + } + return static_cast(result); +} + +const uint8_t nalu_long_start_code[] = {0, 0, 0, 1}; +constexpr uint8_t nalu_short_start_sequence_size = 3; + +using index_start_code_size_pair = std::pair; + +std::optional next_h26x_nalu_index(const uint8_t* buffer, const size_t buffer_size, const size_t search_start_index = 0) +{ + constexpr uint8_t start_code_highest_possible_value = 1; + constexpr uint8_t start_code_end_byte_value = 1; + constexpr uint8_t start_code_leading_bytes_value = 0; + + if (buffer_size < nalu_short_start_sequence_size) { + return std::nullopt; + } + + // look for NAL unit 3 or 4 byte start code + for (size_t i = search_start_index; i < buffer_size - nalu_short_start_sequence_size;) { + if (buffer[i + 2] > start_code_highest_possible_value) { + // third byte is not 0 or 1, can't be a start code + i += nalu_short_start_sequence_size; + } else if (buffer[i + 2] == start_code_end_byte_value) { + // third byte matches the start code end byte, might be a start code sequence + if (buffer[i + 1] == start_code_leading_bytes_value && buffer[i] == start_code_leading_bytes_value) { + // confirmed start sequence {0, 0, 1} + auto nal_unit_start_index = i + nalu_short_start_sequence_size; + + if (i >= 1 && buffer[i - 1] == start_code_leading_bytes_value) { + // 4 byte start code + return std::optional({nal_unit_start_index, 4}); + } + else { + // 3 byte start code + return std::optional({nal_unit_start_index, 3}); + } + } + + i += nalu_short_start_sequence_size; + } else { + // third byte is 0, might be a four byte start code + ++i; + } + } + + return std::nullopt; +} + +bool process_frame_opus(outbound_frame_processor& processor, array_view frame) +{ + processor.add_encrypted_bytes(frame.data(), frame.size()); + return true; +} + +bool process_frame_vp8(outbound_frame_processor& processor, array_view frame) +{ + constexpr uint8_t key_frame_unencrypted_bytes = 10; + constexpr uint8_t delta_frame_unencrypted_bytes = 1; + + // parse the VP8 payload header to determine if it's a key frame + // https://datatracker.ietf.org/doc/html/rfc7741#section-4.3 + + // 0 1 2 3 4 5 6 7 + // +-+-+-+-+-+-+-+-+ + // |Size0|H| VER |P| + // +-+-+-+-+-+-+-+-+ + // P is an inverse key frame flag + + // if this is a key frame the depacketizer will read 10 bytes into the payload header + // if this is a delta frame the depacketizer only needs the first byte of the payload + // header (since that's where the key frame flag is) + + size_t unencrypted_header_bytes = 0; + if ((frame.data()[0] & 0x01) == 0) { + unencrypted_header_bytes = key_frame_unencrypted_bytes; + } else { + unencrypted_header_bytes = delta_frame_unencrypted_bytes; + } + + processor.add_unencrypted_bytes(frame.data(), unencrypted_header_bytes); + processor.add_encrypted_bytes(frame.data() + unencrypted_header_bytes, frame.size() - unencrypted_header_bytes); + return true; +} + +bool process_frame_vp9(outbound_frame_processor& processor, array_view frame) +{ + // payload descriptor is unencrypted in each packet + // and includes all information the depacketizer needs + processor.add_encrypted_bytes(frame.data(), frame.size()); + return true; +} + +bool process_frame_h264(outbound_frame_processor& processor, array_view frame) +{ + // minimize the amount of unencrypted header data for H264 depending on the NAL unit + // type from WebRTC, see: src/modules/rtp_rtcp/source/rtp_format_h264.cc + // src/common_video/h264/h264_common.cc + // src/modules/rtp_rtcp/source/video_rtp_depacketizer_h264.cc + + constexpr uint8_t nal_header_type_mask = 0x1F; + constexpr uint8_t nal_type_slice = 1; + constexpr uint8_t nal_type_idr = 5; + constexpr uint8_t nal_unit_header_size = 1; + + // this frame can be packetized as a STAP-A or a FU-A + // so we need to look at the first NAL units to determine how many bytes + // the packetizer/depacketizer will need into the payload + if (frame.size() < nalu_short_start_sequence_size + nal_unit_header_size) { + throw dpp::length_exception("H264 frame is too small to contain a NAL unit"); + } + + auto nalu_index_pair = next_h26x_nalu_index(frame.data(), frame.size()); + while (nalu_index_pair && nalu_index_pair->first < frame.size() - 1) { + auto [nal_unit_start_index, start_code_size] = *nalu_index_pair; + + auto nal_type = frame.data()[nal_unit_start_index] & nal_header_type_mask; + + // copy the start code and then the NAL unit + + // Because WebRTC will convert them all start codes to 4-byte on the receiver side + // always write a long start code and then the NAL unit + processor.add_unencrypted_bytes(nalu_long_start_code, sizeof(nalu_long_start_code)); + + auto next_nalu_index_pair = next_h26x_nalu_index(frame.data(), frame.size(), nal_unit_start_index); + auto next_nalu_start = next_nalu_index_pair.has_value() ? next_nalu_index_pair->first - next_nalu_index_pair->second : frame.size(); + + if (nal_type == nal_type_slice || nal_type == nal_type_idr) { + // once we've hit a slice or an IDR + // we just need to cover getting to the PPS ID + auto nal_unit_payload_start = nal_unit_start_index + nal_unit_header_size; + auto nal_unit_pps_bytes = bytes_covering_h264_pps(frame.data() + nal_unit_payload_start, frame.size() - nal_unit_payload_start); + + processor.add_unencrypted_bytes(frame.data() + nal_unit_start_index, nal_unit_header_size + nal_unit_pps_bytes); + processor.add_encrypted_bytes(frame.data() + nal_unit_start_index + nal_unit_header_size + nal_unit_pps_bytes, next_nalu_start - nal_unit_start_index - nal_unit_header_size - nal_unit_pps_bytes); + } else { + // copy the whole NAL unit + processor.add_unencrypted_bytes(frame.data() + nal_unit_start_index, next_nalu_start - nal_unit_start_index); + } + nalu_index_pair = next_nalu_index_pair; + } + + return true; +} + +bool process_frame_h265(outbound_frame_processor& processor, array_view frame) +{ + // minimize the amount of unencrypted header data for H265 depending on the NAL unit + // type from WebRTC, see: src/modules/rtp_rtcp/source/rtp_format_h265.cc + // src/common_video/h265/h265_common.cc + // src/modules/rtp_rtcp/source/video_rtp_depacketizer_h265.cc + + constexpr uint8_t nal_header_type_mask = 0x7E; + constexpr uint8_t nal_type_vcl_cutoff = 32; + constexpr uint8_t nal_unit_header_size = 2; + + // this frame can be packetized as a STAP-A or a FU-A + // so we need to look at the first NAL units to determine how many bytes + // the packetizer/depacketizer will need into the payload + if (frame.size() < nalu_short_start_sequence_size + nal_unit_header_size) { + throw dpp::length_exception("H265 frame is too small to contain a NAL unit"); + } + + // look for NAL unit 3 or 4 byte start code + auto nalu_index = next_h26x_nalu_index(frame.data(), frame.size()); + while (nalu_index && nalu_index->first < frame.size() - 1) { + auto [nal_unit_start_index, start_code_size] = *nalu_index; + + uint8_t nal_type = (frame.data()[nal_unit_start_index] & nal_header_type_mask) >> 1; + + // copy the start code and then the NAL unit + + // Because WebRTC will convert them all start codes to 4-byte on the receiver side + // always write a long start code and then the NAL unit + processor.add_unencrypted_bytes(nalu_long_start_code, sizeof(nalu_long_start_code)); + + auto next_nalu_index_pair = next_h26x_nalu_index(frame.data(), frame.size(), nal_unit_start_index); + auto next_nalu_start = next_nalu_index_pair.has_value() ? next_nalu_index_pair->first - next_nalu_index_pair->second : frame.size(); + + if (nal_type < nal_type_vcl_cutoff) { + // found a VCL NAL, encrypt the payload only + processor.add_unencrypted_bytes(frame.data() + nal_unit_start_index, nal_unit_header_size); + processor.add_encrypted_bytes(frame.data() + nal_unit_start_index + nal_unit_header_size, next_nalu_start - nal_unit_start_index - nal_unit_header_size); + } else { + // copy the whole NAL unit + processor.add_unencrypted_bytes(frame.data() + nal_unit_start_index, next_nalu_start - nal_unit_start_index); + } + + nalu_index = next_nalu_index_pair; + } + + return true; +} + +bool process_frame_av1(outbound_frame_processor& processor, array_view frame) +{ + constexpr uint8_t obu_header_has_extension_mask = 0b0'0000'100; + constexpr uint8_t obu_header_has_size_mask = 0b0'0000'010; + constexpr uint8_t obu_header_type_mask = 0b0'1111'000; + constexpr uint8_t obu_type_temporal_delimiter = 2; + constexpr uint8_t obu_type_tile_list = 8; + constexpr uint8_t obu_type_padding = 15; + constexpr uint8_t obu_extension_size_bytes = 1; + + size_t i = 0; + while (i < frame.size()) { + // Read the OBU header. + size_t obu_header_index = i; + uint8_t obu_header = frame.data()[obu_header_index]; + i += sizeof(obu_header); + + bool obu_has_extension = obu_header & obu_header_has_extension_mask; + bool obu_has_size = obu_header & obu_header_has_size_mask; + int obu_type = (obu_header & obu_header_type_mask) >> 3; + + if (obu_has_extension) { + // Skip extension byte + i += obu_extension_size_bytes; + } + + if (i >= frame.size()) { + // Malformed frame + throw dpp::logic_exception("Malformed AV1 frame: header overflows frame"); + } + + size_t obu_payload_size = 0; + if (obu_has_size) { + // Read payload size + const uint8_t* start = frame.data() + i; + const uint8_t* ptr = start; + obu_payload_size = read_leb128(ptr, frame.end()); + if (!ptr) { + // Malformed frame + throw dpp::logic_exception("Malformed AV1 frame: invalid LEB128 size"); + } + i += ptr - start; + } + else { + // If the size is not present, the OBU extends to the end of the frame. + obu_payload_size = frame.size() - i; + } + + const auto obu_payload_index = i; + + if (i + obu_payload_size > frame.size()) { + // Malformed frame + throw dpp::logic_exception("Malformed AV1 frame: payload overflows frame"); + } + + i += obu_payload_size; + + // We only copy the OBUs that will not get dropped by the packetizer + if (obu_type != obu_type_temporal_delimiter && obu_type != obu_type_tile_list && obu_type != obu_type_padding) { + // if this is the last OBU, we may need to flip the "has size" bit + // which allows us to append necessary protocol data to the frame + bool rewritten_without_size = false; + + if (i == frame.size() && obu_has_size) { + // Flip the "has size" bit + obu_header &= ~obu_header_has_size_mask; + rewritten_without_size = true; + } + + // write the OBU header unencrypted + processor.add_unencrypted_bytes(&obu_header, sizeof(obu_header)); + if (obu_has_extension) { + // write the extension byte unencrypted + processor.add_unencrypted_bytes(frame.data() + obu_header_index + sizeof(obu_header), obu_extension_size_bytes); + } + + // write the OBU payload size unencrypted if it was present and we didn't rewrite + // without it + if (obu_has_size && !rewritten_without_size) { + // The AMD AV1 encoder may pad LEB128 encoded sizes with a zero byte which the + // webrtc packetizer removes. To prevent the packetizer from changing the frame, + // we sanitize the size by re-writing it ourselves + uint8_t leb128Buffer[LEB128_MAX_SIZE]; + size_t additionalBytesToWrite = write_leb128(obu_payload_size, leb128Buffer); + processor.add_unencrypted_bytes(leb128Buffer, additionalBytesToWrite); + } + + // add the OBU payload, encrypted + processor.add_encrypted_bytes(frame.data() + obu_payload_index, obu_payload_size); + } + } + + return true; +} + +bool validate_encrypted_frame(outbound_frame_processor& processor, array_view frame) +{ + auto codec = processor.get_codec(); + if (codec != codec::cd_h264 && codec != codec::cd_h265) { + return true; + } + + constexpr size_t padding = nalu_short_start_sequence_size - 1; + + const auto& unencrypted_ranges = processor.get_unencrypted_ranges(); + + // H264 and H265 ciphertexts cannot contain a 3 or 4 byte start code {0, 0, 1} + // otherwise the packetizer gets confused + // and the frame we get on the decryption side will be shifted and fail to decrypt + size_t encrypted_section_start = 0; + for (auto& range : unencrypted_ranges) { + if (encrypted_section_start == range.offset) { + encrypted_section_start += range.size; + continue; + } + + auto start = encrypted_section_start - std::min(encrypted_section_start, size_t{padding}); + auto end = std::min(range.offset + padding, frame.size()); + if (next_h26x_nalu_index(frame.data() + start, end - start)) { + return false; + } + + encrypted_section_start = range.offset + range.size; + } + + if (encrypted_section_start == frame.size()) { + return true; + } + + auto start = encrypted_section_start - std::min(encrypted_section_start, size_t{padding}); + auto end = frame.size(); + if (next_h26x_nalu_index(frame.data() + start, end - start)) { + return false; + } + + return true; +} + +} + + diff --git a/src/dpp/dave/codec_utils.h b/src/dpp/dave/codec_utils.h new file mode 100755 index 0000000000..44ae51c418 --- /dev/null +++ b/src/dpp/dave/codec_utils.h @@ -0,0 +1,94 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#pragma once + +#include "common.h" +#include "frame_processors.h" +#include "array_view.h" + +/** + * @brief Functions for processing specific frame types. + * Different types of audio/video frames have different rules for what parts of it + * must remain unencrypted to allow for routing and processing further up the chain. + */ +namespace dpp::dave::codec_utils { + +/** + * @brief process opus audio frame + * @param processor outbound frame processor + * @param frame frame bytes + * @return true if frame could be processed + */ +bool process_frame_opus(outbound_frame_processor & processor, array_view frame); + +/** + * @brief process VP8 video frame + * @param processor outbound frame processor + * @param frame frame bytes + * @return true if frame could be processed + */ +bool process_frame_vp8(outbound_frame_processor & processor, array_view frame); + +/** + * @brief process VP9 video frame + * @param processor outbound frame processor + * @param frame frame bytes + * @return true if frame could be processed + */ +bool process_frame_vp9(outbound_frame_processor & processor, array_view frame); + +/** + * @brief process H264 video frame + * @param processor outbound frame processor + * @param frame frame bytes + * @return true if frame could be processed + */ +bool process_frame_h264(outbound_frame_processor & processor, array_view frame); + +/** + * @brief process opus frame + * @param processor outbound frame processor + * @param frame frame bytes + * @return true if frame could be processed + */ +bool process_frame_h265(outbound_frame_processor & processor, array_view frame); + +/** + * @brief process opus frame + * @param processor outbound frame processor + * @param frame frame bytes + * @return true if frame could be processed + */ +bool process_frame_av1(outbound_frame_processor & processor, array_view frame); + +/** + * @brief Check if encrypted frame is valid + * @param processor outbound frame processor + * @param frame frame to validate + * @return true if frame could be validated + */ +bool validate_encrypted_frame(outbound_frame_processor& processor, array_view frame); + +} diff --git a/src/dpp/dave/common.h b/src/dpp/dave/common.h new file mode 100755 index 0000000000..62efe5bb60 --- /dev/null +++ b/src/dpp/dave/common.h @@ -0,0 +1,168 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "version.h" + +namespace mlspp::bytes_ns { + /** + * @brief bytes type + */ + struct bytes; +}; + +namespace dpp::dave { + +/** + * @brief Unencrypted frame header size + */ +using unencrypted_frame_header_size = uint16_t; + +/** + * @brief Truncated sync nonce + */ +using truncated_sync_nonce = uint32_t; + +/** + * @brief Magic marker + */ +using magic_marker = uint16_t; + +/** + * @brief Encryption key + */ +using encryption_key = ::mlspp::bytes_ns::bytes; + +/** + * @brief Transition ID + */ +using transition_id = uint16_t; + +/** + * @brief Supplemental bytes size + */ +using supplemental_bytes_size = uint8_t; + +/** + * @brief Media frame types + */ +enum media_type : uint8_t { media_audio, media_video }; + + +/** + * @brief Media codec types + */ +enum codec : uint8_t { cd_unknown, cd_opus, cd_vp8, cd_vp9, cd_h264, cd_h265, cd_av1 }; + +/** + * @brief Returned in std::variant when a message is hard-rejected and should trigger a reset + */ +struct failed_t {}; + +/** + * @brief Returned in std::variant when a message is soft-rejected and should not trigger a reset + */ +struct ignored_t {}; + +/** + * @briefMap of ID-key pairs. + * In process_commit, this lists IDs whose keys have been added, changed, or removed; + * an empty value value means a key was removed. + */ +using roster_map = std::map>; + +/** + * @brief Return type for functions producing RosterMap or hard or soft failures + */ +using roster_variant = std::variant; + +/** + * @brief Magic marker ID + */ +constexpr magic_marker MARKER_BYTES = 0xFAFA; + +/** + * Layout constants + */ +constexpr size_t AES_GCM_128_KEY_BYTES = 16; +constexpr size_t AES_GCM_128_NONCE_BYTES = 12; +constexpr size_t AES_GCM_128_TRUNCATED_SYNC_NONCE_BYTES = 4; +constexpr size_t AES_GCM_128_TRUNCATED_SYNC_NONCE_OFFSET = AES_GCM_128_NONCE_BYTES - AES_GCM_128_TRUNCATED_SYNC_NONCE_BYTES; +constexpr size_t AES_GCM_127_TRUNCATED_TAG_BYTES = 8; +constexpr size_t RATCHET_GENERATION_BYTES = 1; +constexpr size_t RATCHET_GENERATION_SHIFT_BITS = 8 * (AES_GCM_128_TRUNCATED_SYNC_NONCE_BYTES - RATCHET_GENERATION_BYTES); +constexpr size_t SUPPLEMENTAL_BYTES = AES_GCM_127_TRUNCATED_TAG_BYTES + sizeof(supplemental_bytes_size) + sizeof(magic_marker); +constexpr size_t TRANSFORM_PADDING_BYTES = 64; + +/** + * Timing constants + */ +constexpr auto DEFAULT_TRANSITION_EXPIRY = std::chrono::seconds(10); +constexpr auto CIPHER_EXPIRY = std::chrono::seconds(10); + +/** + * Behavior constants + */ +constexpr auto INIT_TRANSITION_ID = 0; +constexpr auto DISABLED_VERSION = 0; +constexpr auto MAX_GENERATION_GAP = 250; +constexpr auto MAX_MISSING_NONCES = 1000; +constexpr auto GENERATION_WRAP = 1 << (8 * RATCHET_GENERATION_BYTES); +constexpr auto MAX_FRAMES_PER_SECOND = 50 + 2 * 60; // 50 audio frames + 2 * 60fps video streams +constexpr std::array OPUS_SILENCE_PACKET = {0xF8, 0xFF, 0xFE}; + +// Utility routine for variant return types + +/** + * @brief Utility to get variant return types wrapped in an optional + * @tparam T type to get from variant + * @tparam V type for the optional + * @param variant variant to get from + * @return value retrieved or nullopt if not found + */ +template inline std::optional get_optional(V&& variant) +{ + if (auto map = std::get_if(&variant)) { + if constexpr (std::is_rvalue_reference_v) { + return std::move(*map); + } else { + return *map; + } + } + else { + return std::nullopt; + } +} + +} // namespace dpp::dave + diff --git a/src/dpp/dave/cryptor_manager.cpp b/src/dpp/dave/cryptor_manager.cpp new file mode 100755 index 0000000000..050d3be3cc --- /dev/null +++ b/src/dpp/dave/cryptor_manager.cpp @@ -0,0 +1,192 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#include "cryptor_manager.h" +#include +#include "key_ratchet.h" +#include +#include + +using namespace std::chrono_literals; + +namespace dpp::dave { + +key_generation compute_wrapped_generation(key_generation oldest, key_generation generation) +{ + // Assume generation is greater than or equal to oldest, this may be wrong in a few cases but + // will be caught by the max generation gap check. + auto remainder = oldest % GENERATION_WRAP; + auto factor = oldest / GENERATION_WRAP + (generation < remainder ? 1 : 0); + return factor * GENERATION_WRAP + generation; +} + +big_nonce compute_wrapped_big_nonce(key_generation generation, truncated_sync_nonce nonce) +{ + // Remove the generation bits from the nonce + auto masked_nonce = nonce & ((1 << RATCHET_GENERATION_SHIFT_BITS) - 1); + // Add the wrapped generation bits back in + return static_cast(generation) << RATCHET_GENERATION_SHIFT_BITS | masked_nonce; +} + +aead_cipher_manager::aead_cipher_manager(dpp::cluster& cl, const clock_interface& clock, std::unique_ptr key_ratchet) + : current_clock(clock), current_key_ratchet(std::move(key_ratchet)), ratchet_creation(clock.now()), ratchet_expiry(time_point::max()), creator(cl) { +} + +bool aead_cipher_manager::can_process_nonce(key_generation generation, truncated_sync_nonce nonce) const +{ + if (!newest_processed_nonce) { + return true; + } + + auto wrapped_big_nonce = compute_wrapped_big_nonce(generation, nonce); + return wrapped_big_nonce > *newest_processed_nonce || std::find(missing_nonces.rbegin(), missing_nonces.rend(), wrapped_big_nonce) != missing_nonces.rend(); +} + +cipher_interface* aead_cipher_manager::get_cipher(key_generation generation) +{ + cleanup_expired_ciphers(); + + if (generation < oldest_generation) { + creator.log(dpp::ll_trace, "Received frame with old generation: " + std::to_string(generation) + ", oldest generation: " + std::to_string(oldest_generation)); + return nullptr; + } + + if (generation > newest_generation + MAX_GENERATION_GAP) { + creator.log(dpp::ll_trace, "Received frame with future generation: " + std::to_string(generation) + ", newest generation: " + std::to_string(newest_generation)); + return nullptr; + } + + auto ratchet_lifetime_sec = + std::chrono::duration_cast(current_clock.now() - ratchet_creation).count(); + auto max_lifetime_frames = MAX_FRAMES_PER_SECOND * ratchet_lifetime_sec; + auto max_lifetime_generations = max_lifetime_frames >> RATCHET_GENERATION_SHIFT_BITS; + if (generation > max_lifetime_generations) { + creator.log(dpp::ll_debug, "Received frame with generation " + std::to_string(generation) + " beyond ratchet max lifetime generations: " + std::to_string(max_lifetime_generations) + ", ratchet lifetime: " + std::to_string(ratchet_lifetime_sec) + "s"); + return nullptr; + } + + auto it = cryptor_generations.find(generation); + if (it == cryptor_generations.end()) { + // We don't have a cryptor for this generation, create one + std::tie(it, std::ignore) = cryptor_generations.emplace(generation, make_expiring_cipher(generation)); + } + + // Return a non-owning pointer to the cryptor + auto& [cryptor, expiry] = it->second; + return cryptor.get(); +} + +void aead_cipher_manager::report_cipher_success(key_generation generation, truncated_sync_nonce nonce) +{ + auto wrapped_big_nonce = compute_wrapped_big_nonce(generation, nonce); + + // Add any missing nonces to the queue + if (!newest_processed_nonce) { + newest_processed_nonce = wrapped_big_nonce; + } + else if (wrapped_big_nonce > *newest_processed_nonce) { + auto oldest_missing_nonce = wrapped_big_nonce > MAX_MISSING_NONCES ? wrapped_big_nonce - MAX_MISSING_NONCES : 0; + + while (!missing_nonces.empty() && missing_nonces.front() < oldest_missing_nonce) { + missing_nonces.pop_front(); + } + + // If we're missing a lot, we don't want to add everything since newestProcessedNonce_ + auto missing_range_start = std::max(oldest_missing_nonce, *newest_processed_nonce + 1); + for (auto i = missing_range_start; i < wrapped_big_nonce; ++i) { + missing_nonces.push_back(i); + } + + // Update the newest processed nonce + newest_processed_nonce = wrapped_big_nonce; + } + else { + auto it = std::find(missing_nonces.begin(), missing_nonces.end(), wrapped_big_nonce); + if (it != missing_nonces.end()) { + missing_nonces.erase(it); + } + } + + if (generation <= newest_generation || cryptor_generations.find(generation) == cryptor_generations.end()) { + return; + } + creator.log(dpp::ll_trace, "Reporting cryptor success, generation: " + std::to_string(generation)); + newest_generation = generation; + + // Update the expiry time for all old cryptors + const auto expiry_time = current_clock.now() + CIPHER_EXPIRY; + for (auto& [gen, cryptor] : cryptor_generations) { + if (gen < newest_generation) { + creator.log(dpp::ll_trace, "Updating expiry for cryptor, generation: " + std::to_string(gen)); + cryptor.expiry = std::min(cryptor.expiry, expiry_time); + } + } +} + +key_generation aead_cipher_manager::compute_wrapped_generation(key_generation generation) +{ + return ::dpp::dave::compute_wrapped_generation(oldest_generation, generation); +} + +aead_cipher_manager::expiring_cipher aead_cipher_manager::make_expiring_cipher(key_generation generation) +{ + // Get the new key from the ratchet + auto key = current_key_ratchet->get_key(generation); + auto expiry_time = time_point::max(); + + // If we got frames out of order, we might have to create a cryptor for an old generation + // In that case, create it with a non-infinite expiry time as we have already transitioned + // to a newer generation + if (generation < newest_generation) { + creator.log(dpp::ll_debug, "Creating cryptor for old generation: " + std::to_string(generation)); + expiry_time = current_clock.now() + CIPHER_EXPIRY; + } + else { + creator.log(dpp::ll_debug, "Creating cryptor for new generation: " + std::to_string(generation)); + } + + return {create_cipher(creator, key), expiry_time}; +} + +void aead_cipher_manager::cleanup_expired_ciphers() +{ + for (auto it = cryptor_generations.begin(); it != cryptor_generations.end();) { + auto& [generation, cryptor] = *it; + + bool expired = cryptor.expiry < current_clock.now(); + if (expired) { + creator.log(dpp::ll_trace, "Removing expired cryptor, generation: " + std::to_string(generation)); + } + + it = expired ? cryptor_generations.erase(it) : ++it; + } + + while (oldest_generation < newest_generation && cryptor_generations.find(oldest_generation) == cryptor_generations.end()) { + creator.log(dpp::ll_trace, "Deleting key for old generation: " + std::to_string(oldest_generation)); + current_key_ratchet->delete_key(oldest_generation); + ++oldest_generation; + } +} + +} diff --git a/src/dpp/dave/cryptor_manager.h b/src/dpp/dave/cryptor_manager.h new file mode 100755 index 0000000000..731c145234 --- /dev/null +++ b/src/dpp/dave/cryptor_manager.h @@ -0,0 +1,205 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#pragma once + +#include +#include +#include +#include + +#include "cipher_interface.h" +#include "key_ratchet.h" +#include "common.h" +#include "clock.h" + +namespace dpp { + class cluster; +} + +namespace dpp::dave { + +/** + * @brief Compute wrapped generation for key + * @param oldest oldest key generation + * @param generation key generation + * @return wrapped computed generation + */ +key_generation compute_wrapped_generation(key_generation oldest, key_generation generation); + +/** + * @brief A big nonce (64 bits) + */ +using big_nonce = uint64_t; + +/** + * @brief Compute wrapped big nonce + * @param generation generation + * @param nonce truncated sync nonce + * @return big nonce (64 bits) + */ +big_nonce compute_wrapped_big_nonce(key_generation generation, truncated_sync_nonce nonce); + +/** + * @brief A manager to handle whichever cipher is best for the current version of DAVE + * + * This will currently instantiate an AES 128 GCM AEAD cipher. + */ +class aead_cipher_manager { +public: + /** + * @brief Chrono time point + */ + using time_point = typename clock_interface::time_point; + + /** + * @brief Constructor + * @param cl Creating cluster + * @param clock chrono clock + * @param key_ratchet key ratchet for cipher + */ + aead_cipher_manager(dpp::cluster& cl, const clock_interface& clock, std::unique_ptr key_ratchet); + + /** + * @brief Update cipher expiry + * @param expiry expiry time + */ + void update_expiry(time_point expiry) { + ratchet_expiry = expiry; + } + + /** + * @brief True if cipher has expired + * @return true if expired + */ + bool is_expired() const { + return current_clock.now() > ratchet_expiry; + } + + /** + * @brief True if nonce can be processed for generation + * @param generation key generation + * @param nonce nonce/IV + * @return true if can be processed + */ + bool can_process_nonce(key_generation generation, truncated_sync_nonce nonce) const; + + /** + * Compute wrapped generation for key + * @param generation key generation + * @return key generation + */ + key_generation compute_wrapped_generation(key_generation generation); + + cipher_interface* get_cipher(key_generation generation); + + /** + * @brief Updates the expiry time for all old ciphers + * @param generation key generation + * @param nonce nonce/IV + */ + void report_cipher_success(key_generation generation, truncated_sync_nonce nonce); + +private: + /** + * @brief Cipher with an expiry date/time + */ + struct expiring_cipher { + /** + * @brief Cipher + */ + std::unique_ptr cryptor; + + /** + * @brief Expiry time + */ + time_point expiry; + }; + + /** + * Create a cipher with an expiry time + * @param generation key generation + * @return expiring cipher + */ + expiring_cipher make_expiring_cipher(key_generation generation); + + /** + * @brief Clean up old expired ciphers + */ + void cleanup_expired_ciphers(); + + /** + * @brief chrono clock + */ + const clock_interface& current_clock; + + /** + * @brief key ratchet for cryptor + */ + std::unique_ptr current_key_ratchet; + + /** + * @brief Cryptor for each generation with expiry + */ + std::unordered_map cryptor_generations; + + /** + * @brief Time ratchet was created + */ + time_point ratchet_creation; + + /** + * @brief Time ratchet expired + */ + time_point ratchet_expiry; + + /** + * @brief Oldest generation for ratchet + */ + key_generation oldest_generation{0}; + + /** + * @brief Newest generation for ratchet + */ + key_generation newest_generation{0}; + + /** + * @brief Newest nonce + */ + std::optional newest_processed_nonce; + + /** + * @brief List of missing nonces from sequence + */ + std::deque missing_nonces; + + /** + * @brief DPP Cluster, used for logging + */ + dpp::cluster& creator; + +}; + +} // namespace dpp::dave + diff --git a/src/dpp/dave/decryptor.cpp b/src/dpp/dave/decryptor.cpp new file mode 100755 index 0000000000..847b647eca --- /dev/null +++ b/src/dpp/dave/decryptor.cpp @@ -0,0 +1,220 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#include "decryptor.h" +#include +#include +#include +#include "common.h" +#include "leb128.h" +#include "scope_exit.h" + +using namespace std::chrono_literals; + +namespace dpp::dave { + +void decryptor::transition_to_key_ratchet(std::unique_ptr key_ratchet, duration transition_expiry) +{ + if (key_ratchet) { + creator.log(dpp::ll_trace, "Transitioning to new key ratchet, expiry: " + std::to_string(transition_expiry.count())); + } + + // Update the expiry time for all existing cryptor managers + update_cryptor_manager_expiry(transition_expiry); + + if (key_ratchet) { + cryptor_managers.emplace_back(creator, current_clock, std::move(key_ratchet)); + } +} + +void decryptor::transition_to_passthrough_mode(bool passthrough_mode, duration transition_expiry) +{ + if (passthrough_mode) { + allow_pass_through_until = time_point::max(); + } + else { + // Update the pass through mode expiry + auto max_expiry = current_clock.now() + transition_expiry; + allow_pass_through_until = std::min(allow_pass_through_until, max_expiry); + } +} + +size_t decryptor::decrypt(media_type this_media_type, array_view encrypted_frame, array_view frame) +{ + if (this_media_type != media_audio && this_media_type != media_video) { + creator.log(dpp::ll_trace, "decrypt failed, invalid media type: " + std::to_string(static_cast(this_media_type))); + return 0; + } + + auto start = current_clock.now(); + + auto local_frame = get_or_create_frame_processor(); + scope_exit cleanup([&] { return_frame_processor(std::move(local_frame)); }); + + // Skip decrypting for silence frames + if (this_media_type == media_audio && encrypted_frame.size() == OPUS_SILENCE_PACKET.size() && std::memcmp(encrypted_frame.data(), OPUS_SILENCE_PACKET.data(), OPUS_SILENCE_PACKET.size()) == 0) { + creator.log(dpp::ll_trace, "decrypt skipping silence of size: " + std::to_string(encrypted_frame.size())); + if (encrypted_frame.data() != frame.data()) { + std::memcpy(frame.data(), encrypted_frame.data(), encrypted_frame.size()); + } + return encrypted_frame.size(); + } + + // Remove any expired cryptor manager + cleanup_expired_cryptor_managers(); + + // Process the incoming frame + // This will check whether it looks like a valid encrypted frame + // and if so it will parse it into its different components + local_frame->parse_frame(encrypted_frame); + + // If the frame is not encrypted and we can pass it through, do it + bool can_use_pass_through = allow_pass_through_until > start; + if (!local_frame->is_encrypted() && can_use_pass_through) { + if (encrypted_frame.data() != frame.data()) { + std::memcpy(frame.data(), encrypted_frame.data(), encrypted_frame.size()); + } + stats[this_media_type].passthroughs++; + return encrypted_frame.size(); + } + + // If the frame is not encrypted, and we can't pass it through, fail + if (!local_frame->is_encrypted()) { + creator.log(dpp::ll_warning, "decrypt failed, frame is not encrypted and pass through is disabled"); + stats[this_media_type].decrypt_failure++; + return 0; + } + + // Try and decrypt with each valid cryptor + // reverse iterate to try the newest cryptors first + bool success = false; + for (auto it = cryptor_managers.rbegin(); it != cryptor_managers.rend(); ++it) { + auto& cryptorManager = *it; + success = decrypt_impl(cryptorManager, this_media_type, *local_frame, frame); + if (success) { + break; + } + } + + size_t bytes_written = 0; + if (success) { + stats[this_media_type].decrypt_success++; + bytes_written = local_frame->reconstruct_frame(frame); + } + else { + stats[this_media_type].decrypt_failure++; + creator.log(dpp::ll_warning, + "decrypt failed, no valid cryptor found, type: " + std::string(this_media_type ? "video" : "audio") + + ", encrypted frame size: " + std::to_string(encrypted_frame.size()) + + ", plaintext frame size: " + std::to_string(frame.size()) + + ", number of cryptor managers: " + std::to_string(cryptor_managers.size()) + + ", pass through enabled: " + std::string(can_use_pass_through ? "yes" : "no") + ); + } + + auto end = current_clock.now(); + stats[this_media_type].decrypt_duration += std::chrono::duration_cast(end - start).count(); + + return bytes_written; +} + +bool decryptor::decrypt_impl(aead_cipher_manager& cipher_manager, media_type this_media_type, inbound_frame_processor& encrypted_frame, array_view frame) +{ + auto tag = encrypted_frame.get_tag(); + auto truncated_nonce = encrypted_frame.get_truncated_nonce(); + auto authenticated_data = encrypted_frame.get_authenticated_data(); + auto ciphertext_buffer = encrypted_frame.get_ciphertext(); + auto plaintext = encrypted_frame.get_plaintext(); + + // expand the truncated nonce to the full sized one needed for decryption + auto nonce_buffer = std::array(); + memcpy(nonce_buffer.data() + AES_GCM_128_TRUNCATED_SYNC_NONCE_OFFSET, &truncated_nonce, AES_GCM_128_TRUNCATED_SYNC_NONCE_BYTES); + + auto nonce_buffer_view = make_array_view(nonce_buffer.data(), nonce_buffer.size()); + + auto generation = cipher_manager.compute_wrapped_generation(truncated_nonce >> RATCHET_GENERATION_SHIFT_BITS); + + if (!cipher_manager.can_process_nonce(generation, truncated_nonce)) { + creator.log(dpp::ll_trace, "decrypt failed, cannot process nonce"); + return false; + } + + // Get the cryptor for this generation + cipher_interface* cipher = cipher_manager.get_cipher(generation); + + if (cipher == nullptr) { + creator.log(dpp::ll_warning, "decrypt failed, no cryptor found for generation: " + std::to_string(generation)); + return false; + } + + // perform the decryption + bool success = cipher->decrypt(plaintext, ciphertext_buffer, tag, nonce_buffer_view, authenticated_data); + stats[this_media_type].decrypt_attempts++; + + if (success) { + cipher_manager.report_cipher_success(generation, truncated_nonce); + } + + return success; +} + +size_t decryptor::get_max_plaintext_byte_size(media_type this_media_type, size_t encrypted_frame_size) +{ + return encrypted_frame_size; +} + +void decryptor::update_cryptor_manager_expiry(duration expiry) +{ + auto max_expiry_time = current_clock.now() + expiry; + for (auto& cryptorManager : cryptor_managers) { + cryptorManager.update_expiry(max_expiry_time); + } +} + +void decryptor::cleanup_expired_cryptor_managers() +{ + while (!cryptor_managers.empty() && cryptor_managers.front().is_expired()) { + creator.log(dpp::ll_trace, "Removing expired cryptor manager"); + cryptor_managers.pop_front(); + } +} + +std::unique_ptr decryptor::get_or_create_frame_processor() +{ + std::lock_guard lock(frame_processors_mutex); + if (frame_processors.empty()) { + return std::make_unique(creator); + } + auto frame_processor = std::move(frame_processors.back()); + frame_processors.pop_back(); + return frame_processor; +} + +void decryptor::return_frame_processor(std::unique_ptr frame_processor) +{ + std::lock_guard lock(frame_processors_mutex); + frame_processors.push_back(std::move(frame_processor)); +} + +} diff --git a/src/dpp/dave/decryptor.h b/src/dpp/dave/decryptor.h new file mode 100755 index 0000000000..e093dc46fb --- /dev/null +++ b/src/dpp/dave/decryptor.h @@ -0,0 +1,221 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "codec_utils.h" +#include "common.h" +#include "cipher_interface.h" +#include "cryptor_manager.h" +#include "frame_processors.h" +#include "version.h" +#include "clock.h" + +namespace dpp { + class cluster; +} + +namespace dpp::dave { + +class key_ratchet_interface; + +/** + * @brief Decryption stats + */ +struct decryption_stats { + /** + * @brief Number of passthroughs + */ + uint64_t passthroughs = 0; + /** + * @brief Number of decryption successes + */ + uint64_t decrypt_success = 0; + /** + * @brief Number of decryption failures + */ + uint64_t decrypt_failure = 0; + /** + * @brief Total encryption duration + */ + uint64_t decrypt_duration = 0; + /** + * @brief Number of decryption attempts + */ + uint64_t decrypt_attempts = 0; +}; + +/** + * @brief Decryptor, decrypts encrypted frames + */ +class decryptor { +public: + /** + * @brief Constructor + * @param cl Creator + */ + decryptor(dpp::cluster& cl) : creator(cl) { }; + + /** + * @brief Chrono duration + */ + using duration = std::chrono::seconds; + + /** + * @brief Set a new key ratchet for a decryptor. These are derived during welcome/commit + * of the session. Once you have a key ratchet, you can derive the key, and decrypt that + * user's audio/video. + * + * @param key_ratchet Key ratchet + * @param transition_expiry Transition expiry. Old keys last this long before being withdrawn + * in preference of this new one. + */ + void transition_to_key_ratchet(std::unique_ptr key_ratchet, duration transition_expiry = DEFAULT_TRANSITION_EXPIRY); + + /** + * @brief Transition to passthrough mode + * + * Passthrough mode occurs when a non-DAVE user connects to the VC. + * + * @param passthrough_mode True to enable passthrough mode + * @param transition_expiry Expiry for the transition + */ + void transition_to_passthrough_mode(bool passthrough_mode, duration transition_expiry = DEFAULT_TRANSITION_EXPIRY); + + /** + * @brief Decrypt a frame + * + * @param this_media_type type of media, audio or video + * @param encrypted_frame encrypted frame bytes + * @param frame plaintext output + * @return size of decrypted frame, or 0 if failure + */ + size_t decrypt(media_type this_media_type, array_view encrypted_frame, array_view frame); + + /** + * @brief Get maximum possible decrypted size of frame from an encrypted frame + * @param this_media_type type of media + * @param encrypted_frame_size encrypted frame size + * @return size of plaintext buffer required + */ + size_t get_max_plaintext_byte_size(media_type this_media_type, size_t encrypted_frame_size); + + /** + * @brief Get decryption stats + * @param this_media_type media type, audio or video + * @return decryption stats + */ + decryption_stats get_stats(media_type this_media_type) const { + return stats[this_media_type]; + } + +private: + /** + * @brief Chrono time point + */ + using time_point = clock_interface::time_point; + + /** + * @brief Decryption implementation + * + * @param cipher_manager cipher manager + * @param this_media_type media type, audio or video + * @param encrypted_frame encrypted frame data + * @param frame decrypted frame data + * @return True if decryption succeeded + */ + bool decrypt_impl(aead_cipher_manager& cipher_manager, media_type this_media_type, inbound_frame_processor& encrypted_frame, array_view frame); + + /** + * @brief Update expiry for an instance of the manager + * @param expiry expiry duration + */ + void update_cryptor_manager_expiry(duration expiry); + + /** + * @brief Clean up expired cryptor managers + */ + void cleanup_expired_cryptor_managers(); + + /** + * @brief Get frame procesor, or create a new one + * @return frame processor + */ + std::unique_ptr get_or_create_frame_processor(); + + /** + * Return frame processor + * @param frame_processor frame processor + */ + void return_frame_processor(std::unique_ptr frame_processor); + + /** + * @brief Chrono clock + */ + clock current_clock; + + /** + * @brief Cryptor manager list + */ + std::deque cryptor_managers; + + /** + * @brief Mutex for thread safety of frame processor list + */ + std::mutex frame_processors_mutex; + + /** + * @brief List of frame processors + */ + std::vector> frame_processors; + + /** + * @brief Passthrough expiry time + */ + time_point allow_pass_through_until{time_point::min()}; + + /** + * @brief Last stats generation time + */ + time_point last_stats_time{time_point::min()}; + + /** + * @brief Stats for audio and video decryption + */ + std::array stats; + + /** + * @brief DPP Cluster, used for logging + */ + dpp::cluster& creator; +}; + +} diff --git a/src/dpp/dave/encryptor.cpp b/src/dpp/dave/encryptor.cpp new file mode 100755 index 0000000000..782d02984a --- /dev/null +++ b/src/dpp/dave/encryptor.cpp @@ -0,0 +1,289 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#include "encryptor.h" +#include +#include +#include +#include +#include +#include "common.h" +#include "cryptor_manager.h" +#include "codec_utils.h" +#include "array_view.h" +#include "leb128.h" +#include "scope_exit.h" + +using namespace std::chrono_literals; + +namespace dpp::dave { + +void encryptor::set_key_ratchet(std::unique_ptr key_ratchet) +{ + std::lock_guard lock(key_gen_mutex); + ratchet = std::move(key_ratchet); + cryptor = nullptr; + current_key_generation = 0; + truncated_nonce = 0; +} + +void encryptor::set_passthrough_mode(bool passthrough_mode) +{ + passthrough_mode_enable = passthrough_mode; + update_current_protocol_version(passthrough_mode ? 0 : max_protocol_version()); +} + +encryptor::result_code encryptor::encrypt(media_type this_media_type, uint32_t ssrc, array_view frame, array_view encrypted_frame, size_t* bytes_written) { + if (this_media_type != media_audio && this_media_type != media_video) { + creator.log(dpp::ll_warning, "encrypt failed, invalid media type: " + std::to_string(static_cast(this_media_type))); + return result_code::rc_encryption_failure; + } + + if (passthrough_mode_enable) { + // Pass frame through without encrypting + std::memcpy(encrypted_frame.data(), frame.data(), frame.size()); + *bytes_written = frame.size(); + stats[this_media_type].passthroughs++; + return result_code::rc_success; + } + + { + std::lock_guard lock(key_gen_mutex); + if (!ratchet) { + stats[this_media_type].encrypt_failure++; + return result_code::rc_encryption_failure; + } + } + + auto start = std::chrono::steady_clock::now(); + auto result = result_code::rc_success; + + // write the codec identifier + auto codec = codec_for_ssrc(ssrc); + + auto frame_processor = get_or_create_frame_processor(); + scope_exit cleanup([&] { return_frame_processor(std::move(frame_processor)); }); + + frame_processor->process_frame(frame, codec); + + const auto& unencrypted_bytes = frame_processor->get_unencrypted_bytes(); + const auto& encrypted_bytes = frame_processor->get_encrypted_bytes(); + auto& ciphertext_bytes = frame_processor->get_ciphertext_bytes(); + + const auto& unencrypted_ranges = frame_processor->get_unencrypted_ranges(); + auto ranges_size = unencrypted_ranges_size(unencrypted_ranges); + + auto additional_data = make_array_view(unencrypted_bytes.data(), unencrypted_bytes.size()); + auto plaintext_buffer = make_array_view(encrypted_bytes.data(), encrypted_bytes.size()); + auto ciphertext_buffer = make_array_view(ciphertext_bytes.data(), ciphertext_bytes.size()); + + auto frame_size = encrypted_bytes.size() + unencrypted_bytes.size(); + auto tag_buffer = make_array_view(encrypted_frame.data() + frame_size, AES_GCM_127_TRUNCATED_TAG_BYTES); + + auto nonce_buffer = std::array(); + auto nonce_buffer_view = make_array_view(nonce_buffer.data(), nonce_buffer.size()); + + constexpr auto MAX_CIPHERTEXT_VALIDATION_RETRIES = 10; + + // some codecs (e.g. H26X) have packetizers that cannot handle specific byte sequences + // so we attempt up to MAX_CIPHERTEXT_VALIDATION_RETRIES to encrypt the frame + // calling into codec utils to validate the ciphertext + supplemental section + // and re-rolling the truncated nonce if it fails + + // the nonce increment will definitely change the ciphertext and the tag + // incrementing the nonce will also change the appropriate bytes + // in the tail end of the nonce + // which can remove start codes from the last 1 or 2 bytes of the nonce + // and the two bytes of the unencrypted header bytes + for (auto attempt = 1; attempt <= MAX_CIPHERTEXT_VALIDATION_RETRIES; ++attempt) { + auto [curr_cryptor, truncatedNonce] = get_next_cryptor_and_nonce(); + + if (!curr_cryptor) { + result = result_code::rc_encryption_failure; + break; + } + + // write the truncated nonce to our temporary full nonce array + // (since the encryption call expects a full size nonce) + std::memcpy(nonce_buffer.data() + AES_GCM_128_TRUNCATED_SYNC_NONCE_OFFSET, &truncatedNonce, AES_GCM_128_TRUNCATED_SYNC_NONCE_BYTES); + + // encrypt the plaintext, adding the unencrypted header to the tag + bool success = curr_cryptor->encrypt(ciphertext_buffer, plaintext_buffer, nonce_buffer_view, additional_data, tag_buffer); + + stats[this_media_type].encrypt_attempts++; + stats[this_media_type].encrypt_max_attempts = + std::max(stats[this_media_type].encrypt_max_attempts, (uint64_t)attempt); + + if (!success) { + result = result_code::rc_encryption_failure; + break; + } + + auto reconstructed_frame_size = frame_processor->reconstruct_frame(encrypted_frame); + + auto size = leb128_size(truncatedNonce); + + auto truncated_nonce_buffer = make_array_view(tag_buffer.end(), size); + auto unencrypted_ranges_buffer = make_array_view(truncated_nonce_buffer.end(), ranges_size); + auto supplemental_bytes_buffer = make_array_view(unencrypted_ranges_buffer.end(), sizeof(supplemental_bytes_size)); + auto marker_bytes_buffer = make_array_view(supplemental_bytes_buffer.end(), sizeof(magic_marker)); + + // write the nonce + auto res = write_leb128(truncatedNonce, truncated_nonce_buffer.begin()); + if (res != size) { + result = result_code::rc_encryption_failure; + break; + } + + // write the unencrypted ranges + res = serialize_unencrypted_ranges(unencrypted_ranges, unencrypted_ranges_buffer.begin(), unencrypted_ranges_buffer.size()); + if (res != ranges_size) { + result = result_code::rc_encryption_failure; + break; + } + + // write the supplemental bytes size + uint64_t supplemental_bytes_large = SUPPLEMENTAL_BYTES + size + ranges_size; + + if (supplemental_bytes_large > std::numeric_limits::max()) { + result = rc_encryption_failure; + break; + } + + supplemental_bytes_size supplemental_bytes = supplemental_bytes_large; + std::memcpy(supplemental_bytes_buffer.data(), &supplemental_bytes, sizeof(supplemental_bytes_size)); + + // write the marker bytes, ends the frame + std::memcpy(marker_bytes_buffer.data(), &MARKER_BYTES, sizeof(magic_marker)); + + auto encrypted_frame_bytes = reconstructed_frame_size + AES_GCM_127_TRUNCATED_TAG_BYTES + size + ranges_size + sizeof(supplemental_bytes_size) + sizeof(magic_marker); + + if (codec_utils::validate_encrypted_frame(*frame_processor, make_array_view(encrypted_frame.data(), encrypted_frame_bytes))) { + *bytes_written = encrypted_frame_bytes; + break; + } + else if (attempt >= MAX_CIPHERTEXT_VALIDATION_RETRIES) { + result = result_code::rc_encryption_failure; + break; + } + } + + auto now = std::chrono::steady_clock::now(); + stats[this_media_type].encrypt_duration += std::chrono::duration_cast(now - start).count(); + if (result == result_code::rc_success) { + stats[this_media_type].encrypt_success++; + } + else { + stats[this_media_type].encrypt_failure++; + } + + return result; +} + +size_t encryptor::get_max_ciphertext_byte_size(media_type this_media_type, size_t frame_size) +{ + return frame_size + SUPPLEMENTAL_BYTES + TRANSFORM_PADDING_BYTES; +} + +void encryptor::assign_ssrc_to_codec(uint32_t ssrc, codec codec_type) +{ + auto existing_codec_it = std::find_if( + ssrc_codec_pairs.begin(), ssrc_codec_pairs.end(), [ssrc](const ssrc_codec_pair& pair) { + return pair.first == ssrc; + } + ); + + if (existing_codec_it == ssrc_codec_pairs.end()) { + ssrc_codec_pairs.emplace_back(ssrc, codec_type); + } + else { + existing_codec_it->second = codec_type; + } +} + +codec encryptor::codec_for_ssrc(uint32_t ssrc) +{ + auto existing_codec_it = std::find_if( + ssrc_codec_pairs.begin(), ssrc_codec_pairs.end(), [ssrc](const ssrc_codec_pair& pair) { + return pair.first == ssrc; + } + ); + + if (existing_codec_it != ssrc_codec_pairs.end()) { + return existing_codec_it->second; + } + else { + return codec::cd_opus; + } +} + +std::unique_ptr encryptor::get_or_create_frame_processor() +{ + std::lock_guard lock(frame_processors_mutex); + if (frame_processors.empty()) { + return std::make_unique(creator); + } + auto frame_processor = std::move(frame_processors.back()); + frame_processors.pop_back(); + return frame_processor; +} + +void encryptor::return_frame_processor(std::unique_ptr frameProcessor) +{ + std::lock_guard lock(frame_processors_mutex); + frame_processors.push_back(std::move(frameProcessor)); +} + +encryptor::cryptor_and_nonce encryptor::get_next_cryptor_and_nonce() +{ + std::lock_guard lock(key_gen_mutex); + if (!ratchet) { + return {nullptr, 0}; + } + + auto generation = compute_wrapped_generation(current_key_generation, ++truncated_nonce >> RATCHET_GENERATION_SHIFT_BITS); + + if (generation != current_key_generation || !cryptor) { + current_key_generation = generation; + + auto key = ratchet->get_key(current_key_generation); + cryptor = create_cipher(creator, key); + } + + return {cryptor, truncated_nonce}; +} + +void encryptor::update_current_protocol_version(protocol_version version) +{ + if (version == current_protocol_version) { + return; + } + + current_protocol_version = version; + if (changed_callback) { + changed_callback(); + } +} + +} diff --git a/src/dpp/dave/encryptor.h b/src/dpp/dave/encryptor.h new file mode 100755 index 0000000000..6b327c83ff --- /dev/null +++ b/src/dpp/dave/encryptor.h @@ -0,0 +1,303 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "codec_utils.h" +#include "common.h" +#include "cipher_interface.h" +#include "key_ratchet.h" +#include "frame_processors.h" +#include "version.h" + +namespace dpp { + class cluster; +} + +namespace dpp::dave { + +/** + * @brief Encryption stats + */ +struct encryption_stats { + /** + * @brief Number of passthrough packets + */ + uint64_t passthroughs = 0; + /** + * @brief Number of encryption successes + */ + uint64_t encrypt_success = 0; + /** + * @brief Number of encryption failures + */ + uint64_t encrypt_failure = 0; + /** + * @brief Duration encrypted + */ + uint64_t encrypt_duration = 0; + /** + * @brief Number of encryption atempts + */ + uint64_t encrypt_attempts = 0; + /** + * @brief Maximum attempts at encryption + */ + uint64_t encrypt_max_attempts = 0; +}; + +class encryptor { +public: + /** + * @brief Constructor + * @param cl Creator + */ + encryptor(dpp::cluster& cl) : creator(cl) { }; + + /** + * @brief Return codes for encryptor::encrypt + */ + enum result_code : uint8_t { + /** + * @brief Successful encryption + */ + rc_success, + /** + * @brief Encryption failure + */ + rc_encryption_failure, + }; + + /** + * @brief Set key ratchet for encryptor, this should be the bot's ratchet. + * @param key_ratchet Bot's key ratchet + */ + void set_key_ratchet(std::unique_ptr key_ratchet); + + /** + * @brief Set encryption to passthrough mode + * @param passthrough_mode true to enable passthrough mode, false to disable + */ + void set_passthrough_mode(bool passthrough_mode); + + /** + * @brief True if key ratchet assigned + * @return key ratchet is assigned + */ + bool has_key_ratchet() const { + return ratchet != nullptr; + } + + /** + * @brief True if is in passthrough mode + * @return is in passthrough mode + */ + bool is_passthrough_mode() const { + return passthrough_mode_enable; + } + + /** + * @brief Assign SSRC to codec + * @note This is unused - all SSRC are assumed to be OPUS for bots at present. + * @param ssrc RTP SSRC + * @param codec_type Codec type + */ + void assign_ssrc_to_codec(uint32_t ssrc, codec codec_type); + + /** + * @brief Get codec for RTP SSRC + * @note This is unused - all SSRC are assumed to be OPUS for bots at present. + * @param ssrc RTP SSRC + * @return always returns OPUS as bots can only send/receive audio at present + */ + codec codec_for_ssrc(uint32_t ssrc); + + /** + * @brief Encrypt plaintext opus frames + * @param this_media_type media type, should always be audio + * @param ssrc RTP SSRC + * @param frame Frame plaintext + * @param encrypted_frame Encrypted frame + * @param bytes_written Number of bytes written to the encrypted buffer + * @return Status code for encryption + */ + encryptor::result_code encrypt(media_type this_media_type, uint32_t ssrc, array_view frame, array_view encrypted_frame, size_t* bytes_written); + + /** + * @brief Get maximum possible ciphertext size for a plaintext buffer + * @param this_media_type media type, should always be audio for bots + * @param frame_size frame size of plaintext buffer + * @return size of ciphertext buffer to allocate + */ + size_t get_max_ciphertext_byte_size(media_type this_media_type, size_t frame_size); + + /** + * @brief Get encryption stats + * @param this_media_type media type + * @return encryption stats + */ + encryption_stats get_stats(media_type this_media_type) const { + return stats[this_media_type]; + } + + /** + * @brief Protocol version changed callback + */ + using protocol_version_changed_callback = std::function; + + /** + * @brief Set protocol version changed callback + * @param callback Callback to set + */ + void set_protocol_version_changed_callback(protocol_version_changed_callback callback) { + changed_callback = std::move(callback); + } + + /** + * @brief Get protocol version + * @return protocol version + */ + protocol_version get_protocol_version() const { + return current_protocol_version; + } + +private: + /** + * @brief Get the current frame processor or create a new one + * @return Frame processor + */ + std::unique_ptr get_or_create_frame_processor(); + + /** + * @brief Return frame processor + * @param frameProcessor frame processor + */ + void return_frame_processor(std::unique_ptr frameProcessor); + + /** + * @brief Pair of cryptor and nonce pointers + */ + using cryptor_and_nonce = std::pair, truncated_sync_nonce>; + + /** + * @brief Get cryptor and nonce + * @return cryptor and nonce + */ + cryptor_and_nonce get_next_cryptor_and_nonce(); + + /** + * @brief Change protocol version + * @param version new protocol version + */ + void update_current_protocol_version(protocol_version version); + + /** + * @brief True if passthrough is enabled + */ + std::atomic_bool passthrough_mode_enable{false}; + + /** + * @brief Key generation mutex for thread safety + */ + std::mutex key_gen_mutex; + + /** + * @brief Current encryption (send) ratchet + */ + std::unique_ptr ratchet; + + /** + * @brief Current encryption cipher + */ + std::shared_ptr cryptor; + + /** + * @brief Current key generation number + */ + key_generation current_key_generation{0}; + + /** + * @brief Current truncated sync nonce + */ + truncated_sync_nonce truncated_nonce{0}; + + /** + * @brief Frame processor list mutex + */ + std::mutex frame_processors_mutex; + + /** + * @brief List of outbound frame processors + */ + std::vector> frame_processors; + + /** + * @brief A pair of 32 bit SSRC and codec in use for that SSRC + */ + using ssrc_codec_pair = std::pair; + + /** + * @brief List of codec pairs for SSRCs + */ + std::vector ssrc_codec_pairs; + + /** + * @brief A chrono time point + */ + using time_point = std::chrono::time_point; + + /** + * @brief Last time stats were updated + */ + time_point last_stats_time{time_point::min()}; + + /** + * @brief Stores audio/video encryption stats + */ + std::array stats; + + /** + * @brief Callback for version change, if any + */ + protocol_version_changed_callback changed_callback; + + /** + * Current protocol version supported + */ + protocol_version current_protocol_version{max_protocol_version()}; + + /** + * @brief DPP Cluster, used for logging + */ + dpp::cluster& creator; +}; + +} diff --git a/src/dpp/dave/frame_processors.cpp b/src/dpp/dave/frame_processors.cpp new file mode 100755 index 0000000000..a8b7aa185e --- /dev/null +++ b/src/dpp/dave/frame_processors.cpp @@ -0,0 +1,400 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#include "frame_processors.h" +#include +#include +#include +#include +#include +#include +#include "codec_utils.h" +#include "array_view.h" +#include "leb128.h" + +#if defined(_MSC_VER) + #include +#endif + +namespace dpp::dave { + +#if defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64)) + /** + * @brief ARM does not have a builtin for overflow detecting add + * This implements a non-UB version of that. + * @param carry_in Input carry from previous add + * @param a First operand + * @param b Second operand + * @param result Output result + * @return True if overflow occured, false if it didn't + */ + inline uint8_t addcarry_size_t(size_t carry_in, size_t a, size_t b, size_t* result) { + size_t partial_sum = a + b; + uint8_t carry1 = (partial_sum < a); + size_t final_sum = partial_sum + carry_in; + uint8_t carry2 = (final_sum < partial_sum); + *result = final_sum; + return carry1 || carry2; + } +#endif + +std::pair overflow_add(size_t a, size_t b) +{ + size_t res; +#if defined(_MSC_VER) && defined(_M_X64) + bool didOverflow = _addcarry_u64(0, a, b, &res); +#elif defined(_MSC_VER) && defined(_M_IX86) + bool didOverflow = _addcarry_u32(0, a, b, &res); +#elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64)) + bool didOverflow = addcarry_size_t(0, a, b, &res); +#else + bool didOverflow = __builtin_add_overflow(a, b, &res); +#endif + return {didOverflow, res}; +} + +uint8_t unencrypted_ranges_size(const ranges& unencrypted_ranges) +{ + size_t size = 0; + for (const auto& range : unencrypted_ranges) { + size += leb128_size(range.offset); + size += leb128_size(range.size); + } + return static_cast(size); +} + +uint8_t serialize_unencrypted_ranges(const ranges& unencrypted_ranges, uint8_t* buffer, size_t buffer_size) +{ + auto write_at = buffer; + auto end = buffer + buffer_size; + for (const auto& range : unencrypted_ranges) { + auto range_size = leb128_size(range.offset) + leb128_size(range.size); + if (range_size > static_cast(end - write_at)) { + break; + } + + write_at += write_leb128(range.offset, write_at); + write_at += write_leb128(range.size, write_at); + } + return static_cast(write_at - buffer); +} + +uint8_t deserialize_unencrypted_ranges(const uint8_t*& read_at, const uint8_t buffer_size, ranges& unencrypted_ranges) +{ + auto start = read_at; + auto end = read_at + buffer_size; + while (read_at < end) { + size_t offset = read_leb128(read_at, end); + if (read_at == nullptr) { + break; + } + + size_t size = read_leb128(read_at, end); + if (read_at == nullptr) { + break; + } + unencrypted_ranges.push_back({offset, size}); + } + + if (read_at != end) { + unencrypted_ranges.clear(); + read_at = nullptr; + return 0; + } + + return static_cast(read_at - start); +} + +bool validate_unencrypted_ranges(const ranges& unencrypted_ranges, size_t frame_size) +{ + if (unencrypted_ranges.empty()) { + return true; + } + + // validate that the ranges are in order and don't overlap + for (auto i = 0u; i < unencrypted_ranges.size(); ++i) { + auto current = unencrypted_ranges[i]; + // The current range should not overflow into the next range + // or if it is the last range, the end of the frame + auto max_end = + i + 1 < unencrypted_ranges.size() ? unencrypted_ranges[i + 1].offset : frame_size; + + auto [did_overflow, current_end] = overflow_add(current.offset, current.size); + if (did_overflow || current_end > max_end) { + return false; + } + } + + return true; +} + +size_t do_reconstruct(ranges ranges, const std::vector& range_bytes, const std::vector& other_bytes, const array_view& output) +{ + size_t frame_index = 0; + size_t range_bytes_index = 0; + size_t other_bytes_index = 0; + + const auto copy_range_bytes = [&](size_t size) { + std::memcpy(output.data() + frame_index, range_bytes.data() + range_bytes_index, size); + range_bytes_index += size; + frame_index += size; + }; + + const auto copy_other_bytes = [&](size_t size) { + std::memcpy(output.data() + frame_index, other_bytes.data() + other_bytes_index, size); + other_bytes_index += size; + frame_index += size; + }; + + for (const auto& range : ranges) { + if (range.offset > frame_index) { + copy_other_bytes(range.offset - frame_index); + } + + copy_range_bytes(range.size); + } + + if (other_bytes_index < other_bytes.size()) { + copy_other_bytes(other_bytes.size() - other_bytes_index); + } + + return frame_index; +} + +void inbound_frame_processor::clear() +{ + encrypted = false; + original_size = 0; + truncated_nonce = std::numeric_limits::max(); + unencrypted_ranges.clear(); + authenticated.clear(); + ciphertext.clear(); + plaintext.clear(); +} + +void inbound_frame_processor::parse_frame(array_view frame) +{ + clear(); + + constexpr auto min_supplemental_bytes_size = AES_GCM_127_TRUNCATED_TAG_BYTES + sizeof(supplemental_bytes_size) + sizeof(magic_marker); + if (frame.size() < min_supplemental_bytes_size) { + creator.log(dpp::ll_warning, "Encrypted frame is too small to contain min supplemental bytes"); + return; + } + + // Check the frame ends with the magic marker + auto magic_marker_buffer = frame.end() - sizeof(magic_marker); + if (memcmp(magic_marker_buffer, &MARKER_BYTES, sizeof(magic_marker)) != 0) { + return; + } + + // Read the supplemental bytes size + supplemental_bytes_size bytes_size; + auto bytes_size_buffer = magic_marker_buffer - sizeof(supplemental_bytes_size); + memcpy(&bytes_size, bytes_size_buffer, sizeof(supplemental_bytes_size)); + + // Check the frame is large enough to contain the supplemental bytes + if (frame.size() < bytes_size) { + creator.log(dpp::ll_warning, "Encrypted frame is too small to contain supplemental bytes"); + return; + } + + // Check that supplemental bytes size is large enough to contain the supplemental bytes + if (bytes_size < min_supplemental_bytes_size) { + creator.log(dpp::ll_warning, "Supplemental bytes size is too small to contain supplemental bytes"); + return; + } + + auto supplemental_bytes_buffer = frame.end() - bytes_size; + + // Read the tag + tag = make_array_view(supplemental_bytes_buffer, AES_GCM_127_TRUNCATED_TAG_BYTES); + + // Read the nonce + auto nonce_buffer = supplemental_bytes_buffer + AES_GCM_127_TRUNCATED_TAG_BYTES; + auto read_at = nonce_buffer; + auto end = bytes_size_buffer; + truncated_nonce = static_cast(read_leb128(read_at, end)); + if (read_at == nullptr) { + creator.log(dpp::ll_warning, "Failed to read truncated nonce"); + return; + } + + // Read the unencrypted ranges + auto ranges_size = static_cast(end - read_at); + deserialize_unencrypted_ranges(read_at, ranges_size, unencrypted_ranges); + if (read_at == nullptr) { + creator.log(dpp::ll_warning, "Failed to read unencrypted ranges"); + return; + } + + if (!validate_unencrypted_ranges(unencrypted_ranges, frame.size())) { + creator.log(dpp::ll_warning, "Invalid unencrypted ranges"); + return; + } + + // This is overly aggressive but will keep reallocations to a minimum + authenticated.reserve(frame.size()); + ciphertext.reserve(frame.size()); + plaintext.reserve(frame.size()); + + original_size = frame.size(); + + // Split the frame into authenticated and ciphertext bytes + size_t frame_index = 0; + for (const auto& range : unencrypted_ranges) { + auto encrypted_bytes = range.offset - frame_index; + if (encrypted_bytes > 0) { + add_ciphertext_bytes(frame.data() + frame_index, encrypted_bytes); + } + + add_authenticated_bytes(frame.data() + range.offset, range.size); + frame_index = range.offset + range.size; + } + auto actual_frame_size = frame.size() - bytes_size; + if (frame_index < actual_frame_size) { + add_ciphertext_bytes(frame.data() + frame_index, actual_frame_size - frame_index); + } + + // Make sure the plaintext buffer is the same size as the ciphertext buffer + plaintext.resize(ciphertext.size()); + + // We've successfully parsed the frame + // Mark the frame as encrypted + encrypted = true; +} + +size_t inbound_frame_processor::reconstruct_frame(array_view frame) const +{ + if (!encrypted) { + creator.log(dpp::ll_warning, "Cannot reconstruct an invalid encrypted frame"); + return 0; + } + + if (authenticated.size() + plaintext.size() > frame.size()) { + creator.log(dpp::ll_warning, "Frame is too small to contain the decrypted frame"); + return 0; + } + + return do_reconstruct(unencrypted_ranges, authenticated, plaintext, frame); +} + +void inbound_frame_processor::add_authenticated_bytes(const uint8_t* data, size_t size) +{ + authenticated.resize(authenticated.size() + size); + memcpy(authenticated.data() + authenticated.size() - size, data, size); +} + +void inbound_frame_processor::add_ciphertext_bytes(const uint8_t* data, size_t size) +{ + ciphertext.resize(ciphertext.size() + size); + memcpy(ciphertext.data() + ciphertext.size() - size, data, size); +} + +void outbound_frame_processor::reset() +{ + frame_codec = codec::cd_unknown; + frame_index = 0; + unencrypted_bytes.clear(); + encrypted_bytes.clear(); + unencrypted_ranges.clear(); +} + +void outbound_frame_processor::process_frame(array_view frame, codec codec) +{ + reset(); + + frame_codec = codec; + unencrypted_bytes.reserve(frame.size()); + encrypted_bytes.reserve(frame.size()); + + bool success = false; + switch (codec) { + case codec::cd_opus: + success = codec_utils::process_frame_opus(*this, frame); + break; + case codec::cd_vp8: + success = codec_utils::process_frame_vp8(*this, frame); + break; + case codec::cd_vp9: + success = codec_utils::process_frame_vp9(*this, frame); + break; + case codec::cd_h264: + success = codec_utils::process_frame_h264(*this, frame); + break; + case codec::cd_h265: + success = codec_utils::process_frame_h265(*this, frame); + break; + case codec::cd_av1: + success = codec_utils::process_frame_av1(*this, frame); + break; + default: + throw dpp::logic_exception("Unsupported codec for frame encryption"); + } + + if (!success) { + frame_index = 0; + unencrypted_bytes.clear(); + encrypted_bytes.clear(); + unencrypted_ranges.clear(); + add_encrypted_bytes(frame.data(), frame.size()); + } + + ciphertext_bytes.resize(encrypted_bytes.size()); +} + +size_t outbound_frame_processor::reconstruct_frame(array_view frame) +{ + if (unencrypted_bytes.size() + ciphertext_bytes.size() > frame.size()) { + creator.log(dpp::ll_warning, "Frame is too small to contain the encrypted frame"); + return 0; + } + + return do_reconstruct(unencrypted_ranges, unencrypted_bytes, ciphertext_bytes, frame); +} + +void outbound_frame_processor::add_unencrypted_bytes(const uint8_t* bytes, size_t size) +{ + if (!unencrypted_ranges.empty() && + unencrypted_ranges.back().offset + unencrypted_ranges.back().size == frame_index) { + // extend the last range + unencrypted_ranges.back().size += size; + } else { + // add a new range (offset, size) + unencrypted_ranges.push_back({frame_index, size}); + } + + unencrypted_bytes.resize(unencrypted_bytes.size() + size); + memcpy(unencrypted_bytes.data() + unencrypted_bytes.size() - size, bytes, size); + frame_index += size; +} + +void outbound_frame_processor::add_encrypted_bytes(const uint8_t* bytes, size_t size) +{ + encrypted_bytes.resize(encrypted_bytes.size() + size); + memcpy(encrypted_bytes.data() + encrypted_bytes.size() - size, bytes, size); + frame_index += size; +} + +} diff --git a/src/dpp/dave/frame_processors.h b/src/dpp/dave/frame_processors.h new file mode 100755 index 0000000000..0ec4b8e255 --- /dev/null +++ b/src/dpp/dave/frame_processors.h @@ -0,0 +1,353 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#pragma once + +#include +#include +#include +#include +#include "common.h" +#include "array_view.h" + +namespace dpp { + class cluster; +} + +namespace dpp::dave { + +/** + * @brief Range inside a frame + */ +struct range { + size_t offset; + size_t size; +}; + +/** + * @brief Vector of ranges in a frame + */ +using ranges = std::vector; + +/** + * @brief Get total size of unencrypted ranges + * @param unencrypted_ranges unencrypted ranges + * @return size + */ +uint8_t unencrypted_ranges_size(const ranges& unencrypted_ranges); + +/** + * @brief Serialise unencrypted ranges + * @param unencrypted_ranges unencrypted ranges + * @param buffer buffer to serialise to + * @param buffer_size size of buffer + * @return size of ranges written + */ +uint8_t serialize_unencrypted_ranges(const ranges& unencrypted_ranges, uint8_t* buffer, size_t buffer_size); + +/** + * @brief Deserialise unencrypted ranges + * @param read_at buffer to write to + * @param buffer_size buffer size + * @param unencrypted_ranges unencrypted ranges to write to + * @return size of unencrypted ranges written + */ +uint8_t deserialize_unencrypted_ranges(const uint8_t*& read_at, const uint8_t buffer_size, ranges& unencrypted_ranges); + +/** + * @brief Validate unencrypted ranges + * @param unencrypted_ranges unencrypted ranges + * @param frame_size frame size + * @return true if validated + */ +bool validate_unencrypted_ranges(const ranges& unencrypted_ranges, size_t frame_size); + +/** + * @brief Processes inbound frames from the decryptor + */ +class inbound_frame_processor { +public: + /** + * @brief Create inbound frame processor + * @param _creator creating cluster + */ + inbound_frame_processor(dpp::cluster& _creator) : creator(_creator) { }; + + /** + * @brief Parse inbound frame + * @param frame frame bytes + */ + void parse_frame(array_view frame); + + /** + * @brief Rebuild frame after decryption + * @param frame frame bytes + * @return size of reconstructed frame + */ + [[nodiscard]] size_t reconstruct_frame(array_view frame) const; + + /** + * @brief True if encrypted + * @return is encrypted + */ + [[nodiscard]] bool is_encrypted() const { + return encrypted; + } + + /** + * @brief Get size + * @return Original frame size + */ + [[nodiscard]] size_t size() const { + return original_size; + } + + /** + * @brief Clear the processor state + */ + void clear(); + + /** + * @brief get AEAD tag for frame processor + * @return AEAD tag + */ + [[nodiscard]] array_view get_tag() const { + return tag; + } + + /** + * @brief Get truncated sync nonce + * @return truncated sync nonce + */ + [[nodiscard]] truncated_sync_nonce get_truncated_nonce() const { + return truncated_nonce; + } + + /** + * @brief Get authenticated AEAD data + * @return AEAD auth data + */ + [[nodiscard]] array_view get_authenticated_data() const { + return make_array_view(authenticated.data(), authenticated.size()); + } + + /** + * @brief Get ciphertext + * @return Ciphertext view + */ + [[nodiscard]] array_view get_ciphertext() const { + return make_array_view(ciphertext.data(), ciphertext.size()); + } + + /** + * @brief Get plain text + * @return Plain text view + */ + [[nodiscard]] array_view get_plaintext() { + return make_array_view(plaintext); + } + +private: + /** + * @brief Add authenticated bytes + * @param data authenticated data + * @param size authenticated data size + */ + void add_authenticated_bytes(const uint8_t* data, size_t size); + + /** + * @brief Add ciphertext bytes + * @param data ciphertext data + * @param size ciphertext data size + */ + void add_ciphertext_bytes(const uint8_t* data, size_t size); + + /** + * @brief True if frames are encrypted + */ + bool encrypted{false}; + + /** + * @brief Original size + */ + size_t original_size{0}; + + /** + * @brief AEAD tag + */ + array_view tag; + + /** + * @brief Truncated nonce + */ + truncated_sync_nonce truncated_nonce; + + /** + * @brief Unencrypted parts of the frames + */ + ranges unencrypted_ranges; + + /** + * @brief additional authenticated data + */ + std::vector authenticated; + + /** + * @brief Ciphertext + */ + std::vector ciphertext; + + /** + * @brief Plaintext + */ + std::vector plaintext; + + /** + * @brief DPP Cluster, used for logging + */ + dpp::cluster& creator; +}; + +/** + * @brief Outbound frame processor, processes outbound frames for encryption + */ +class outbound_frame_processor { +public: + /** + * @brief Create outbound frame processor + * @param _creator creating cluster + */ + outbound_frame_processor(dpp::cluster& _creator) : creator(_creator) { }; + + /** + * @brief Process outbound frame + * @param frame frame data + * @param codec codec to use + */ + void process_frame(array_view frame, codec codec); + + /** + * @brief do_reconstruct frame + * @param frame frame data + * @return size of reconstructed frame + */ + size_t reconstruct_frame(array_view frame); + + /** + * @brief Get codec + * @return codec + */ + [[nodiscard]] codec get_codec() const { + return frame_codec; + } + + /** + * @brief Get unencrypted bytes + * @return unencrypted bytes + */ + [[nodiscard]] const std::vector& get_unencrypted_bytes() const { + return unencrypted_bytes; + } + + /** + * @brief Get encrypted bytes + * @return Encrypted bytes + */ + [[nodiscard]] const std::vector& get_encrypted_bytes() const { + return encrypted_bytes; + } + + /** + * @brief Get ciphertext bytes + * @return ciphertext bytes + */ + [[nodiscard]] std::vector& get_ciphertext_bytes() { + return ciphertext_bytes; + } + + /** + * @brief Get unencrypted bytes + * @return unencrypted bytes + */ + [[nodiscard]] const ranges& get_unencrypted_ranges() const { + return unencrypted_ranges; + } + + /** + * @brief Reset outbound processor + */ + void reset(); + + /** + * @brief Add unencrypted bytes + * @param bytes unencrypted bytes + * @param size unencrypted size + */ + void add_unencrypted_bytes(const uint8_t* bytes, size_t size); + + /** + * @brief Add encrypted bytes + * @param bytes encrypted bytes + * @param size encrypted size + */ + void add_encrypted_bytes(const uint8_t* bytes, size_t size); + +private: + /** + * @brief Codec used to decrypt + */ + codec frame_codec{codec::cd_unknown}; + + /** + * @brief Frame index + */ + size_t frame_index{0}; + + /** + * @brief Unencrypted bytes + */ + std::vector unencrypted_bytes; + + /** + * @brief Encrypted bytes + */ + std::vector encrypted_bytes; + + /** + * @brief Ciphertext bytes + */ + std::vector ciphertext_bytes; + + /** + * @brief Unencrypted ranges that need to be kept plaintext to allow for RTP routing + */ + ranges unencrypted_ranges; + + /** + * @brief DPP Cluster, used for logging + */ + dpp::cluster& creator; +}; + +} diff --git a/src/dpp/dave/key_ratchet.h b/src/dpp/dave/key_ratchet.h new file mode 100755 index 0000000000..a5ed643364 --- /dev/null +++ b/src/dpp/dave/key_ratchet.h @@ -0,0 +1,63 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#pragma once + +#include +#include "common.h" + +namespace dpp::dave { + +/** + * @brief Key generation number + */ +using key_generation = uint32_t; + +/** + * @brief Represents the interface used for a key ratchet. + * A key ratchet is a way of propogating key pairs up the tree used by MLS + * so we dont have to store O(n^n) key combinations. + */ +class key_ratchet_interface { // NOLINT +public: + /** + * @brief Default destructor + */ + virtual ~key_ratchet_interface() noexcept = default; + + /** + * @brief Get key for ratchet + * @param generation current generation number + * @return encryption key + */ + virtual encryption_key get_key(key_generation generation) noexcept = 0; + + /** + * @brief Delete key for ratchet + * @param generation current generation number + */ + virtual void delete_key(key_generation generation) noexcept = 0; +}; + +} diff --git a/src/dpp/dave/leb128.cpp b/src/dpp/dave/leb128.cpp new file mode 100755 index 0000000000..f43de97772 --- /dev/null +++ b/src/dpp/dave/leb128.cpp @@ -0,0 +1,84 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * https://webrtc.googlesource.com/src/+/refs/heads/main/modules/rtp_rtcp/source/leb128.cc + * Copyright (c) 2023 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + * + ************************************************************************************/ +#include "leb128.h" + +namespace dpp::dave { + +size_t leb128_size(uint64_t value) +{ + int size = 0; + while (value >= 0x80) { + ++size; + value >>= 7; + } + return size + 1; +} + +uint64_t read_leb128(const uint8_t*& read_at, const uint8_t* end) +{ + uint64_t value = 0; + int fill_bits = 0; + while (read_at != end && fill_bits < 64 - 7) { + uint8_t leb_128_byte = *read_at; + value |= uint64_t{leb_128_byte & 0x7Fu} << fill_bits; + ++read_at; + fill_bits += 7; + if ((leb_128_byte & 0x80) == 0) { + return value; + } + } + // Read 9 bytes and didn't find the terminator byte. Check if 10th byte + // is that terminator, however to fit result into uint64_t it may carry only + // single bit. + if (read_at != end && *read_at <= 1) { + value |= uint64_t{*read_at} << fill_bits; + ++read_at; + return value; + } + // Failed to find terminator leb128 byte. + read_at = nullptr; + return 0; +} + +size_t write_leb128(uint64_t value, uint8_t* buffer) +{ + int size = 0; + while (value >= 0x80) { + buffer[size] = 0x80 | (value & 0x7F); + ++size; + value >>= 7; + } + buffer[size] = static_cast(value); + ++size; + return size; +} + +} diff --git a/src/dpp/dave/leb128.h b/src/dpp/dave/leb128.h new file mode 100755 index 0000000000..29b1298416 --- /dev/null +++ b/src/dpp/dave/leb128.h @@ -0,0 +1,68 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * https://webrtc.googlesource.com/src/+/refs/heads/main/modules/rtp_rtcp/source/leb128.cc + * Copyright (c) 2023 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + * + ************************************************************************************/ +#pragma once + +#include +#include + +namespace dpp::dave { + +/** + * @brief Maximum size of LEB128 value + */ +constexpr size_t LEB128_MAX_SIZE = 10; + +/** + * @brief Returns number of bytes needed to store `value` in leb128 format. + * @param value value to return size for + * @return size of leb128 + */ +size_t leb128_size(uint64_t value); + +/** + * @brief Reads leb128 encoded value and advance read_at by number of bytes consumed. + * Sets read_at to nullptr on error. + * @param read_at start position + * @param end end position + * @return decoded value + */ +uint64_t read_leb128(const uint8_t*& read_at, const uint8_t* end); + +/** + * @brief Encodes `value` in leb128 format. Assumes buffer has size of + * at least Leb128Size(value). Returns number of bytes consumed. + * @param value value to encode + * @param buffer buffer to encode into + * @return size of encoding + */ +size_t write_leb128(uint64_t value, uint8_t* buffer); + +} diff --git a/src/dpp/dave/mls_key_ratchet.cpp b/src/dpp/dave/mls_key_ratchet.cpp new file mode 100755 index 0000000000..b693210b5b --- /dev/null +++ b/src/dpp/dave/mls_key_ratchet.cpp @@ -0,0 +1,54 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#include "mls_key_ratchet.h" +#include + +namespace dpp::dave { + +mls_key_ratchet::mls_key_ratchet(dpp::cluster& cl, ::mlspp::CipherSuite suite, bytes base_secret) noexcept : ratchet(suite, std::move(base_secret)), creator(cl) { +} + +mls_key_ratchet::~mls_key_ratchet() noexcept = default; + +encryption_key mls_key_ratchet::get_key(key_generation generation) noexcept +{ + creator.log(dpp::ll_debug, "Retrieving key for generation " + std::to_string(generation) + " from hash ratchet"); + try { + auto key_and_nonce = ratchet.get(generation); + return std::move(key_and_nonce.key.as_vec()); + } + catch (const std::exception& e) { + creator.log(dpp::ll_warning, "Failed to retrieve key for generation " + std::to_string(generation) + ": " + std::string(e.what())); + return {}; + } +} + +void mls_key_ratchet::delete_key(key_generation generation) noexcept +{ + ratchet.erase(generation); +} + +} + diff --git a/src/dpp/dave/mls_key_ratchet.h b/src/dpp/dave/mls_key_ratchet.h new file mode 100755 index 0000000000..3f61c18a72 --- /dev/null +++ b/src/dpp/dave/mls_key_ratchet.h @@ -0,0 +1,79 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#pragma once + +#include +#include "key_ratchet.h" + +namespace dpp { + class cluster; +} + +namespace dpp::dave { + +/** + * @brief An implementation of the key ratchet using MLS + */ +class mls_key_ratchet : public key_ratchet_interface { // NOLINT +public: + /** + * @brief Constructor + * @param suite MLS ciphersuite to use + * @param base_secret base secret + */ + mls_key_ratchet(dpp::cluster& cl, ::mlspp::CipherSuite suite, bytes base_secret) noexcept; + + /** + * @brief Destructor + */ + ~mls_key_ratchet() noexcept override; + + /** + * @brief Gey key for ratchet + * @param generation current generation + * @return encryption key + */ + encryption_key get_key(key_generation generation) noexcept override; + + /** + * Delete key for ratchet + * @param generation current generation + */ + void delete_key(key_generation generation) noexcept override; + +private: + /** + * @brief MLS hash ratchet + */ + ::mlspp::HashRatchet ratchet; + + /** + * @brief DPP Cluster, used for logging + */ + dpp::cluster& creator; +}; + +} + diff --git a/src/dpp/dave/openssl_aead_cipher.cpp b/src/dpp/dave/openssl_aead_cipher.cpp new file mode 100755 index 0000000000..175710e598 --- /dev/null +++ b/src/dpp/dave/openssl_aead_cipher.cpp @@ -0,0 +1,159 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ +#include "openssl_aead_cipher.h" +#include +#include +#include +#include +#include "common.h" + +namespace dpp::dave { + +openssl_aead_cipher::openssl_aead_cipher(dpp::cluster& _creator, const encryption_key& key) : + cipher_interface(_creator), + ssl_context(EVP_CIPHER_CTX_new()), + aes_key(std::vector(key.data(), key.data() + key.size())) { +} + +openssl_aead_cipher::~openssl_aead_cipher() { + EVP_CIPHER_CTX_free(ssl_context); +} + +bool openssl_aead_cipher::encrypt(byte_view ciphertext_buffer_out, const_byte_view plaintext_buffer, const_byte_view nonce_buffer, const_byte_view additional_data, byte_view tag_buffer_out) { + + int len{}; + + if (EVP_EncryptInit_ex(ssl_context, EVP_aes_128_gcm(), nullptr, nullptr, nullptr) == 0) { + creator.log(dpp::ll_warning, "SSL Error: " + std::to_string(ERR_get_error())); + return false; + } + + /* + * Set IV length + */ + if (EVP_CIPHER_CTX_ctrl(ssl_context, EVP_CTRL_GCM_SET_IVLEN, AES_GCM_128_NONCE_BYTES, nullptr) == 0) { + creator.log(dpp::ll_warning, "SSL Error: " + std::to_string(ERR_get_error())); + return false; + } + + /* Initialise key and IV */ + if (EVP_EncryptInit_ex(ssl_context, nullptr, nullptr, aes_key.data(), nonce_buffer.data()) == 0) { + creator.log(dpp::ll_warning, "SSL Error: " + std::to_string(ERR_get_error())); + return false; + } + + /* + * Provide any AAD data. This can be called zero or more times as + * required + */ + if (EVP_EncryptUpdate(ssl_context, nullptr, &len, additional_data.data(), (int)additional_data.size()) == 0) { + creator.log(dpp::ll_warning, "SSL Error: " + std::to_string(ERR_get_error())); + return false; + } + + /* + * Provide the message to be encrypted, and obtain the encrypted output. + * EVP_EncryptUpdate can be called multiple times if necessary + */ + if (EVP_EncryptUpdate(ssl_context, ciphertext_buffer_out.data(), &len, plaintext_buffer.data(), (int)plaintext_buffer.size()) == 0) { + creator.log(dpp::ll_warning, "SSL Error: " + std::to_string(ERR_get_error())); + return false; + } + + /* + * Finalise the encryption. Normally ciphertext bytes may be written at + * this stage, but this does not occur in GCM mode + */ + if (EVP_EncryptFinal_ex(ssl_context, ciphertext_buffer_out.data() + len, &len) == 0) { + creator.log(dpp::ll_warning, "SSL Error: " + std::to_string(ERR_get_error())); + return false; + } + + /* Get the tag */ + if (EVP_CIPHER_CTX_ctrl(ssl_context, EVP_CTRL_GCM_GET_TAG, AES_GCM_127_TRUNCATED_TAG_BYTES, tag_buffer_out.data()) == 0) { + creator.log(dpp::ll_warning, "SSL Error: " + std::to_string(ERR_get_error())); + return false; + } + + return true; +} + +bool openssl_aead_cipher::decrypt(byte_view plaintext_buffer_out, const_byte_view ciphertext_buffer, const_byte_view tag_buffer, const_byte_view nonce_buffer, const_byte_view additional_data) { + + int len = 0; + + /* Initialise the decryption operation. */ + if (EVP_DecryptInit_ex(ssl_context, EVP_aes_128_gcm(), nullptr, nullptr, nullptr) == 0) { + creator.log(dpp::ll_warning, "SSL Error: " + std::to_string(ERR_get_error())); + return false; + } + + /* Set IV length. Not necessary if this is 12 bytes (96 bits) */ + if (EVP_CIPHER_CTX_ctrl(ssl_context, EVP_CTRL_GCM_SET_IVLEN, AES_GCM_128_NONCE_BYTES, nullptr) == 0) { + creator.log(dpp::ll_warning, "SSL Error: " + std::to_string(ERR_get_error())); + return false; + } + + /* Initialise key and IV */ + if (EVP_DecryptInit_ex(ssl_context, nullptr, nullptr, aes_key.data(), nonce_buffer.data()) == 0) { + creator.log(dpp::ll_warning, "SSL Error: " + std::to_string(ERR_get_error())); + return false; + } + + /* + * Provide any AAD data. This can be called zero or more times as + * required + */ + if (EVP_DecryptUpdate(ssl_context, nullptr, &len, additional_data.data(), (int)additional_data.size()) == 0) { + creator.log(dpp::ll_warning, "SSL Error: " + std::to_string(ERR_get_error())); + return false; + } + + /* + * Provide the message to be decrypted, and obtain the plaintext output. + * EVP_DecryptUpdate can be called multiple times if necessary + */ + if (EVP_DecryptUpdate(ssl_context, plaintext_buffer_out.data(), &len, ciphertext_buffer.data(), (int)ciphertext_buffer.size()) == 0) { + creator.log(dpp::ll_warning, "SSL Error: " + std::to_string(ERR_get_error())); + return false; + } + + /* Set expected tag value. Works in OpenSSL 1.0.1d and later */ + if (EVP_CIPHER_CTX_ctrl(ssl_context, EVP_CTRL_GCM_SET_TAG, AES_GCM_127_TRUNCATED_TAG_BYTES, (void*)tag_buffer.data()) == 0) { + creator.log(dpp::ll_warning, "SSL Error: " + std::to_string(ERR_get_error())); + return false; + } + + /* + * Finalise the decryption. A positive return value indicates success, + * anything else is a failure - the plaintext is not trustworthy. + */ + if (EVP_DecryptFinal_ex(ssl_context, plaintext_buffer_out.data() + len, &len) == 0) { + creator.log(dpp::ll_warning, "SSL Error: " + std::to_string(ERR_get_error())); + return false; + } + + return true; +} + +} // namespace dpp::dave + diff --git a/src/dpp/dave/openssl_aead_cipher.h b/src/dpp/dave/openssl_aead_cipher.h new file mode 100755 index 0000000000..c535eef108 --- /dev/null +++ b/src/dpp/dave/openssl_aead_cipher.h @@ -0,0 +1,99 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ +#pragma once + +#include +#include +#include +#include "cipher_interface.h" + +namespace dpp { + class cluster; +} + +namespace dpp::dave { + +/** + * @brief OpenSSL AES 128 GCM AEAD cipher + * + * Replaces the boringSSL AES cipher in the Discord implementation, so we don't + * have a conflicting dependency. + */ +class openssl_aead_cipher : public cipher_interface { // NOLINT +public: + + /** + * @brief constructor + * @param _creator Creator + * @param key encryption key + */ + openssl_aead_cipher(dpp::cluster& _creator, const encryption_key& key); + + /** + * @brief Destructor + */ + ~openssl_aead_cipher() override; + + /** + * @brief Returns true if valid + * @return True if valid + */ + [[nodiscard]] bool inline is_valid() const { + return ssl_context != nullptr; + } + + /** + * @brief Encrypt plaintext to ciphertext and authenticate it with tag/AAD + * @param ciphertext_buffer_out ciphertext + * @param plaintext_buffer plaintext + * @param nonce_buffer nonce/IV + * @param additional_data additional authenticated data + * @param tag_buffer_out tag + * @return True if encryption succeeded + */ + bool encrypt(byte_view ciphertext_buffer_out, const_byte_view plaintext_buffer, const_byte_view nonce_buffer, const_byte_view additional_data, byte_view tag_buffer_out) override; + + /** + * @brief Decrypt ciphertext to plaintext if it authenticates with tag/AAD + * @param plaintext_buffer_out plaintext + * @param ciphertext_buffer ciphertext + * @param tag_buffer tag + * @param nonce_buffer nonce/IV + * @param additional_data additional authenticated data + * @return True if decryption succeeded + */ + bool decrypt(byte_view plaintext_buffer_out, const_byte_view ciphertext_buffer, const_byte_view tag_buffer, const_byte_view nonce_buffer, const_byte_view additional_data) override; + +private: + /** + * @brief Using EVP_CIPHER_CTX instead of EVP_AEAD_CTX + */ + EVP_CIPHER_CTX* ssl_context; + + /** + * @brief Encryption/decryption key + */ + std::vector aes_key; +}; + +} // namespace dpp::dave + diff --git a/src/dpp/dave/parameters.cpp b/src/dpp/dave/parameters.cpp new file mode 100755 index 0000000000..bfaa44f7c2 --- /dev/null +++ b/src/dpp/dave/parameters.cpp @@ -0,0 +1,75 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#include "parameters.h" + +namespace dpp::dave::mls { + +::mlspp::CipherSuite::ID ciphersuite_id_for_protocol_version(protocol_version version) noexcept +{ + return ::mlspp::CipherSuite::ID::P256_AES128GCM_SHA256_P256; +} + +::mlspp::CipherSuite ciphersuite_for_protocol_version(protocol_version version) noexcept +{ + return ::mlspp::CipherSuite{ciphersuite_id_for_protocol_version(version)}; +} + +::mlspp::CipherSuite::ID ciphersuite_id_for_signature_version(signature_version version) noexcept +{ + return ::mlspp::CipherSuite::ID::P256_AES128GCM_SHA256_P256; +} + +::mlspp::CipherSuite ciphersuite_for_signature_version(signature_version version) noexcept +{ + return ::mlspp::CipherSuite{ciphersuite_id_for_protocol_version(version)}; +} + +::mlspp::Capabilities leaf_node_capabilities_for_protocol_version(protocol_version version) noexcept +{ + auto capabilities = ::mlspp::Capabilities::create_default(); + + capabilities.cipher_suites = {ciphersuite_id_for_protocol_version(version)}; + capabilities.credentials = {::mlspp::CredentialType::basic}; + + return capabilities; +} + +::mlspp::ExtensionList leaf_node_extensions_for_protocol_version(protocol_version version) noexcept +{ + return ::mlspp::ExtensionList{}; +} + +::mlspp::ExtensionList group_extensions_for_protocol_version(protocol_version version, const ::mlspp::ExternalSender& external_sender) noexcept +{ + auto extension_list = ::mlspp::ExtensionList{}; + extension_list.add(::mlspp::ExternalSendersExtension{{ + {external_sender.signature_key, external_sender.credential}, + }}); + return extension_list; +} + +} // namespace dpp::dave::mls + + diff --git a/src/dpp/dave/parameters.h b/src/dpp/dave/parameters.h new file mode 100755 index 0000000000..ca8590d42d --- /dev/null +++ b/src/dpp/dave/parameters.h @@ -0,0 +1,85 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#pragma once + +#include +#include +#include + +#include "version.h" + +namespace dpp::dave::mls { + +/** + * @brief Get ciphersuite id for protocol version + * @param version protocol version + * @return ciphersuite id + */ +::mlspp::CipherSuite::ID ciphersuite_id_for_protocol_version(protocol_version version) noexcept; + +/** + * @brief Get ciphersuite for protocol version + * @param version protocol version + * @return ciphersuite + */ +::mlspp::CipherSuite ciphersuite_for_protocol_version(protocol_version version) noexcept; + +/** + * @brief Get ciphersuite id for signature version + * @param version signature version + * @return Ciphersuite id + */ +::mlspp::CipherSuite::ID ciphersuite_id_for_signature_version(signature_version version) noexcept; + +/** + * @brief Get ciphersuite for singnature version + * @param version signature version + * @return Ciphersuite + */ +::mlspp::CipherSuite ciphersuite_for_signature_version(signature_version version) noexcept; + +/** + * @brief Get leaf node capabilities for protocol version + * @param version protocol version + * @return capabilities + */ +::mlspp::Capabilities leaf_node_capabilities_for_protocol_version(protocol_version version) noexcept; + +/** + * @brief Get leaf node extensions for protocol version + * @param version protocol version + * @return extension list + */ +::mlspp::ExtensionList leaf_node_extensions_for_protocol_version(protocol_version version) noexcept; + +/** + * @brief Get group extensions for protocol version + * @param version protocol bersion + * @param external_sender external sender + * @return extension list + */ +::mlspp::ExtensionList group_extensions_for_protocol_version(protocol_version version, const ::mlspp::ExternalSender& external_sender) noexcept; + +} diff --git a/src/dpp/dave/persisted_key_pair.cpp b/src/dpp/dave/persisted_key_pair.cpp new file mode 100755 index 0000000000..2ed4afc87a --- /dev/null +++ b/src/dpp/dave/persisted_key_pair.cpp @@ -0,0 +1,99 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#include "persisted_key_pair.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "parameters.h" + +static const std::string self_signature_label = "DiscordSelfSignature"; + +static std::string make_key_id(const std::string& session_id, ::mlspp::CipherSuite suite) { + return session_id + "-" + std::to_string((uint16_t)suite.cipher_suite()) + "-" + std::to_string(dpp::dave::mls::KeyVersion); +} + +static std::mutex mtx; +static std::map> map; + +namespace dpp::dave::mls { + +static std::shared_ptr<::mlspp::SignaturePrivateKey> get_persisted_key_pair(dpp::cluster& creator, key_pair_context_type ctx, const std::string& session_id, ::mlspp::CipherSuite suite) { + std::lock_guard lk(mtx); + + std::string id = make_key_id(session_id, suite); + + if (auto it = map.find(id); it != map.end()) { + return it->second; + } + + std::shared_ptr<::mlspp::SignaturePrivateKey> ret = ::dpp::dave::mls::detail::get_generic_persisted_key_pair(creator, ctx, id, suite); + + if (!ret) { + creator.log(dpp::ll_warning, "Failed to get key in get_persisted_key_pair"); + return nullptr; + } + + map.emplace(id, ret); + + return ret; +} + +std::shared_ptr<::mlspp::SignaturePrivateKey> get_persisted_key_pair(dpp::cluster& creator, key_pair_context_type ctx, const std::string& session_id, protocol_version version) +{ + return get_persisted_key_pair(creator, ctx, session_id, ciphersuite_for_protocol_version(version)); +} + +KeyAndSelfSignature get_persisted_public_key(dpp::cluster& creator, key_pair_context_type ctx, const std::string& session_id, signature_version version) { + auto suite = ciphersuite_for_signature_version(version); + auto pair = get_persisted_key_pair(creator, ctx, session_id, suite); + + if (!pair) { + return {}; + } + + bytes sign_data = from_ascii(session_id + ":") + pair->public_key.data; + + return { + pair->public_key.data.as_vec(), + std::move(pair->sign(suite, self_signature_label, sign_data).as_vec()), + }; +} + +bool delete_persisted_key_pair(dpp::cluster& creator, key_pair_context_type ctx, const std::string& session_id, signature_version version) { + std::string id = make_key_id(session_id, ciphersuite_for_signature_version(version)); + std::lock_guard lk(mtx); + map.erase(id); + return ::dpp::dave::mls::detail::delete_generic_persisted_key_pair(creator, ctx, id); +} + +} diff --git a/src/dpp/dave/persisted_key_pair.h b/src/dpp/dave/persisted_key_pair.h new file mode 100755 index 0000000000..ceb5d185b1 --- /dev/null +++ b/src/dpp/dave/persisted_key_pair.h @@ -0,0 +1,120 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "parameters.h" +#include "version.h" + +namespace dpp { + class cluster; +} + +namespace mlspp { + struct SignaturePrivateKey; +}; + +namespace dpp::dave::mls { + +/** + * @brief Key pair context type + */ +using key_pair_context_type = const char *; + +/** + * @brief Get persisted key pair + * @param ctx context (pass nullptr to generate transient key) + * @param session_id session id (pass empty string to generate transient key) + * @param version Protocol version + * @return MLS signature private key + */ +std::shared_ptr<::mlspp::SignaturePrivateKey> get_persisted_key_pair(dpp::cluster& creator, key_pair_context_type ctx, const std::string& session_id, protocol_version version); + +/** + * @brief self signed signature and key + */ +struct KeyAndSelfSignature { + /** + * @brief key + */ + std::vector key; + /** + * @brief signature + */ + std::vector signature; +}; + +/** + * @brief Get persisted public key + * @param ctx context (set to nullptr to get transient key) + * @param session_id session id (set to empty string to get transient key) + * @param version protocol version + * @return Key and self signature + */ +KeyAndSelfSignature get_persisted_public_key(dpp::cluster& creator, key_pair_context_type ctx, const std::string& session_id, signature_version version); + +/** + * @brief Delete persisted key pair + * @param ctx context + * @param session_id session ID + * @param version protocol version + * @return true if deleted + */ +bool delete_persisted_key_pair(dpp::cluster& creator, key_pair_context_type ctx, const std::string& session_id, signature_version version); + +/** + * @brief Key version for DAVE + */ +constexpr unsigned KeyVersion = 1; + +namespace detail { + /** + * Get generic persisted key pair + * @param ctx context + * @param id key ID + * @param suite ciphersuite + * @return signature and private key + */ + std::shared_ptr<::mlspp::SignaturePrivateKey> get_generic_persisted_key_pair(dpp::cluster& creator, key_pair_context_type ctx, const std::string& id, ::mlspp::CipherSuite suite); + + /** + * Delete generic persisted key pair + * @param ctx context + * @param id id + * @return true if deleted + */ + bool delete_generic_persisted_key_pair(dpp::cluster& creator, key_pair_context_type ctx, const std::string& id); +} + +} diff --git a/src/dpp/dave/persisted_key_pair_generic.cpp b/src/dpp/dave/persisted_key_pair_generic.cpp new file mode 100755 index 0000000000..1bc315eda6 --- /dev/null +++ b/src/dpp/dave/persisted_key_pair_generic.cpp @@ -0,0 +1,182 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif +#include +#include +#include +#include +#include "parameters.h" +#include "persisted_key_pair.h" + +static const std::string_view key_storage_dir = "Discord Key Storage"; + +static std::filesystem::path get_key_storage_directory() { + std::filesystem::path dir; + +#if defined(__ANDROID__) + dir = std::filesystem::path("/data/data"); + + { + std::ifstream idFile("/proc/self/cmdline", std::ios_base::in); + std::string appId; + std::getline(idFile, appId, '\0'); + dir /= appId; + } +#else + #if defined(_WIN32) + if (const wchar_t* appdata = _wgetenv(L"LOCALAPPDATA")) { + dir = std::filesystem::path(appdata); + } + #else + if (const char* xdg = getenv("XDG_CONFIG_HOME")) { + dir = std::filesystem::path(xdg); + } + else if (const char* home = getenv("HOME")) { + dir = std::filesystem::path(home); + dir /= ".config"; + } + #endif + else { + return dir; + } +#endif + + return dir / key_storage_dir; +} + +namespace dpp::dave::mls::detail { + +std::shared_ptr<::mlspp::SignaturePrivateKey> get_generic_persisted_key_pair(dpp::cluster& creator, key_pair_context_type ctx, const std::string& id, ::mlspp::CipherSuite suite) +{ + ::mlspp::SignaturePrivateKey ret; + std::string curstr; + std::filesystem::path dir = get_key_storage_directory(); + + if (dir.empty()) { + creator.log(dpp::ll_warning, "Failed to determine key storage directory in get_persisted_key_pair"); + return nullptr; + } + + std::error_code errc; + std::filesystem::create_directories(dir, errc); + if (errc) { + creator.log(dpp::ll_warning, "Failed to create key storage directory in get_persisted_key_pair: " + std::to_string(errc.value())); + return nullptr; + } + + std::filesystem::path file = dir / (id + ".key"); + + if (std::filesystem::exists(file)) { + std::ifstream ifs(file, std::ios_base::in | std::ios_base::binary); + if (!ifs) { + creator.log(dpp::ll_warning, "Failed to open key in get_persisted_key_pair"); + return nullptr; + } + + std::stringstream s; + s << ifs.rdbuf(); + curstr = s.str(); + if (!ifs) { + creator.log(dpp::ll_warning, "Failed to read key in get_persisted_key_pair"); + return nullptr; + } + + try { + ret = ::mlspp::SignaturePrivateKey::from_jwk(suite, curstr); + } + catch (std::exception& ex) { + creator.log(dpp::ll_warning, "Failed to parse key in get_persisted_key_pair: " + std::string(ex.what())); + return nullptr; + } + } + else { + ret = ::mlspp::SignaturePrivateKey::generate(suite); + + std::string newstr = ret.to_jwk(suite); + + std::filesystem::path tmpfile = file; + tmpfile += ".tmp"; + +#ifdef _WIN32 + int fd = _wopen(tmpfile.c_str(), _O_WRONLY | _O_CREAT | _O_TRUNC, _S_IREAD | _S_IWRITE); +#else + int fd = open(tmpfile.c_str(), O_WRONLY | O_CLOEXEC | O_NOFOLLOW | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); +#endif + if (fd < 0) { + creator.log(dpp::ll_warning, "Failed to open output file in get_persisted_key_pair: " + std::to_string(errno) + " (" + tmpfile.generic_string() + ")"); + return nullptr; + } + +#ifdef _WIN32 + int written = _write(fd, newstr.c_str(), static_cast(newstr.size())); + _close(fd); +#else + ssize_t written = write(fd, newstr.c_str(), newstr.size()); + close(fd); +#endif + if (written < 0 || (size_t)written != newstr.size()) { + creator.log(dpp::ll_warning, "Failed to write output file in get_persisted_key_pair: " + std::to_string(errno)); + return nullptr; + } + + std::filesystem::rename(tmpfile, file, errc); + if (errc) { + creator.log(dpp::ll_warning, "Failed to rename output file in get_persisted_key_pair: " + std::to_string(errc.value())); + return nullptr; + } + } + + if (!ret.public_key.data.empty()) { + return std::make_shared<::mlspp::SignaturePrivateKey>(std::move(ret)); + } + return nullptr; + +} + +bool delete_generic_persisted_key_pair(dpp::cluster& creator, key_pair_context_type ctx, const std::string& id) +{ + std::error_code errc; + std::filesystem::path dir = get_key_storage_directory(); + if (dir.empty()) { + creator.log(dpp::ll_warning, "Failed to determine key storage directory in get_persisted_key_pair"); + return false; + } + + std::filesystem::path file = dir / (id + ".key"); + return std::filesystem::remove(file, errc); +} + +} diff --git a/src/dpp/dave/scope_exit.h b/src/dpp/dave/scope_exit.h new file mode 100755 index 0000000000..6191e2e499 --- /dev/null +++ b/src/dpp/dave/scope_exit.h @@ -0,0 +1,99 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#pragma once + +#include +#include +#include + +namespace dpp::dave { + +/** + * @brief Calls a lambda when the class goes out of scope + */ +class [[nodiscard]] scope_exit final { +public: + /** + * Create new scope_exit with a lambda + * @tparam Cleanup lambda type + * @param cleanup lambda + */ + template explicit scope_exit(Cleanup&& cleanup) : exit_function{std::forward(cleanup)} { + } + + /** + * @brief Move constructor + * @param rhs other object + */ + scope_exit(scope_exit&& rhs) : exit_function{std::move(rhs.exit_function)} { + rhs.exit_function = nullptr; + } + + /** + * @brief Calls lambda + */ + ~scope_exit() { + if (exit_function) { + exit_function(); + } + } + + /** + * @brief move assignment + * @param rhs other object + * @return self + */ + scope_exit& operator=(scope_exit&& rhs) { + exit_function = std::move(rhs.exit_function); + rhs.exit_function = nullptr; + return *this; + } + + /** + * @brief Clear the lambda so it isn't called + */ + void dismiss() { + exit_function = nullptr; + } + +private: + /** + * @brief Deleted copy constructor + */ + scope_exit(scope_exit const&) = delete; + + /** + * @brief Deleted assignment operator + * @return + */ + scope_exit& operator=(scope_exit const&) = delete; + + /** + * @brief Lambda to call + */ + std::function exit_function; +}; + +} diff --git a/src/dpp/dave/session.cpp b/src/dpp/dave/session.cpp new file mode 100755 index 0000000000..0bb7f10568 --- /dev/null +++ b/src/dpp/dave/session.cpp @@ -0,0 +1,725 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#include "session.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "mls_key_ratchet.h" +#include "user_credential.h" +#include "parameters.h" +#include "persisted_key_pair.h" +#include "util.h" +#include "openssl/evp.h" + +#define TRACK_MLS_ERROR(reason) \ + if (failure_callback) { \ + failure_callback(__FUNCTION__, reason); \ + } + +namespace dpp::dave::mls { + +struct queued_proposal { + ::mlspp::ValidatedContent content; + ::mlspp::bytes_ns::bytes ref; +}; + +session::session(dpp::cluster& cluster, key_pair_context_type context, const std::string& auth_session_id, mls_failure_callback callback) noexcept + : signing_key_id(auth_session_id), key_pair_context(context), failure_callback(std::move(callback)), creator(cluster) +{ + creator.log(dpp::ll_debug, "Creating a new MLS session"); +} + +session::~session() noexcept = default; + +void session::init(protocol_version version, uint64_t group_id, std::string const& self_user_id, std::shared_ptr<::mlspp::SignaturePrivateKey>& transient_key) noexcept { + reset(); + + bot_user_id = self_user_id; + + creator.log(dpp::ll_debug, "Initializing MLS session with protocol version " + std::to_string(version) + " and group ID " + std::to_string(group_id)); + session_protocol_version = version; + session_group_id = std::move(big_endian_bytes_from(group_id).as_vec()); + + init_leaf_node(self_user_id, transient_key); + + create_pending_group(); +} + +void session::reset() noexcept { + creator.log(dpp::ll_debug, "Resetting MLS session"); + + clear_pending_state(); + + current_state.reset(); + outbound_cached_group_state.reset(); + + session_protocol_version = 0; + session_group_id.clear(); +} + +void session::set_protocol_version(protocol_version version) noexcept { + if (version != session_protocol_version) { + // when we need to retain backwards compatibility + // there may be some changes to the MLS objects required here + // until then we can just update the stored version + session_protocol_version = version; + } +} + +std::vector session::get_last_epoch_authenticator() const noexcept { + if (!current_state) { + creator.log(dpp::ll_debug, "Cannot get epoch authenticator without an established MLS group"); + return {}; + } + return std::move(current_state->epoch_authenticator().as_vec()); +} + +void session::set_external_sender(const std::vector &external_sender_package) noexcept +try { + if (current_state) { + creator.log(dpp::ll_warning, "Cannot set external sender after joining/creating an MLS group"); + return; + } + + creator.log(dpp::ll_debug, "Unmarshalling MLS external sender"); + + mls_external_sender = std::make_unique<::mlspp::ExternalSender>( + ::mlspp::tls::get<::mlspp::ExternalSender>(external_sender_package)); + + if (!session_group_id.empty()) { + create_pending_group(); + } +} +catch (const std::exception& e) { + creator.log(dpp::ll_error, "Failed to unmarshal external sender: " + std::string(e.what())); + TRACK_MLS_ERROR(e.what()); + return; +} + +std::optional> session::process_proposals(std::vector proposals, std::set const& recognised_user_ids) noexcept +try { + if (!pending_group_state && !current_state) { + creator.log(dpp::ll_debug, "Cannot process proposals without any pending or established MLS group state"); + return std::nullopt; + } + + if (!state_with_proposals) { + state_with_proposals = std::make_unique<::mlspp::State>( + pending_group_state ? *pending_group_state : *current_state); + } + + creator.log(dpp::ll_debug, "Processing MLS proposals message of " + std::to_string(proposals.size()) + " bytes"); + + ::mlspp::tls::istream in_stream(proposals); + + bool is_revoke = false; + in_stream >> is_revoke; + + if (is_revoke) { + creator.log(dpp::ll_trace, "Revoking from proposals"); + } + + const auto suite = state_with_proposals->cipher_suite(); + + if (is_revoke) { + std::vector<::mlspp::bytes_ns::bytes> refs; + in_stream >> refs; + + for (const auto& ref : refs) { + bool found = false; + for (auto it = proposal_queue.begin(); it != proposal_queue.end(); it++) { + if (it->ref == ref) { + found = true; + proposal_queue.erase(it); + break; + } + } + + if (!found) { + creator.log(dpp::ll_debug, "Cannot revoke unrecognized proposal ref"); + TRACK_MLS_ERROR("Unrecognized proposal revocation"); + return std::nullopt; + } + } + + state_with_proposals = std::make_unique<::mlspp::State>( + pending_group_state ? *pending_group_state : *current_state); + + for (auto& prop : proposal_queue) { + // success will queue the proposal, failure will throw + state_with_proposals->handle(prop.content); + } + } else { + std::vector<::mlspp::MLSMessage> messages; + in_stream >> messages; + + for (const auto& proposal_message : messages) { + auto validated_content = state_with_proposals->unwrap(proposal_message); + + if (!validate_proposal_message(validated_content.authenticated_content(), + *state_with_proposals, + recognised_user_ids)) { + return std::nullopt; + } + + // success will queue the proposal, failure will throw + state_with_proposals->handle(validated_content); + + auto ref = suite.ref(validated_content.authenticated_content()); + + proposal_queue.push_back({ + std::move(validated_content), + std::move(ref), + }); + } + } + + // generate a commit + auto commit_secret = ::mlspp::hpke::random_bytes(suite.secret_size()); + + auto commit_options = ::mlspp::CommitOpts{ + {}, // no extra proposals + true, // inline tree in welcome + false, // do not force path + {} // default leaf node options + }; + + auto [commit_message, welcome_message, new_state] = state_with_proposals->commit(commit_secret, commit_options, {}); + + creator.log(dpp::ll_debug, "Prepared commit/welcome/next state for MLS group from received proposals"); + + // combine the commit and welcome messages into a single buffer + auto out_stream = ::mlspp::tls::ostream(); + out_stream << commit_message; + + // keep a copy of the commit, we can check incoming pending group commit later for a match + pending_group_commit = std::make_unique<::mlspp::MLSMessage>(std::move(commit_message)); + + // if there were any add proposals in this commit, then we also include the welcome message + if (welcome_message.secrets.size() > 0) { + out_stream << welcome_message; + } + + // cache the outbound state in case we're the winning sender + outbound_cached_group_state = std::make_unique<::mlspp::State>(std::move(new_state)); + + return out_stream.bytes(); +} +catch (const std::exception& e) { + creator.log(dpp::ll_warning, "Failed to parse MLS proposals: " + std::string(e.what())); + TRACK_MLS_ERROR(e.what()); + return std::nullopt; +} + +bool session::is_recognized_user_id(const ::mlspp::Credential& cred, std::set const& recognised_user_ids) const +{ + std::string uid = user_credential_to_string(cred, session_protocol_version); + if (uid.empty()) { + creator.log(dpp::ll_warning, "Attempted to verify credential of unexpected type"); + return false; + } + + if (recognised_user_ids.find(uid) == recognised_user_ids.end()) { + creator.log(dpp::ll_warning, "Attempted to verify credential for unrecognized user ID: " + uid); + return false; + } + + return true; +} + +bool session::validate_proposal_message(::mlspp::AuthenticatedContent const& message, ::mlspp::State const& target_state, std::set const& recognised_user_ids) const { + if (message.wire_format != ::mlspp::WireFormat::mls_public_message) { + creator.log(dpp::ll_warning, "MLS proposal message must be PublicMessage"); + TRACK_MLS_ERROR("Invalid proposal wire format"); + return false; + } + + if (message.content.epoch != target_state.epoch()) { + creator.log(dpp::ll_warning, "MLS proposal message must be for current epoch (" + std::to_string(message.content.epoch) + " != " + std::to_string(target_state.epoch()) + ")"); + TRACK_MLS_ERROR("Proposal epoch mismatch"); + return false; + } + + if (message.content.content_type() != ::mlspp::ContentType::proposal) { + creator.log(dpp::ll_warning, "process_proposals called with non-proposal message"); + TRACK_MLS_ERROR("Unexpected message type"); + return false; + } + + if (message.content.sender.sender_type() != ::mlspp::SenderType::external) { + creator.log(dpp::ll_warning, "MLS proposal must be from external sender"); + TRACK_MLS_ERROR("Unexpected proposal sender type"); + return false; + } + + const auto& proposal = ::mlspp::tls::var::get<::mlspp::Proposal>(message.content.content); + switch (proposal.proposal_type()) { + case ::mlspp::ProposalType::add: { + const auto& credential = + ::mlspp::tls::var::get<::mlspp::Add>(proposal.content).key_package.leaf_node.credential; + if (!is_recognized_user_id(credential, recognised_user_ids)) { + creator.log(dpp::ll_warning, "MLS proposal must be for recognised user"); + TRACK_MLS_ERROR("Unexpected user ID in add proposal"); + return false; + } + break; + } + case ::mlspp::ProposalType::remove: + // Remove proposals are always allowed (mlspp will validate that it's a recognized user) + break; + default: + creator.log(dpp::ll_warning, "MLS proposal must be add or remove"); + TRACK_MLS_ERROR("Unexpected proposal type"); + return false; + } + + return true; +} + +bool session::can_process_commit(const ::mlspp::MLSMessage& commit) noexcept +{ + if (!state_with_proposals) { + return false; + } + + if (commit.group_id() != session_group_id) { + creator.log(dpp::ll_warning, "MLS commit message was for unexpected group"); + return false; + } + + return true; +} + +roster_variant session::process_commit(std::vector commit) noexcept +try { + creator.log(dpp::ll_debug, "Processing commit"); + + auto commit_message = ::mlspp::tls::get<::mlspp::MLSMessage>(commit); + + if (!can_process_commit(commit_message)) { + creator.log(dpp::ll_warning, "process_commit called with unprocessable MLS commit"); + return ignored_t{}; + } + + // in case we're the sender of this commit + // we need to pull the cached state from our outbound cache + std::optional<::mlspp::State> optional_cached_state = std::nullopt; + if (outbound_cached_group_state) { + optional_cached_state = *(outbound_cached_group_state.get()); + } + + auto new_state = state_with_proposals->handle(commit_message, optional_cached_state); + if (!new_state) { + creator.log(dpp::ll_warning, "MLS commit handling did not produce a new state"); + return failed_t{}; + } + + creator.log(dpp::ll_debug, "Successfully processed MLS commit, updating state; our leaf index is " + std::to_string(new_state->index().val) + "; current epoch is " + std::to_string(new_state->epoch())); + + roster_map ret = replace_state(std::make_unique<::mlspp::State>(std::move(*new_state))); + + // reset the outbound cached group since we handled the commit for this epoch + outbound_cached_group_state.reset(); + clear_pending_state(); + + return ret; +} +catch (const std::exception& e) { + creator.log(dpp::ll_warning, "Failed to process MLS commit: " + std::string(e.what())); + TRACK_MLS_ERROR(e.what()); + return failed_t{}; +} + +std::optional session::process_welcome(std::vector welcome, std::set const& recognised_user_ids) noexcept +try { + if (!has_cryptographic_state_for_welcome()) { + creator.log(dpp::ll_warning, "Missing local crypto state necessary to process MLS welcome"); + return std::nullopt; + } + + if (!mls_external_sender) { + creator.log(dpp::ll_warning, "Cannot process MLS welcome without an external sender"); + return std::nullopt; + } + + if (current_state) { + creator.log(dpp::ll_warning, "Cannot process MLS welcome after joining/creating an MLS group"); + return std::nullopt; + } + + // unmarshal the incoming welcome + auto unmarshalled_welcome = ::mlspp::tls::get<::mlspp::Welcome>(welcome); + + // construct the state from the unmarshalled welcome + auto new_state = std::make_unique<::mlspp::State>( + *join_init_private_key, + *hpke_private_key, + *signature_private_key, + *join_key_package, + unmarshalled_welcome, + std::nullopt, + std::map<::mlspp::bytes_ns::bytes, ::mlspp::bytes_ns::bytes>()); + + // perform application-level verification of the new state + if (!verify_welcome_state(*new_state, recognised_user_ids)) { + creator.log(dpp::ll_warning, "Group received in MLS welcome is not valid"); + return std::nullopt; + } + + creator.log(dpp::ll_debug, "Successfully welcomed to MLS Group, our leaf index is " + std::to_string(new_state->index().val) + "; current epoch is " + std::to_string(new_state->epoch())); + + // make the verified state our new (and only) state + roster_map ret = replace_state(std::move(new_state)); + + // clear out any pending state for creating/joining a group + clear_pending_state(); + + return ret; +} +catch (const std::exception& e) { + creator.log(dpp::ll_warning, "Failed to create group state from MLS welcome: " + std::string(e.what())); + TRACK_MLS_ERROR(e.what()); + return std::nullopt; +} + +roster_map session::replace_state(std::unique_ptr<::mlspp::State>&& state) +{ + roster_map new_roster; + for (const ::mlspp::LeafNode& node : state->roster()) { + if (node.credential.type() != ::mlspp::CredentialType::basic) { + continue; + } + + const auto& cred = node.credential.template get<::mlspp::BasicCredential>(); + new_roster[from_big_endian_bytes(cred.identity)] = node.signature_key.data.as_vec(); + } + + roster_map change_map; + + std::set_difference(new_roster.begin(), new_roster.end(), roster.begin(), roster.end(), std::inserter(change_map, change_map.end())); + + struct missing_item_wrapper { + roster_map& map; + + using iterator = roster_map::iterator; + using const_iterator = roster_map::const_iterator; + using value_type = roster_map::value_type; + + iterator insert(const_iterator it, const value_type& value) + { + return map.try_emplace(it, value.first, std::vector{}); + } + + iterator begin() { return map.begin(); } + + iterator end() { return map.end(); } + }; + + missing_item_wrapper wrapper{change_map}; + + std::set_difference(roster.begin(), + roster.end(), + new_roster.begin(), + new_roster.end(), + std::inserter(wrapper, wrapper.end())); + + roster = std::move(new_roster); + current_state = std::move(state); + + return change_map; +} + +bool session::has_cryptographic_state_for_welcome() const noexcept +{ + return join_key_package && join_init_private_key && signature_private_key && hpke_private_key; +} + +bool session::verify_welcome_state(::mlspp::State const& state, std::set const& recognised_user_ids) const +{ + if (!mls_external_sender) { + creator.log(dpp::ll_warning, "Cannot verify MLS welcome without an external sender"); + TRACK_MLS_ERROR("Missing external sender when processing Welcome"); + return false; + } + + auto ext = state.extensions().template find(); + if (!ext) { + creator.log(dpp::ll_warning, "MLS welcome missing external senders extension"); + TRACK_MLS_ERROR("Welcome message missing external sender extension"); + return false; + } + + if (ext->senders.size() != 1) { + creator.log(dpp::ll_warning, "MLS welcome lists unexpected number of external senders: " + std::to_string(ext->senders.size())); + TRACK_MLS_ERROR("Welcome message lists unexpected external sender count"); + return false; + } + + if (ext->senders.front() != *mls_external_sender) { + creator.log(dpp::ll_warning, "MLS welcome lists unexpected external sender"); + TRACK_MLS_ERROR("Welcome message lists unexpected external sender"); + return false; + } + + // TODO: Until we leverage revocation in the protocol + // if we re-enable this change we will refuse welcome messages + // because someone was previously supposed to be added but disconnected + // before all in-flight proposals were handled. + + for (const auto& leaf : state.roster()) { + if (!is_recognized_user_id(leaf.credential, recognised_user_ids)) { + creator.log(dpp::ll_warning, "MLS welcome lists unrecognized user ID"); + } + } + + return true; +} + +void session::init_leaf_node(std::string const& self_user_id, std::shared_ptr<::mlspp::SignaturePrivateKey>& transient_key) noexcept +try { + auto ciphersuite = ciphersuite_for_protocol_version(session_protocol_version); + + if (!transient_key) { + if (!signing_key_id.empty()) { + transient_key = get_persisted_key_pair(creator, key_pair_context, signing_key_id, session_protocol_version); + if (!transient_key) { + creator.log(dpp::ll_warning, "Did not receive MLS signature private key from get_persisted_key_pair; aborting"); + return; + } + } + else { + transient_key = std::make_shared<::mlspp::SignaturePrivateKey>( + ::mlspp::SignaturePrivateKey::generate(ciphersuite)); + } + } + + signature_private_key = transient_key; + + auto self_credential = create_user_credential(self_user_id, session_protocol_version); + hpke_private_key = std::make_unique<::mlspp::HPKEPrivateKey>(::mlspp::HPKEPrivateKey::generate(ciphersuite)); + self_leaf_node = std::make_unique<::mlspp::LeafNode>( + ciphersuite, hpke_private_key->public_key, signature_private_key->public_key, std::move(self_credential), + leaf_node_capabilities_for_protocol_version(session_protocol_version), ::mlspp::Lifetime::create_default(), + leaf_node_extensions_for_protocol_version(session_protocol_version), *signature_private_key + ); + + creator.log(dpp::ll_debug, "Created MLS leaf node"); +} +catch (const std::exception& e) { + creator.log(dpp::ll_warning, "Failed to initialize MLS leaf node: " + std::string(e.what())); + TRACK_MLS_ERROR(e.what()); +} + +void session::reset_join_key_package() noexcept +try { + if (!self_leaf_node) { + creator.log(dpp::ll_warning, "Cannot initialize join key package without a leaf node"); + return; + } + + auto ciphersuite = ciphersuite_for_protocol_version(session_protocol_version); + join_init_private_key = std::make_unique<::mlspp::HPKEPrivateKey>(::mlspp::HPKEPrivateKey::generate(ciphersuite)); + join_key_package = std::make_unique<::mlspp::KeyPackage>(ciphersuite, join_init_private_key->public_key, *self_leaf_node, leaf_node_extensions_for_protocol_version(session_protocol_version), *signature_private_key); +} +catch (const std::exception& e) { + creator.log(dpp::ll_warning, "Failed to initialize join key package: " + std::string(e.what())); + TRACK_MLS_ERROR(e.what()); +} + +void session::create_pending_group() noexcept +try { + if (session_group_id.empty()) { + creator.log(dpp::ll_warning, "Cannot create MLS group without a group ID"); + return; + } + + if (!mls_external_sender) { + creator.log(dpp::ll_debug, "Cannot create MLS group without external sender"); + return; + } + + if (!self_leaf_node) { + creator.log(dpp::ll_warning, "Cannot create MLS group without self leaf node"); + return; + } + + creator.log(dpp::ll_debug, "Creating a pending MLS group"); + + auto ciphersuite = ciphersuite_for_protocol_version(session_protocol_version); + pending_group_state = std::make_unique<::mlspp::State>( + session_group_id, + ciphersuite, + *hpke_private_key, + *signature_private_key, + *self_leaf_node, + group_extensions_for_protocol_version(session_protocol_version, *mls_external_sender) + ); + creator.log(dpp::ll_debug, "Created a pending MLS group"); +} +catch (const std::exception& e) { + creator.log(dpp::ll_warning, "Failed to create MLS group: " + std::string(e.what())); + TRACK_MLS_ERROR(e.what()); + return; +} + +std::vector session::get_marshalled_key_package() noexcept +try { + // key packages are not meant to be re-used + // so every time the client asks for a key package we create a new one + reset_join_key_package(); + + if (!join_key_package) { + creator.log(dpp::ll_warning, "Cannot marshal an uninitialized key package"); + return {}; + } + + return ::mlspp::tls::marshal(*join_key_package); +} +catch (const std::exception& e) { + creator.log(dpp::ll_warning, "Failed to marshal join key package: " + std::string(e.what())); + TRACK_MLS_ERROR(e.what()); + return {}; +} + +std::unique_ptr session::get_key_ratchet(std::string const& user_id) const noexcept +{ + if (!current_state) { + creator.log(dpp::ll_warning, "Cannot get key ratchet without an established MLS group"); + return nullptr; + } + + // change the string user ID to a little endian 64 bit user ID + // TODO: Make this use dpp::snowflake + auto u64_user_id = strtoull(user_id.c_str(), nullptr, 10); + auto user_id_bytes = ::mlspp::bytes_ns::bytes(sizeof(u64_user_id)); + memcpy(user_id_bytes.data(), &u64_user_id, sizeof(u64_user_id)); + + // generate the base secret for the hash ratchet + auto secret = current_state->do_export(session::USER_MEDIA_KEY_BASE_LABEL, user_id_bytes, AES_GCM_128_KEY_BYTES); + + // this assumes the MLS ciphersuite produces an AES_GCM_128_KEY_BYTES sized key + // would need to be updated to a different ciphersuite if there's a future mismatch + return std::make_unique(creator, current_state->cipher_suite(), std::move(secret)); +} + +void session::get_pairwise_fingerprint(uint16_t version, std::string const& user_id, pairwise_fingerprint_callback callback) const noexcept +try { + if (!current_state || !signature_private_key) { + throw std::invalid_argument("No established MLS group"); + } + + uint64_t remote_user_id = strtoull(user_id.c_str(), nullptr, 10); + uint64_t self_user_id = strtoull(bot_user_id.c_str(), nullptr, 10); + + auto it = roster.find(remote_user_id); + if (it == roster.end()) { + throw std::invalid_argument("Unknown user ID: " + user_id); + } + + ::mlspp::tls::ostream toHash1; + ::mlspp::tls::ostream toHash2; + + toHash1 << version; + toHash1.write_raw(it->second); + toHash1 << remote_user_id; + + toHash2 << version; + toHash2.write_raw(signature_private_key->public_key.data); + toHash2 << self_user_id; + + std::vector> keyData = { + toHash1.bytes(), + toHash2.bytes(), + }; + + std::sort(keyData.begin(), keyData.end()); + + std::thread([callback = std::move(callback), + data = ::mlspp::bytes_ns::bytes(std::move(keyData[0])) + keyData[1]] { + static constexpr uint8_t salt[] = { + 0x24, + 0xca, + 0xb1, + 0x7a, + 0x7a, + 0xf8, + 0xec, + 0x2b, + 0x82, + 0xb4, + 0x12, + 0xb9, + 0x2d, + 0xab, + 0x19, + 0x2e, + }; + + constexpr uint64_t N = 16384, r = 8, p = 2, max_mem = 32 * 1024 * 1024; + constexpr size_t hash_len = 64; + + std::vector out(hash_len); + + int ret = EVP_PBE_scrypt((const char*)data.data(), + data.size(), + salt, + sizeof(salt), + N, + r, + p, + max_mem, + out.data(), + out.size()); + + if (ret == 1) { + callback(out); + } else { + callback({}); + } + }).detach(); +} +catch (const std::exception& e) { + creator.log(dpp::ll_warning, "Failed to generate pairwise fingerprint: " + std::string(e.what())); + callback({}); +} + +void session::clear_pending_state() +{ + pending_group_state.reset(); + pending_group_commit.reset(); + join_init_private_key.reset(); + join_key_package.reset(); + hpke_private_key.reset(); + self_leaf_node.reset(); + state_with_proposals.reset(); + proposal_queue.clear(); +} + +} diff --git a/src/dpp/dave/session.h b/src/dpp/dave/session.h new file mode 100755 index 0000000000..36e5dabb17 --- /dev/null +++ b/src/dpp/dave/session.h @@ -0,0 +1,351 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "persisted_key_pair.h" +#include "key_ratchet.h" +#include "version.h" + +namespace mlspp { + struct AuthenticatedContent; + struct Credential; + struct ExternalSender; + struct HPKEPrivateKey; + struct KeyPackage; + struct LeafNode; + struct MLSMessage; + struct SignaturePrivateKey; + class State; +} + +namespace dpp { + class cluster; +} + +namespace dpp::dave::mls { + +struct queued_proposal; + +/** + * @brief Represents an MLS DAVE session + */ +class session { // NOLINT +public: + /** + * @brief An MLS failure callback + */ + using mls_failure_callback = std::function; + + /** + * @brief Constructor + * @param context key pair context (set to nullptr to use a transient key pair) + * @param auth_session_id auth session id (set to empty string to use a transient key pair) + * @param callback callback for failure + */ + session(dpp::cluster& cluster, key_pair_context_type context, const std::string& auth_session_id, mls_failure_callback callback) noexcept; + + /** + * @brief Destructor + */ + ~session() noexcept; + + /** + * @brief Initalise session + * @note This is not done in the constructor, as we may need to do this again on upgrade or downgrade, + * whilst preserving other state set by the constructor. + * + * @param version protocol version + * @param group_id group id (channel id) + * @param self_user_id bot's user id + * @param transient_key transient private key + */ + void init(protocol_version version, uint64_t group_id, std::string const& self_user_id, std::shared_ptr<::mlspp::SignaturePrivateKey>& transient_key) noexcept; + + /** + * @brief Reset the session to defaults + */ + void reset() noexcept; + + /** + * @brief Set protocol version for session + * @param version protocol version + */ + void set_protocol_version(protocol_version version) noexcept; + + /** + * @brief Get protocol version for session + * @return protocol version + */ + [[nodiscard]] protocol_version get_protocol_version() const noexcept { + return session_protocol_version; + } + + /** + * @brief Get last epoch authenticator, the discord privacy code for the vc + * @return privacy code + */ + [[nodiscard]] std::vector get_last_epoch_authenticator() const noexcept; + + /** + * @brief Set external sender from external sender opcode + * @param external_sender_package external sender package + */ + void set_external_sender(std::vector const& external_sender_package) noexcept; + + /** + * @brief Process proposals from proposals opcode + * @param proposals proposals blob from websocket + * @param recognised_user_ids list of recognised user IDs + * @return optional vector to send in reply as commit welcome + */ + std::optional> process_proposals(std::vector proposals, std::set const& recognised_user_ids) noexcept; + + /** + * @brief Process commit message from discord websocket + * @param commit commit message from discord websocket + * @return roster list of people in the vc + */ + roster_variant process_commit(std::vector commit) noexcept; + + /** + * @brief Process welcome blob + * @param welcome welcome blob from discord + * @param recognised_user_ids Recognised user ID list + * @return roster list of people in the vc + */ + std::optional process_welcome(std::vector welcome, std::set const& recognised_user_ids) noexcept; + + /** + * @brief Get the bot user's key package for sending to websocket + * @return marshalled key package + */ + std::vector get_marshalled_key_package() noexcept; + + /** + * @brief Get key ratchet for a user (including the bot) + * @param user_id User id to get ratchet for + * @return The user's key ratchet for use in an encryptor or decryptor + */ + [[nodiscard]] std::unique_ptr get_key_ratchet(std::string const& user_id) const noexcept; + + /** + * @brief callback for completion of pairwise fingerprint + */ + using pairwise_fingerprint_callback = std::function const&)>; + + /** + * @brief Get pairwise fingerprint (used to validate discord member in vc) + * @warning This uses SCRYPT and is extremely resource intensive. It will spawn a thread + * which will call your callback on completion. + * @param version Should always be 0x00 + * @param user_id User ID to get fingerprint for + * @param callback Callback for completion + */ + void get_pairwise_fingerprint(uint16_t version, std::string const& user_id, pairwise_fingerprint_callback callback) const noexcept; + +private: + /** + * @brief Initialise leaf node + * @param self_user_id Bot user id + * @param transient_key Transient key + */ + void init_leaf_node(std::string const& self_user_id, std::shared_ptr<::mlspp::SignaturePrivateKey>& transient_key) noexcept; + + /** + * @brief Reset join key + */ + void reset_join_key_package() noexcept; + + /** + * @brief Create pending MLS group + */ + void create_pending_group() noexcept; + + /** + * @brief Is ready for welcome + * @return true if ready for welcome + */ + [[nodiscard]] bool has_cryptographic_state_for_welcome() const noexcept; + + /** + * @brief Check if user ID is valid + * @param cred MLS credential + * @param recognised_user_ids list of recognised user IDs + * @return + */ + [[nodiscard]] bool is_recognized_user_id(const ::mlspp::Credential& cred, std::set const& recognised_user_ids) const; + + /** + * @brief Validate proposals message + * @param message authenticated content message + * @param target_state new state + * @param recognised_user_ids recognised list of user IDs + * @return true if validated + */ + [[nodiscard]] bool validate_proposal_message(::mlspp::AuthenticatedContent const& message, ::mlspp::State const& target_state, std::set const& recognised_user_ids) const; + + /** + * @brief Verify that welcome state is valid + * @param state current state + * @param recognised_user_ids list of recognised user IDs + * @return + */ + [[nodiscard]] bool verify_welcome_state(::mlspp::State const& state, std::set const& recognised_user_ids) const; + + /** + * @brief Check if can process a commit now + * @param commit Commit message + * @return true if can process + */ + [[nodiscard]] bool can_process_commit(const ::mlspp::MLSMessage& commit) noexcept; + + /** + * @brief Replace state with a new one + * @param state new state + * @return new roster list of users in VC + */ + roster_map replace_state(std::unique_ptr<::mlspp::State>&& state); + + /** + * @brief Clear pending MLS state + */ + void clear_pending_state(); + + /** + * @brief Constant media key label + */ + inline static const std::string USER_MEDIA_KEY_BASE_LABEL = "Discord Secure Frames v0"; + + /** + * @brief DAVE protocol version for the session + */ + protocol_version session_protocol_version; + + /** + * @brief Session group ID (voice channel id) + */ + std::vector session_group_id; + + /** + * @brief Signing key id + */ + std::string signing_key_id; + + /** + * @brief The bot's user snowflake ID + */ + std::string bot_user_id; + + /** + * @brief The bot's key pair context + */ + key_pair_context_type key_pair_context{nullptr}; + + /** + * @brief Our leaf node in the ratchet tree + */ + std::unique_ptr<::mlspp::LeafNode> self_leaf_node; + + /** + * @brief The bots signature private key + */ + std::shared_ptr<::mlspp::SignaturePrivateKey> signature_private_key; + + /** + * @brief HPKE private key + */ + std::unique_ptr<::mlspp::HPKEPrivateKey> hpke_private_key; + + /** + * @brief Private key for join initialisation + */ + std::unique_ptr<::mlspp::HPKEPrivateKey> join_init_private_key; + + /** + * @brief Join key package + */ + std::unique_ptr<::mlspp::KeyPackage> join_key_package; + + /** + * @brief MLS External sender (the discord voice gateway server) + */ + std::unique_ptr<::mlspp::ExternalSender> mls_external_sender; + + /** + * @brief Pending MLS group state + */ + std::unique_ptr<::mlspp::State> pending_group_state; + + /** + * @brief Pending MLS group commit + */ + std::unique_ptr<::mlspp::MLSMessage> pending_group_commit; + + /** + * @brief Outbound cached group state + */ + std::unique_ptr<::mlspp::State> outbound_cached_group_state; + + /** + * @brief Current MLS state + */ + std::unique_ptr<::mlspp::State> current_state; + + /** + * @brief Participant roster, all users who are in the VC with dave enabled + */ + roster_map roster; + + /** + * @brief Current state containing proposals + */ + std::unique_ptr<::mlspp::State> state_with_proposals; + + /** + * @brief Queue of proposals to process + */ + std::list proposal_queue; + + /** + * @brief Function to call on failure, if any + */ + mls_failure_callback failure_callback{}; + + /** + * @brief DPP Cluster, used for logging + */ + dpp::cluster& creator; +}; + +} diff --git a/src/dpp/dave/user_credential.cpp b/src/dpp/dave/user_credential.cpp new file mode 100755 index 0000000000..49cfefcaef --- /dev/null +++ b/src/dpp/dave/user_credential.cpp @@ -0,0 +1,50 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#include "user_credential.h" +#include +#include "util.h" + +namespace dpp::dave::mls { + +::mlspp::Credential create_user_credential(const std::string& user_id, protocol_version version) { + // convert the string user ID to a big endian uint64_t + auto id = std::stoull(user_id); + auto credential_bytes = big_endian_bytes_from(id); + return ::mlspp::Credential::basic(credential_bytes); +} + +std::string user_credential_to_string(const ::mlspp::Credential& cred, protocol_version version) { + if (cred.type() != ::mlspp::CredentialType::basic) { + return ""; + } + + const auto& basic = cred.template get<::mlspp::BasicCredential>(); + auto uid_val = from_big_endian_bytes(basic.identity); + return std::to_string(uid_val); +} + +} + + diff --git a/src/dpp/dave/user_credential.h b/src/dpp/dave/user_credential.h new file mode 100755 index 0000000000..3fecf70bdd --- /dev/null +++ b/src/dpp/dave/user_credential.h @@ -0,0 +1,50 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#pragma once + +#include +#include +#include "version.h" + +namespace dpp::dave::mls { + +/** + * @brief Create user credentials + * @param user_id user id + * @param version protocol version + * @return + */ +::mlspp::Credential create_user_credential(const std::string& user_id, protocol_version version); + +/** + * @brief Convert user credentials to string + * @param cred user credentials + * @param version protocol version + * @return user credentials as string + */ +std::string user_credential_to_string(const ::mlspp::Credential& cred, protocol_version version); + +} + diff --git a/src/dpp/dave/util.cpp b/src/dpp/dave/util.cpp new file mode 100755 index 0000000000..d8e8929b72 --- /dev/null +++ b/src/dpp/dave/util.cpp @@ -0,0 +1,52 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#include "util.h" + +namespace dpp::dave::mls { + +::mlspp::bytes_ns::bytes big_endian_bytes_from(uint64_t value) noexcept { + auto buffer = ::mlspp::bytes_ns::bytes(); + buffer.reserve(sizeof(value)); + + for (int i = sizeof(value) - 1; i >= 0; --i) { + buffer.push_back(static_cast(value >> (i * 8))); + } + + return buffer; +} + +uint64_t from_big_endian_bytes(const ::mlspp::bytes_ns::bytes& buffer) noexcept { + uint64_t val = 0; + + if (buffer.size() <= sizeof(val)) { + for (uint8_t byte : buffer) { + val = (val << 8) | byte; + } + } + + return val; +} + +} diff --git a/src/dpp/dave/util.h b/src/dpp/dave/util.h new file mode 100755 index 0000000000..3ecdf4dae7 --- /dev/null +++ b/src/dpp/dave/util.h @@ -0,0 +1,46 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#pragma once + +#include +#include + +namespace dpp::dave::mls { + +/** + * @brief Convert uint64_t to bytes + * @param value 64 bit value + * @return bytes + */ +::mlspp::bytes_ns::bytes big_endian_bytes_from(uint64_t value) noexcept; + +/** + * @brief Convert uint64_t to bytes + * @param value bytes + * @return 64 bit value + */ +uint64_t from_big_endian_bytes(const ::mlspp::bytes_ns::bytes& value) noexcept; + +} diff --git a/src/dpp/dave/version.cpp b/src/dpp/dave/version.cpp new file mode 100755 index 0000000000..8fe703a41b --- /dev/null +++ b/src/dpp/dave/version.cpp @@ -0,0 +1,35 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#include "version.h" + +namespace dpp::dave { + +constexpr protocol_version current_dave_protocol_version = 1; + +protocol_version max_protocol_version() { + return current_dave_protocol_version; +} + +} diff --git a/src/dpp/dave/version.h b/src/dpp/dave/version.h new file mode 100755 index 0000000000..972940a769 --- /dev/null +++ b/src/dpp/dave/version.h @@ -0,0 +1,47 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This folder is a modified fork of libdave, https://github.com/discord/libdave + * Copyright (c) 2024 Discord, Licensed under MIT + * + ************************************************************************************/ +#pragma once + +#include + +namespace dpp::dave { + +/** + * @brief Protocol Version ID + */ +using protocol_version = uint16_t; + +/** + * @brief Signature version ID + */ +using signature_version = uint8_t; + +/** + * @brief Get maximum supported protocol version + * @return maximum protocol version + */ +protocol_version max_protocol_version(); + +} diff --git a/src/dpp/discordclient.cpp b/src/dpp/discordclient.cpp index 401d9cd610..cf43b76fca 100644 --- a/src/dpp/discordclient.cpp +++ b/src/dpp/discordclient.cpp @@ -20,7 +20,6 @@ * ************************************************************************************/ #include -#include #include #include #include @@ -30,18 +29,6 @@ #include #include #include -#ifdef _WIN32 - #include - #include - #include -#else - #include - #include - #include - #include - #include - #include -#endif #define PATH_UNCOMPRESSED_JSON "/?v=" DISCORD_API_VERSION "&encoding=json" #define PATH_COMPRESSED_JSON "/?v=" DISCORD_API_VERSION "&encoding=json&compress=zlib-stream" @@ -220,7 +207,7 @@ void discord_client::run() this->thread_id = runner->native_handle(); } -bool discord_client::handle_frame(const std::string &buffer) +bool discord_client::handle_frame(const std::string &buffer, ws_opcode opcode) { std::string& data = (std::string&)buffer; @@ -340,7 +327,7 @@ bool discord_client::handle_frame(const std::string &buffer) } } }; - this->write(jsonobj_to_string(obj)); + this->write(jsonobj_to_string(obj), protocol == ws_etf ? OP_BINARY : OP_TEXT); resumes++; } else { /* Full connect */ @@ -369,7 +356,7 @@ bool discord_client::handle_frame(const std::string &buffer) } } }; - this->write(jsonobj_to_string(obj)); + this->write(jsonobj_to_string(obj), protocol == ws_etf ? OP_BINARY : OP_TEXT); this->connect_time = creator->last_identify = time(nullptr); reconnects++; } @@ -459,6 +446,11 @@ void discord_client::log(dpp::loglevel severity, const std::string &msg) const dpp::log_t logmsg(nullptr, msg); logmsg.severity = severity; logmsg.message = msg; + size_t pos{0}; + while ((pos = logmsg.message.find(token, pos)) != std::string::npos) { + logmsg.message.replace(pos, token.length(), "*****"); + pos += 5; + } creator->on_log.call(logmsg); } } @@ -539,7 +531,7 @@ void discord_client::one_second_timer() ping_start = utility::time_f(); last_ping_message.clear(); } - this->write(message); + this->write(message, protocol == ws_etf ? OP_BINARY : OP_TEXT); } } @@ -608,7 +600,7 @@ uint64_t discord_client::get_channel_count() { return total; } -discord_client& discord_client::connect_voice(snowflake guild_id, snowflake channel_id, bool self_mute, bool self_deaf) { +discord_client& discord_client::connect_voice(snowflake guild_id, snowflake channel_id, bool self_mute, bool self_deaf, bool enable_dave) { #ifdef HAVE_VOICE std::unique_lock lock(voice_mutex); if (connecting_voice_channels.find(guild_id) != connecting_voice_channels.end()) { @@ -617,11 +609,11 @@ discord_client& discord_client::connect_voice(snowflake guild_id, snowflake chan return *this; } } - connecting_voice_channels[guild_id] = std::make_unique(this, channel_id); + connecting_voice_channels[guild_id] = std::make_unique(this, channel_id, enable_dave); /* Once sent, this expects two events (in any order) on the websocket: * VOICE_SERVER_UPDATE and VOICE_STATUS_UPDATE */ - log(ll_debug, "Sending op 4 to join VC, guild " + std::to_string(guild_id) + " channel " + std::to_string(channel_id)); + log(ll_debug, "Sending op 4 to join VC, guild " + std::to_string(guild_id) + " channel " + std::to_string(channel_id) + (enable_dave ? " WITH DAVE" : "")); queue_message(jsonobj_to_string(json({ { "op", 4 }, { "d", { @@ -684,7 +676,7 @@ voiceconn* discord_client::get_voice(snowflake guild_id) { } -voiceconn::voiceconn(discord_client* o, snowflake _channel_id) : creator(o), channel_id(_channel_id), voiceclient(nullptr) { +voiceconn::voiceconn(discord_client* o, snowflake _channel_id, bool enable_dave) : creator(o), channel_id(_channel_id), voiceclient(nullptr), dave(enable_dave) { } bool voiceconn::is_ready() { @@ -713,7 +705,7 @@ voiceconn& voiceconn::connect(snowflake guild_id) { auto t = std::thread([guild_id, this]() { try { this->creator->log(ll_debug, "Connecting voice for guild " + std::to_string(guild_id) + " channel " + std::to_string(this->channel_id)); - this->voiceclient = new discord_voice_client(creator->creator, this->channel_id, guild_id, this->token, this->session_id, this->websocket_hostname); + this->voiceclient = new discord_voice_client(creator->creator, this->channel_id, guild_id, this->token, this->session_id, this->websocket_hostname, this->dave); /* Note: Spawns thread! */ this->voiceclient->run(); } @@ -727,4 +719,4 @@ voiceconn& voiceconn::connect(snowflake guild_id) { } -} // namespace dpp +} diff --git a/src/dpp/discordevents.cpp b/src/dpp/discordevents.cpp index f3e2aebb68..fb58faf610 100644 --- a/src/dpp/discordevents.cpp +++ b/src/dpp/discordevents.cpp @@ -22,7 +22,7 @@ /* OpenBSD errors when xopen_source is defined. * We want to make sure that OpenBSD does not define it. */ -#if !defined(__OpenBSD__) +#ifndef __OpenBSD__ #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE #endif @@ -35,11 +35,7 @@ #include #include #include -#include -#include -#include #include -#include #include #include @@ -439,4 +435,4 @@ void discord_client::handle_event(const std::string &event, json &j, const std:: } } -} // namespace dpp +} diff --git a/src/dpp/discordvoiceclient.cpp b/src/dpp/discordvoiceclient.cpp index 996882e935..5b241d1122 100644 --- a/src/dpp/discordvoiceclient.cpp +++ b/src/dpp/discordvoiceclient.cpp @@ -20,42 +20,22 @@ * ************************************************************************************/ -#include -#ifdef _WIN32 - #include - #include - #include -#else - #include - #include - #include - #include - #include - #include - #include -#endif -#include +#include #include -#include #include #include #include -#include #include -#include -#include #include #ifdef HAVE_VOICE - #include - #include + #include "voice/enabled/enabled.h" #else - struct OpusDecoder {}; - struct OpusEncoder {}; - struct OpusRepacketizer {}; + #include "voice/stub/stub.h" #endif namespace dpp { + moving_averager::moving_averager(uint64_t collection_count_new) { collectionCount = collection_count_new; } @@ -76,284 +56,7 @@ moving_averager::operator float() { } return returnData / static_cast(values.size()); } - else { - return 0.0f; - } -} - -[[maybe_unused]] -constexpr int32_t opus_sample_rate_hz = 48000; -[[maybe_unused]] -constexpr int32_t opus_channel_count = 2; -std::string external_ip; - -/** - * @brief Represents an RTP packet. Size should always be exactly 12. - */ -struct rtp_header { - uint16_t constant; - uint16_t sequence; - uint32_t timestamp; - uint32_t ssrc; - - rtp_header(uint16_t _seq, uint32_t _ts, uint32_t _ssrc) : constant(htons(0x8078)), sequence(htons(_seq)), timestamp(htonl(_ts)), ssrc(htonl(_ssrc)) { - } -}; - -bool discord_voice_client::sodium_initialised = false; - -bool discord_voice_client::voice_payload::operator<(const voice_payload& other) const { - if (timestamp != other.timestamp) { - return timestamp > other.timestamp; - } - - constexpr rtp_seq_t wrap_around_test_boundary = 5000; - if ((seq < wrap_around_test_boundary && other.seq >= wrap_around_test_boundary) - || (seq >= wrap_around_test_boundary && other.seq < wrap_around_test_boundary)) { - /* Match the cases where exactly one of the sequence numbers "may have" - * wrapped around. - * - * Examples: - * 1. this->seq = 65530, other.seq = 10 // Did wrap around - * 2. this->seq = 5002, other.seq = 4990 // Not wrapped around - * - * Add 5000 to both sequence numbers to force wrap around so they can be - * compared. This should be fine to do to case 2 as well, as long as the - * addend (5000) is not too large to cause one of them to wrap around. - * - * In practice, we should be unlikely to hit the case where - * - * this->seq = 65530, other.seq = 5001 - * - * because we shouldn't receive more than 5000 payloads in one batch, unless - * the voice courier thread is super slow. Also remember that the timestamp - * is compared first, and payloads this far apart shouldn't have the same - * timestamp. - */ - - /* Casts here ensure the sum wraps around and not implicitly converted to - * wider types. - */ - return static_cast(seq + wrap_around_test_boundary) - > static_cast(other.seq + wrap_around_test_boundary); - } else { - return seq > other.seq; - } -} - -#ifdef HAVE_VOICE -size_t audio_mix(discord_voice_client& client, audio_mixer& mixer, opus_int32* pcm_mix, const opus_int16* pcm, size_t park_count, int samples, int& max_samples) { - /* Mix the combined stream if combined audio is bound */ - if (client.creator->on_voice_receive_combined.empty()) { - return 0; - } - - /* We must upsample the data to 32 bits wide, otherwise we could overflow */ - for (opus_int32 v = 0; v < (samples * opus_channel_count) / mixer.byte_blocks_per_register; ++v) { - mixer.combine_samples(pcm_mix, pcm); - pcm += mixer.byte_blocks_per_register; - pcm_mix += mixer.byte_blocks_per_register; - } - client.moving_average += park_count; - max_samples = (std::max)(samples, max_samples); - return park_count + 1; -} -#endif - -void discord_voice_client::voice_courier_loop(discord_voice_client& client, courier_shared_state_t& shared_state) { -#ifdef HAVE_VOICE - utility::set_thread_name(std::string("vcourier/") + std::to_string(client.server_id)); - while (true) { - std::this_thread::sleep_for(std::chrono::milliseconds{client.iteration_interval}); - - struct flush_data_t { - snowflake user_id; - rtp_seq_t min_seq; - std::priority_queue parked_payloads; - std::vector> pending_decoder_ctls; - std::shared_ptr decoder; - }; - std::vector flush_data; - - /* - * Transport the payloads onto this thread, and - * release the lock as soon as possible. - */ - { - std::unique_lock lk(shared_state.mtx); - - /* mitigates vector resizing while holding the mutex */ - flush_data.reserve(shared_state.parked_voice_payloads.size()); - - bool has_payload_to_deliver = false; - for (auto& [user_id, parking_lot] : shared_state.parked_voice_payloads) { - has_payload_to_deliver = has_payload_to_deliver || !parking_lot.parked_payloads.empty(); - flush_data.push_back(flush_data_t{user_id, - parking_lot.range.min_seq, - std::move(parking_lot.parked_payloads), - /* Quickly check if we already have a decoder and only take the pending ctls if so. */ - parking_lot.decoder ? std::move(parking_lot.pending_decoder_ctls) - : decltype(parking_lot.pending_decoder_ctls){}, - parking_lot.decoder}); - parking_lot.range.min_seq = parking_lot.range.max_seq + 1; - parking_lot.range.min_timestamp = parking_lot.range.max_timestamp + 1; - } - - if (!has_payload_to_deliver) { - if (shared_state.terminating) { - /* We have delivered all data to handlers. Terminate now. */ - break; - } - - shared_state.signal_iteration.wait(lk); - /* - * More data came or about to terminate, or just a spurious wake. - * We need to collect the payloads again to determine what to do next. - */ - continue; - } - } - - if (client.creator->on_voice_receive.empty() && client.creator->on_voice_receive_combined.empty()) { - /* - * We do this check late, to ensure this thread drains the data - * and prevents accumulating them even when there are no handlers. - */ - continue; - } - - /* This 32 bit PCM audio buffer is an upmixed version of the streams - * combined for all users. This is a wider width audio buffer so that - * there is no clipping when there are many loud audio sources at once. - */ - opus_int32 pcm_mix[23040] = { 0 }; - size_t park_count = 0; - int max_samples = 0; - int samples = 0; - - for (auto& d : flush_data) { - if (!d.decoder) { - continue; - } - for (const auto& decoder_ctl : d.pending_decoder_ctls) { - decoder_ctl(*d.decoder); - } - for (rtp_seq_t seq = d.min_seq; !d.parked_payloads.empty(); ++seq) { - opus_int16 pcm[23040]; - if (d.parked_payloads.top().seq != seq) { - /* - * Lost a packet with sequence number "seq", - * But Opus decoder might be able to guess something. - */ - if (int samples = opus_decode(d.decoder.get(), nullptr, 0, pcm, 5760, 0); - samples >= 0) { - /* - * Since this sample comes from a lost packet, - * we can only pretend there is an event, without any raw payload byte. - */ - voice_receive_t vr(nullptr, "", &client, d.user_id, reinterpret_cast(pcm), - samples * opus_channel_count * sizeof(opus_int16)); - - park_count = audio_mix(client, *client.mixer, pcm_mix, pcm, park_count, samples, max_samples); - client.creator->on_voice_receive.call(vr); - } - } else { - voice_receive_t& vr = *d.parked_payloads.top().vr; - if (vr.audio_data.size() > 0x7FFFFFFF) { - throw dpp::length_exception(err_massive_audio, "audio_data > 2GB! This should never happen!"); - } - if (samples = opus_decode(d.decoder.get(), vr.audio_data.data(), - static_cast(vr.audio_data.size() & 0x7FFFFFFF), pcm, 5760, 0); - samples >= 0) { - vr.reassign(&client, d.user_id, reinterpret_cast(pcm), - samples * opus_channel_count * sizeof(opus_int16)); - client.end_gain = 1.0f / client.moving_average; - park_count = audio_mix(client, *client.mixer, pcm_mix, pcm, park_count, samples, max_samples); - client.creator->on_voice_receive.call(vr); - } - - d.parked_payloads.pop(); - } - } - } - - /* If combined receive is bound, dispatch it */ - if (park_count) { - - /* Downsample the 32 bit samples back to 16 bit */ - opus_int16 pcm_downsample[23040] = { 0 }; - opus_int16* pcm_downsample_ptr = pcm_downsample; - opus_int32* pcm_mix_ptr = pcm_mix; - client.increment = (client.end_gain - client.current_gain) / static_cast(samples); - for (int64_t x = 0; x < (samples * opus_channel_count) / client.mixer->byte_blocks_per_register; ++x) { - client.mixer->collect_single_register(pcm_mix_ptr, pcm_downsample_ptr, client.current_gain, client.increment); - client.current_gain += client.increment * static_cast(client.mixer->byte_blocks_per_register); - pcm_mix_ptr += client.mixer->byte_blocks_per_register; - pcm_downsample_ptr += client.mixer->byte_blocks_per_register; - } - - voice_receive_t vr(nullptr, "", &client, 0, reinterpret_cast(pcm_downsample), - max_samples * opus_channel_count * sizeof(opus_int16)); - - client.creator->on_voice_receive_combined.call(vr); - } - } -#endif -} - -discord_voice_client::discord_voice_client(dpp::cluster* _cluster, snowflake _channel_id, snowflake _server_id, const std::string &_token, const std::string &_session_id, const std::string &_host) - : websocket_client(_host.substr(0, _host.find(":")), _host.substr(_host.find(":") + 1, _host.length()), "/?v=4", OP_TEXT), - runner(nullptr), - connect_time(0), - mixer(std::make_unique()), - port(0), - ssrc(0), - timescale(1000000), - paused(false), - encoder(nullptr), - repacketizer(nullptr), - fd(INVALID_SOCKET), - secret_key(nullptr), - sequence(0), - timestamp(0), - last_timestamp(std::chrono::high_resolution_clock::now()), - sending(false), - tracks(0), - creator(_cluster), - terminating(false), - heartbeat_interval(0), - last_heartbeat(time(nullptr)), - token(_token), - sessionid(_session_id), - server_id(_server_id), - channel_id(_channel_id) -{ -#if HAVE_VOICE - if (!discord_voice_client::sodium_initialised) { - if (sodium_init() < 0) { - throw dpp::voice_exception(err_sodium, "discord_voice_client::discord_voice_client; sodium_init() failed"); - } - discord_voice_client::sodium_initialised = true; - } - int opusError = 0; - encoder = opus_encoder_create(opus_sample_rate_hz, opus_channel_count, OPUS_APPLICATION_VOIP, &opusError); - if (opusError) { - throw dpp::voice_exception(err_opus, "discord_voice_client::discord_voice_client; opus_encoder_create() failed"); - } - repacketizer = opus_repacketizer_create(); - if (!repacketizer) { - throw dpp::voice_exception(err_opus, "discord_voice_client::discord_voice_client; opus_repacketizer_create() failed"); - } - try { - this->connect(); - } - catch (std::exception&) { - cleanup(); - throw; - } -#else - throw dpp::voice_exception(err_no_voice_support, "Voice support not enabled in this build of D++"); -#endif + return 0.0f; } discord_voice_client::~discord_voice_client() @@ -361,38 +64,8 @@ discord_voice_client::~discord_voice_client() cleanup(); } -void discord_voice_client::cleanup() -{ - if (runner) { - this->terminating = true; - runner->join(); - delete runner; - runner = nullptr; - } -#if HAVE_VOICE - if (encoder) { - opus_encoder_destroy(encoder); - encoder = nullptr; - } - if (repacketizer) { - opus_repacketizer_destroy(repacketizer); - repacketizer = nullptr; - } - if (voice_courier.joinable()) { - { - std::lock_guard lk(voice_courier_shared_state.mtx); - voice_courier_shared_state.terminating = true; - } - voice_courier_shared_state.signal_iteration.notify_one(); - voice_courier.join(); - } -#endif - delete[] secret_key; - secret_key = nullptr; -} - bool discord_voice_client::is_ready() { - return secret_key != nullptr; + return has_secret_key; } bool discord_voice_client::is_playing() { @@ -400,272 +73,75 @@ bool discord_voice_client::is_playing() { return (!this->outbuf.empty()); } -void discord_voice_client::thread_run() -{ - utility::set_thread_name(std::string("vc/") + std::to_string(server_id)); - - size_t times_looped = 0; - time_t last_loop_time = time(nullptr); - - do { - bool error = false; - ssl_client::read_loop(); - ssl_client::close(); - - time_t current_time = time(nullptr); - /* Here, we check if it's been longer than 3 seconds since the previous loop, - * this gives us time to see if it's an actual disconnect, or an error. - * This will prevent us from looping too much, meaning error codes do not cause an infinite loop. - */ - if (current_time - last_loop_time >= 3) - times_looped = 0; - - /* This does mean we'll always have times_looped at a minimum of 1, this is intended. */ - times_looped++; - /* If we've looped 5 or more times, abort the loop. */ - if (times_looped >= 5) { - log(dpp::ll_warning, "Reached max loops whilst attempting to read from the websocket. Aborting websocket."); - break; - } +uint16_t dave_binary_header_t::get_transition_id() const { + bool has_transition_id = opcode == voice_client_dave_mls_welcome || opcode == voice_client_dave_announce_commit_transition; + if (!has_transition_id) { + throw dpp::logic_exception("Can't get transition ID from buffer that is not of type voice_client_dave_announce_commit_transition(29) or voice_client_dave_mls_welcome(30)"); + } + return transition_id; +} - last_loop_time = current_time; +dave_binary_header_t::dave_binary_header_t(const std::string& buffer) { + if (buffer.length() < 5) { + throw dpp::length_exception("DAVE binary buffer too short (<5)"); + } + seq = (buffer[0] << 8) | buffer[1]; + opcode = buffer[2]; + transition_id = (buffer[3] << 8) | buffer[4]; - if (!terminating) { - log(dpp::ll_debug, "Attempting to reconnect the websocket..."); - do { - try { - ssl_client::connect(); - websocket_client::connect(); - } - catch (const std::exception &e) { - log(dpp::ll_error, std::string("Error establishing voice websocket connection, retry in 5 seconds: ") + e.what()); - ssl_client::close(); - std::this_thread::sleep_for(std::chrono::seconds(5)); - error = true; - } - } while (error && !terminating); - } - } while(!terminating); + bool has_transition_id = opcode == voice_client_dave_mls_welcome || opcode == voice_client_dave_announce_commit_transition; + package.assign(buffer.begin() + (has_transition_id ? 5 : 3), buffer.end()); } -void discord_voice_client::run() -{ - this->runner = new std::thread(&discord_voice_client::thread_run, this); - this->thread_id = runner->native_handle(); +std::vector dave_binary_header_t::get_data() const { + return package; } -int discord_voice_client::udp_send(const char* data, size_t length) -{ - sockaddr_in servaddr; - memset(&servaddr, 0, sizeof(servaddr)); - servaddr.sin_family = AF_INET; - servaddr.sin_port = htons(this->port); - servaddr.sin_addr.s_addr = inet_addr(this->ip.c_str()); - return (int) sendto(this->fd, data, (int)length, 0, (const sockaddr*)&servaddr, (int)sizeof(sockaddr_in)); +std::string discord_voice_client::get_privacy_code() const { +#ifdef HAVE_VOICE + return is_end_to_end_encrypted() ? mls_state->privacy_code : ""; +#else + return ""; +#endif } -int discord_voice_client::udp_recv(char* data, size_t max_length) -{ - return (int) recv(this->fd, data, (int)max_length, 0); +void discord_voice_client::get_user_privacy_code(const dpp::snowflake user, privacy_code_callback_t callback) const { +#ifdef HAVE_VOICE + if (!is_end_to_end_encrypted()) { + callback(""); + return; + } + mls_state->dave_session->get_pairwise_fingerprint(0x0000, user.str(), [callback](const std::vector &data) { + callback(data.size() == 64 ? generate_displayable_code(data, 45) : ""); + }); +#else + callback(""); +#endif } -bool discord_voice_client::handle_frame(const std::string &data) -{ - log(dpp::ll_trace, std::string("R: ") + data); - json j; - - try { - j = json::parse(data); - } - catch (const std::exception &e) { - log(dpp::ll_error, std::string("discord_voice_client::handle_frame ") + e.what() + ": " + data); - return true; +bool discord_voice_client::is_end_to_end_encrypted() const { +#ifdef HAVE_VOICE + if (mls_state == nullptr || mls_state->encryptor == nullptr) { + return false; } - if (j.find("op") != j.end()) { - uint32_t op = j["op"]; + bool has_pending_downgrade = mls_state->pending_transition.is_pending && mls_state->pending_transition.protocol_version != dave_version_1; - switch (op) { - /* Client Disconnect */ - case 13: { - if (j.find("d") != j.end() && j["d"].find("user_id") != j["d"].end() && !j["d"]["user_id"].is_null()) { - snowflake u_id = snowflake_not_null(&j["d"], "user_id"); - auto it = std::find_if(ssrc_map.begin(), ssrc_map.end(), - [&u_id](const auto & p) { return p.second == u_id; }); - - if (it != ssrc_map.end()) { - ssrc_map.erase(it); - } - - if (!creator->on_voice_client_disconnect.empty()) { - voice_client_disconnect_t vcd(nullptr, data); - vcd.voice_client = this; - vcd.user_id = u_id; - creator->on_voice_client_disconnect.call(vcd); - } - } - } - break; - /* Speaking */ - case 5: - /* Client Connect (doesn't seem to work) */ - case 12: { - if (j.find("d") != j.end() - && j["d"].find("user_id") != j["d"].end() && !j["d"]["user_id"].is_null() - && j["d"].find("ssrc") != j["d"].end() && !j["d"]["ssrc"].is_null() && j["d"]["ssrc"].is_number_integer()) { - uint32_t u_ssrc = j["d"]["ssrc"].get(); - snowflake u_id = snowflake_not_null(&j["d"], "user_id"); - ssrc_map[u_ssrc] = u_id; - - if (!creator->on_voice_client_speaking.empty()) { - voice_client_speaking_t vcs(nullptr, data); - vcs.voice_client = this; - vcs.user_id = u_id; - vcs.ssrc = u_ssrc; - creator->on_voice_client_speaking.call(vcs); - } - } - } - break; - /* Voice resume */ - case 9: - log(ll_debug, "Voice connection resumed"); - break; - /* Voice HELLO */ - case 8: { - if (j.find("d") != j.end() && j["d"].find("heartbeat_interval") != j["d"].end() && !j["d"]["heartbeat_interval"].is_null()) { - this->heartbeat_interval = j["d"]["heartbeat_interval"].get(); - } - - if (!modes.empty()) { - log(dpp::ll_debug, "Resuming voice session..."); - json obj = { - { "op", 7 }, - { - "d", - { - { "server_id", std::to_string(this->server_id) }, - { "session_id", this->sessionid }, - { "token", this->token }, - } - } - }; - this->write(obj.dump(-1, ' ', false, json::error_handler_t::replace)); - } else { - log(dpp::ll_debug, "Connecting new voice session..."); - json obj = { - { "op", 0 }, - { - "d", - { - { "user_id", creator->me.id }, - { "server_id", std::to_string(this->server_id) }, - { "session_id", this->sessionid }, - { "token", this->token }, - } - } - }; - this->write(obj.dump(-1, ' ', false, json::error_handler_t::replace)); - } - this->connect_time = time(nullptr); - } - break; - /* Session description */ - case 4: { - json &d = j["d"]; - secret_key = new uint8_t[32]; - size_t ofs = 0; - for (auto & c : d["secret_key"]) { - *(secret_key + ofs) = (uint8_t)c; - ofs++; - if (ofs > 31) { - break; - } - } - - /* This is needed to start voice receiving and make sure that the start of sending isn't cut off */ - send_silence(20); - - /* Fire on_voice_ready */ - if (!creator->on_voice_ready.empty()) { - voice_ready_t rdy(nullptr, data); - rdy.voice_client = this; - rdy.voice_channel_id = this->channel_id; - creator->on_voice_ready.call(rdy); - } - } - break; - /* Voice ready */ - case 2: { - /* Video stream stuff comes in this frame too, but we can't use it (YET!) */ - json &d = j["d"]; - this->ip = d["ip"].get(); - this->port = d["port"].get(); - this->ssrc = d["ssrc"].get(); - // Modes - for (auto & m : d["modes"]) { - this->modes.push_back(m.get()); - } - log(ll_debug, "Voice websocket established; UDP endpoint: " + ip + ":" + std::to_string(port) + " [ssrc=" + std::to_string(ssrc) + "] with " + std::to_string(modes.size()) + " modes"); - - external_ip = discover_ip(); - - dpp::socket newfd; - if ((newfd = ::socket(AF_INET, SOCK_DGRAM, 0)) >= 0) { - - sockaddr_in servaddr{}; - memset(&servaddr, 0, sizeof(sockaddr_in)); - servaddr.sin_family = AF_INET; - servaddr.sin_addr.s_addr = htonl(INADDR_ANY); - servaddr.sin_port = htons(0); - - if (bind(newfd, (sockaddr*)&servaddr, sizeof(servaddr)) < 0) { - throw dpp::connection_exception(err_bind_failure, "Can't bind() client UDP socket"); - } - - if (!set_nonblocking(newfd, true)) { - throw dpp::connection_exception(err_nonblocking_failure, "Can't switch voice UDP socket to non-blocking mode!"); - } - - /* Hook poll() in the ssl_client to add a new file descriptor */ - this->fd = newfd; - this->custom_writeable_fd = std::bind(&discord_voice_client::want_write, this); - this->custom_readable_fd = std::bind(&discord_voice_client::want_read, this); - this->custom_writeable_ready = std::bind(&discord_voice_client::write_ready, this); - this->custom_readable_ready = std::bind(&discord_voice_client::read_ready, this); - - int bound_port = 0; - sockaddr_in sin; - socklen_t len = sizeof(sin); - if (getsockname(this->fd, (sockaddr *)&sin, &len) > -1) { - bound_port = ntohs(sin.sin_port); - } - - log(ll_debug, "External IP address: " + external_ip); - - this->write(json({ - { "op", 1 }, - { "d", { - { "protocol", "udp" }, - { "data", { - { "address", external_ip }, - { "port", bound_port }, - { "mode", "xsalsa20_poly1305" } - } - } - } - } - }).dump(-1, ' ', false, json::error_handler_t::replace)); - } - } - break; - } - } - return true; + /* + * A dave_version 0 should be enough to know we're in non-e2ee session, we should also check for pending downgrade and + * whether session encryptor actually has key rachet set to encrypt opus packets. + */ + return !has_pending_downgrade && dave_version != dave_version_none && mls_state->encryptor->has_key_ratchet(); +#else + return false; +#endif } discord_voice_client& discord_voice_client::pause_audio(bool pause) { this->paused = pause; + if (!this->paused) { + this->sent_stop_frames = false; + } return *this; } @@ -690,236 +166,14 @@ dpp::utility::uptime discord_voice_client::get_remaining() { } discord_voice_client& discord_voice_client::stop_audio() { - std::lock_guard lock(this->stream_mutex); - outbuf.clear(); - track_meta.clear(); - tracks = 0; - return *this; -} - -void discord_voice_client::send(const char* packet, size_t len, uint64_t duration) { - std::lock_guard lock(this->stream_mutex); - voice_out_packet frame; - frame.packet = std::string(packet, len); - frame.duration = duration; - outbuf.emplace_back(frame); -} - -void discord_voice_client::read_ready() -{ -#ifdef HAVE_VOICE - uint8_t buffer[65535]; - int packet_size = this->udp_recv((char*)buffer, sizeof(buffer)); - - if (packet_size > 0 && (!creator->on_voice_receive.empty() || !creator->on_voice_receive_combined.empty())) { - constexpr size_t header_size = 12; - if (static_cast(packet_size) < header_size) { - /* Invalid RTP payload */ - return; - } - - /* It's a "silence packet" - throw it away. */ - if (packet_size < 44) { - return; - } - - if (uint8_t payload_type = buffer[1] & 0b0111'1111; - 72 <= payload_type && payload_type <= 76) { - /* - * This is an RTCP payload. Discord is known to send - * RTCP Receiver Reports. - * - * See https://datatracker.ietf.org/doc/html/rfc3551#section-6 - */ - return; - } - - voice_payload vp{0, // seq, populate later - 0, // timestamp, populate later - std::make_unique(nullptr, std::string((char*)buffer, packet_size))}; - - vp.vr->voice_client = this; - - { /* Get the User ID of the speaker */ - uint32_t speaker_ssrc; - std::memcpy(&speaker_ssrc, &buffer[8], sizeof(uint32_t)); - speaker_ssrc = ntohl(speaker_ssrc); - vp.vr->user_id = ssrc_map[speaker_ssrc]; - } - - /* Get the sequence number of the voice UDP packet */ - std::memcpy(&vp.seq, &buffer[2], sizeof(rtp_seq_t)); - vp.seq = ntohs(vp.seq); - /* Get the timestamp of the voice UDP packet */ - std::memcpy(&vp.timestamp, &buffer[4], sizeof(rtp_timestamp_t)); - vp.timestamp = ntohl(vp.timestamp); - - /* Nonce is the RTP Header with zero padding */ - uint8_t nonce[24] = { 0 }; - std::memcpy(nonce, &buffer[0], header_size); - - /* Get the number of CSRC in header */ - const size_t csrc_count = buffer[0] & 0b0000'1111; - /* Skip to the encrypted voice data */ - const ptrdiff_t offset_to_data = header_size + sizeof(uint32_t) * csrc_count; - uint8_t* encrypted_data = buffer + offset_to_data; - const size_t encrypted_data_len = packet_size - offset_to_data; - - if (crypto_secretbox_open_easy(encrypted_data, encrypted_data, - encrypted_data_len, nonce, secret_key)) { - /* Invalid Discord RTP payload. */ - return; - } - - const uint8_t* decrypted_data = encrypted_data; - size_t decrypted_data_len = encrypted_data_len - crypto_box_MACBYTES; - if ([[maybe_unused]] const bool uses_extension = (buffer[0] >> 4) & 0b0001) { - /* Skip the RTP Extensions */ - size_t ext_len = 0; - { - uint16_t ext_len_in_words; - memcpy(&ext_len_in_words, &decrypted_data[2], sizeof(uint16_t)); - ext_len_in_words = ntohs(ext_len_in_words); - ext_len = sizeof(uint32_t) * ext_len_in_words; - } - constexpr size_t ext_header_len = sizeof(uint16_t) * 2; - decrypted_data += ext_header_len + ext_len; - decrypted_data_len -= ext_header_len + ext_len; - } - - /* - * We're left with the decrypted, opus-encoded data. - * Park the payload and decode on the voice courier thread. - */ - vp.vr->audio_data.assign(decrypted_data, decrypted_data + decrypted_data_len); - - { - std::lock_guard lk(voice_courier_shared_state.mtx); - auto& [range, payload_queue, pending_decoder_ctls, decoder] = voice_courier_shared_state.parked_voice_payloads[vp.vr->user_id]; - - if (!decoder) { - /* - * Most likely this is the first time we encounter this speaker. - * Do some initialization for not only the decoder but also the range. - */ - range.min_seq = vp.seq; - range.min_timestamp = vp.timestamp; - - int opus_error = 0; - decoder.reset(opus_decoder_create(opus_sample_rate_hz, opus_channel_count, &opus_error), - &opus_decoder_destroy); - if (opus_error) { - /** - * NOTE: The -10 here makes the opus_error match up with values of exception_error_code, - * which would otherwise conflict as every C library loves to use values from -1 downwards. - */ - throw dpp::voice_exception((exception_error_code)(opus_error - 10), "discord_voice_client::discord_voice_client; opus_decoder_create() failed"); - } - } - - if (vp.seq < range.min_seq && vp.timestamp < range.min_timestamp) { - /* This packet arrived too late. We can only discard it. */ - return; - } - range.max_seq = vp.seq; - range.max_timestamp = vp.timestamp; - payload_queue.push(std::move(vp)); - } - - voice_courier_shared_state.signal_iteration.notify_one(); - - if (!voice_courier.joinable()) { - /* Courier thread is not running, start it */ - voice_courier = std::thread(&voice_courier_loop, - std::ref(*this), - std::ref(voice_courier_shared_state)); - } - } -#else - throw dpp::voice_exception(err_no_voice_support, "Voice support not enabled in this build of D++"); -#endif -} - -void discord_voice_client::write_ready() -{ - uint64_t duration = 0; - bool track_marker_found = false; - uint64_t bufsize = 0; - send_audio_type_t type = satype_recorded_audio; { std::lock_guard lock(this->stream_mutex); - if (!this->paused && outbuf.size()) { - type = send_audio_type; - if (outbuf[0].packet.size() == sizeof(uint16_t) && (*((uint16_t*)(outbuf[0].packet.data()))) == AUDIO_TRACK_MARKER) { - outbuf.erase(outbuf.begin()); - track_marker_found = true; - if (tracks > 0) { - tracks--; - } - } - if (outbuf.size()) { - if (this->udp_send(outbuf[0].packet.data(), outbuf[0].packet.length()) == (int)outbuf[0].packet.length()) { - duration = outbuf[0].duration * timescale; - bufsize = outbuf[0].packet.length(); - outbuf.erase(outbuf.begin()); - } - } - } - } - if (duration) { - if (type == satype_recorded_audio) { - std::chrono::nanoseconds latency = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - last_timestamp); - std::chrono::nanoseconds sleep_time = std::chrono::nanoseconds(duration) - latency; - if (sleep_time.count() > 0) { - std::this_thread::sleep_for(sleep_time); - } - } - else if (type == satype_overlap_audio) { - std::chrono::nanoseconds latency = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - last_timestamp); - std::chrono::nanoseconds sleep_time = std::chrono::nanoseconds(duration) + last_sleep_remainder - latency; - std::chrono::nanoseconds sleep_increment = (std::chrono::nanoseconds(duration) - latency) / AUDIO_OVERLAP_SLEEP_SAMPLES; - if (sleep_time.count() > 0) { - uint16_t samples_count = 0; - std::chrono::nanoseconds overshoot_accumulator{}; - - do { - std::chrono::high_resolution_clock::time_point start_sleep = std::chrono::high_resolution_clock::now(); - std::this_thread::sleep_for(sleep_increment); - std::chrono::high_resolution_clock::time_point end_sleep = std::chrono::high_resolution_clock::now(); - - samples_count++; - overshoot_accumulator += std::chrono::duration_cast(end_sleep - start_sleep) - sleep_increment; - sleep_time -= std::chrono::duration_cast(end_sleep - start_sleep); - } while (std::chrono::nanoseconds(overshoot_accumulator.count() / samples_count) + sleep_increment < sleep_time); - last_sleep_remainder = sleep_time; - } else { - last_sleep_remainder = std::chrono::nanoseconds(0); - } - } - - last_timestamp = std::chrono::high_resolution_clock::now(); - if (!creator->on_voice_buffer_send.empty()) { - voice_buffer_send_t snd(nullptr, ""); - snd.buffer_size = bufsize; - snd.packets_left = outbuf.size(); - snd.voice_client = this; - creator->on_voice_buffer_send.call(snd); - } - } - if (track_marker_found) { - if (!creator->on_voice_track_marker.empty()) { - voice_track_marker_t vtm(nullptr, ""); - vtm.voice_client = this; - { - std::lock_guard lock(this->stream_mutex); - if (!track_meta.empty()) { - vtm.track_meta = track_meta[0]; - track_meta.erase(track_meta.begin()); - } - } - creator->on_voice_track_marker.call(vtm); - } + outbuf.clear(); + track_meta.clear(); + tracks = 0; } + this->send_stop_frames(); + return *this; } dpp::utility::uptime discord_voice_client::get_uptime() @@ -932,19 +186,6 @@ bool discord_voice_client::is_connected() return (this->get_state() == CONNECTED); } -dpp::socket discord_voice_client::want_write() { - std::lock_guard lock(this->stream_mutex); - if (!this->paused && !outbuf.empty()) { - return fd; - } else { - return INVALID_SOCKET; - } -} - -dpp::socket discord_voice_client::want_read() { - return fd; -} - void discord_voice_client::error(uint32_t errorcode) { const static std::map errortext = { @@ -1079,70 +320,28 @@ void discord_voice_client::one_second_timer() if (!message_queue.empty()) { std::string message = message_queue.front(); message_queue.pop_front(); - this->write(message); + this->write(message, OP_TEXT); } } if (this->heartbeat_interval) { /* Check if we're due to emit a heartbeat */ if (time(nullptr) > last_heartbeat + ((heartbeat_interval / 1000.0) * 0.75)) { - queue_message(json({{"op", 3}, {"d", rand()}}).dump(-1, ' ', false, json::error_handler_t::replace), true); + queue_message(json({ + {"op", voice_opcode_connection_heartbeat}, + { + "d", { + {"t", rand()}, + {"seq_ack", receive_sequence}, + } + }, + }).dump(-1, ' ', false, json::error_handler_t::replace), true); last_heartbeat = time(nullptr); } } } } -size_t discord_voice_client::encode(uint8_t *input, size_t inDataSize, uint8_t *output, size_t &outDataSize) -{ -#if HAVE_VOICE - outDataSize = 0; - int mEncFrameBytes = 11520; - int mEncFrameSize = 2880; - if (0 == (inDataSize % mEncFrameBytes)) { - bool isOk = true; - uint8_t *out = encode_buffer; - - memset(out, 0, sizeof(encode_buffer)); - repacketizer = opus_repacketizer_init(repacketizer); - if (!repacketizer) { - log(ll_warning, "opus_repacketizer_init(): failure"); - return outDataSize; - } - for (size_t i = 0; i < (inDataSize / mEncFrameBytes); ++ i) { - const opus_int16* pcm = (opus_int16*)(input + i * mEncFrameBytes); - int ret = opus_encode(encoder, pcm, mEncFrameSize, out, 65536); - if (ret > 0) { - int retval = opus_repacketizer_cat(repacketizer, out, ret); - if (retval != OPUS_OK) { - isOk = false; - log(ll_warning, "opus_repacketizer_cat(): " + std::string(opus_strerror(retval))); - break; - } - out += ret; - } else { - isOk = false; - log(ll_warning, "opus_encode(): " + std::string(opus_strerror(ret))); - break; - } - } - if (isOk) { - int ret = opus_repacketizer_out(repacketizer, output, 65536); - if (ret > 0) { - outDataSize = ret; - } else { - log(ll_warning, "opus_repacketizer_out(): " + std::string(opus_strerror(ret))); - } - } - } else { - throw dpp::voice_exception(err_invalid_voice_packet_length, "Invalid input data length: " + std::to_string(inDataSize) + ", must be n times of " + std::to_string(mEncFrameBytes)); - } -#else - throw dpp::voice_exception(err_no_voice_support, "Voice support not enabled in this build of D++"); -#endif - return outDataSize; -} - discord_voice_client& discord_voice_client::insert_marker(const std::string& metadata) { /* Insert a track marker. A track marker is a single 16 bit value of 0xFFFF. * This is too small to be a valid RTP packet so the send function knows not @@ -1196,114 +395,21 @@ discord_voice_client& discord_voice_client::skip_to_next_marker() { } discord_voice_client& discord_voice_client::send_silence(const uint64_t duration) { - uint8_t silence_packet[3] = { 0xf8, 0xff, 0xfe }; send_audio_opus(silence_packet, 3, duration); return *this; } discord_voice_client& discord_voice_client::set_send_audio_type(send_audio_type_t type) { - { - std::lock_guard lock(this->stream_mutex); - send_audio_type = type; - } - return *this; -} - -discord_voice_client& discord_voice_client::send_audio_raw(uint16_t* audio_data, const size_t length) { -#if HAVE_VOICE - if (length < 4) { - throw dpp::voice_exception(err_invalid_voice_packet_length, "Raw audio packet size can't be less than 4"); - } - - if ((length % 4) != 0) { - throw dpp::voice_exception(err_invalid_voice_packet_length, "Raw audio packet size should be divisible by 4"); - } - - if (length > send_audio_raw_max_length) { - std::string s_audio_data((const char*)audio_data, length); - - while (s_audio_data.length() > send_audio_raw_max_length) { - std::string packet(s_audio_data.substr(0, send_audio_raw_max_length)); - const auto packet_size = static_cast(packet.size()); - - s_audio_data.erase(s_audio_data.begin(), s_audio_data.begin() + packet_size); - - send_audio_raw((uint16_t*)packet.data(), packet_size); - } - - return *this; - } - - if (length < send_audio_raw_max_length) { - std::string packet((const char*)audio_data, length); - packet.resize(send_audio_raw_max_length, 0); - - return send_audio_raw((uint16_t*)packet.data(), packet.size()); - } - - opus_int32 encodedAudioMaxLength = (opus_int32)length; - std::vector encodedAudioData(encodedAudioMaxLength); - size_t encodedAudioLength = encodedAudioMaxLength; - - encodedAudioLength = this->encode((uint8_t*)audio_data, length, encodedAudioData.data(), encodedAudioLength); - - send_audio_opus(encodedAudioData.data(), encodedAudioLength); -#else - throw dpp::voice_exception(err_no_voice_support, "Voice support not enabled in this build of D++"); -#endif - return *this; -} - -discord_voice_client& discord_voice_client::send_audio_opus(uint8_t* opus_packet, const size_t length) { -#if HAVE_VOICE - int samples = opus_packet_get_nb_samples(opus_packet, (opus_int32)length, opus_sample_rate_hz); - uint64_t duration = (samples / 48) / (timescale / 1000000); - send_audio_opus(opus_packet, length, duration); -#else - throw dpp::voice_exception(err_no_voice_support, "Voice support not enabled in this build of D++"); -#endif - return *this; -} - -discord_voice_client& discord_voice_client::send_audio_opus(uint8_t* opus_packet, const size_t length, uint64_t duration) { -#if HAVE_VOICE - int frameSize = (int)(48 * duration * (timescale / 1000000)); - opus_int32 encodedAudioMaxLength = (opus_int32)length; - std::vector encodedAudioData(encodedAudioMaxLength); - size_t encodedAudioLength = encodedAudioMaxLength; - - encodedAudioLength = length; - encodedAudioData.reserve(length); - memcpy(encodedAudioData.data(), opus_packet, length); - - ++sequence; - const int nonceSize = 24; - rtp_header header(sequence, timestamp, (uint32_t)ssrc); - - int8_t nonce[nonceSize]; - std::memcpy(nonce, &header, sizeof(header)); - std::memset(nonce + sizeof(header), 0, sizeof(nonce) - sizeof(header)); - - std::vector audioDataPacket(sizeof(header) + encodedAudioLength + crypto_secretbox_MACBYTES); - std::memcpy(audioDataPacket.data(), &header, sizeof(header)); - - crypto_secretbox_easy(audioDataPacket.data() + sizeof(header), encodedAudioData.data(), encodedAudioLength, (const unsigned char*)nonce, secret_key); - - this->send((const char*)audioDataPacket.data(), audioDataPacket.size(), duration); - timestamp += frameSize; - - speak(); -#else - throw dpp::voice_exception(err_no_voice_support, "Voice support not enabled in this build of D++"); -#endif + std::lock_guard lock(this->stream_mutex); + send_audio_type = type; return *this; } discord_voice_client& discord_voice_client::speak() { if (!this->sending) { this->queue_message(json({ - {"op", 5}, + {"op", voice_opcode_client_speaking}, {"d", { {"speaking", 1}, {"delay", 0}, @@ -1324,47 +430,6 @@ uint64_t discord_voice_client::get_timescale() { return timescale; } -std::string discord_voice_client::discover_ip() { - dpp::socket newfd = SOCKET_ERROR; - unsigned char packet[74] = { 0 }; - (*(uint16_t*)(packet)) = htons(0x01); - (*(uint16_t*)(packet + 2)) = htons(70); - (*(uint32_t*)(packet + 4)) = htonl((uint32_t)this->ssrc); - if ((newfd = ::socket(AF_INET, SOCK_DGRAM, 0)) >= 0) { - sockaddr_in servaddr{}; - memset(&servaddr, 0, sizeof(sockaddr_in)); - servaddr.sin_family = AF_INET; - servaddr.sin_addr.s_addr = htonl(INADDR_ANY); - servaddr.sin_port = htons(0); - if (bind(newfd, (sockaddr*)&servaddr, sizeof(servaddr)) < 0) { - log(ll_warning, "Could not bind socket for IP discovery"); - return ""; - } - memset(&servaddr, 0, sizeof(servaddr)); - servaddr.sin_family = AF_INET; - servaddr.sin_port = htons(this->port); - servaddr.sin_addr.s_addr = inet_addr(this->ip.c_str()); - if (::connect(newfd, (const sockaddr*)&servaddr, sizeof(sockaddr_in)) < 0) { - log(ll_warning, "Could not connect socket for IP discovery"); - return ""; - } - if (::send(newfd, (const char*)packet, 74, 0) == -1) { - log(ll_warning, "Could not send packet for IP discovery"); - return ""; - } - if (recv(newfd, (char*)packet, 74, 0) == -1) { - log(ll_warning, "Could not receive packet for IP discovery"); - return ""; - } - - close_socket(newfd); - - //utility::debug_dump(packet, 74); - return std::string((const char*)(packet + 8)); - } - return ""; -} - discord_voice_client& discord_voice_client::set_iteration_interval(uint16_t interval) { this->iteration_interval = interval; return *this; @@ -1374,4 +439,15 @@ uint16_t discord_voice_client::get_iteration_interval() { return this->iteration_interval; } -} // namespace dpp +discord_voice_client& discord_voice_client::send_stop_frames(bool send_now) { + uint8_t silence_frames[sizeof(silence_packet) / sizeof(*silence_packet) * 5]; + for (size_t i = 0; i < sizeof(silence_frames) / sizeof(*silence_frames); i++) { + silence_frames[i] = silence_packet[i % 3]; + } + + this->send_audio_opus(silence_frames, sizeof(silence_frames) / sizeof(*silence_frames), 20, send_now); + + return *this; +} + +} diff --git a/src/dpp/dispatcher.cpp b/src/dpp/dispatcher.cpp index 6ac9775d40..db8ea2e888 100644 --- a/src/dpp/dispatcher.cpp +++ b/src/dpp/dispatcher.cpp @@ -286,4 +286,4 @@ void voice_receive_t::reassign(discord_voice_client* vc, snowflake _user_id, con audio_size = audio_data.size(); } -} // namespace dpp +} diff --git a/src/dpp/dns.cpp b/src/dpp/dns.cpp index e87fbd45e8..42aed3ce8f 100644 --- a/src/dpp/dns.cpp +++ b/src/dpp/dns.cpp @@ -21,7 +21,6 @@ ************************************************************************************/ #include -#include #include #include #include @@ -39,75 +38,102 @@ namespace dpp /* Cache container */ dns_cache_t dns_cache; - const dns_cache_entry* resolve_hostname(const std::string& hostname, const std::string& port) +/** +* @brief Get address length +* @return address length +*/ +int dns_cache_entry::size() const { + return static_cast(addr.ai_addrlen); +} + +const address_t dns_cache_entry::get_connecting_address(uint16_t port) const { + return address_t(resolved_addr, port); +} + +socket dns_cache_entry::make_connecting_socket() const { + return ::socket(addr.ai_family, addr.ai_socktype, addr.ai_protocol); +} + +const dns_cache_entry* resolve_hostname(const std::string& hostname, const std::string& port) +{ + addrinfo hints, *addrs; + dns_cache_t::const_iterator iter; + time_t now = time(nullptr); + int error; + bool exists = false; + + /* Thread safety scope */ { - addrinfo hints, *addrs; - dns_cache_t::const_iterator iter; - time_t now = time(nullptr); - int error; - bool exists = false; - - /* Thread safety scope */ - { - /* Check cache for existing DNS record. This can use a shared lock. */ - std::shared_lock dns_cache_lock(dns_cache_mutex); - iter = dns_cache.find(hostname); - if (iter != dns_cache.end()) { - exists = true; - if (now < iter->second->expire_timestamp) { - /* there is a cached entry that is still valid, return it */ - return iter->second; - } + /* Check cache for existing DNS record. This can use a shared lock. */ + std::shared_lock dns_cache_lock(dns_cache_mutex); + iter = dns_cache.find(hostname); + if (iter != dns_cache.end()) { + exists = true; + if (now < iter->second->expire_timestamp) { + /* there is a cached entry that is still valid, return it */ + return iter->second; } } - if (exists) { - /* there is a cached entry, but it has expired, - * delete and free it, and fall through to a new lookup. - * We must use a unique lock here as we modify the cache. - */ - std::unique_lock dns_cache_lock(dns_cache_mutex); - iter = dns_cache.find(hostname); - if (iter != dns_cache.end()) { /* re-validate iter */ - delete iter->second; - dns_cache.erase(iter); - } - } - - /* The hints indicate what sort of DNS results we are interested in. - * To change this to support IPv6, one change we need to make here is - * to change AF_INET to AF_UNSPEC. Everything else should just work fine. + } + if (exists) { + /* there is a cached entry, but it has expired, + * delete and free it, and fall through to a new lookup. + * We must use a unique lock here as we modify the cache. */ - memset(&hints, 0, sizeof(addrinfo)); - hints.ai_family = AF_INET; // IPv6 explicitly unsupported by Discord - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - if ((error = getaddrinfo(hostname.c_str(), port.c_str(), &hints, &addrs))) { - /** - * The -20 makes sure the error codes dont conflict with codes given in the rest of the list - * Because C libraries love to use -1 and below directly as conflicting error codes. - */ - throw dpp::connection_exception((exception_error_code)(error - 20), std::string("getaddrinfo error: ") + gai_strerror(error)); + std::unique_lock dns_cache_lock(dns_cache_mutex); + iter = dns_cache.find(hostname); + if (iter != dns_cache.end()) { /* re-validate iter */ + delete iter->second; + dns_cache.erase(iter); } + } + + /* The hints indicate what sort of DNS results we are interested in. + * To change this to support IPv6, one change we need to make here is + * to change AF_INET to AF_UNSPEC. Everything else should just work fine. + */ + memset(&hints, 0, sizeof(addrinfo)); + hints.ai_family = AF_INET; // IPv6 explicitly unsupported by Discord + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; - /* Thread safety scope */ - { - /* Update cache, requires unique lock */ - std::unique_lock dns_cache_lock(dns_cache_mutex); - dns_cache_entry* cache_entry = new dns_cache_entry(); - - /* The sockaddr struct contains a bunch of raw pointers that we - * must copy to the cache, before freeing it with freeaddrinfo(). - * Icky icky C APIs. - */ - memcpy(&cache_entry->addr, addrs, sizeof(addrinfo)); - memcpy(&cache_entry->ai_addr, addrs->ai_addr, addrs->ai_addrlen); - cache_entry->expire_timestamp = now + one_hour; - dns_cache[hostname] = cache_entry; - - /* Now we're done with this horrible struct, free it and return */ - freeaddrinfo(addrs); - return cache_entry; + if ((error = getaddrinfo(hostname.c_str(), port.c_str(), &hints, &addrs))) { + /** + * The -20 makes sure the error codes dont conflict with codes given in the rest of the list + * Because C libraries love to use -1 and below directly as conflicting error codes. + */ + throw dpp::connection_exception((exception_error_code)(error - 20), std::string("getaddrinfo error: ") + gai_strerror(error)); + } + + /* Thread safety scope */ + { + /* Update cache, requires unique lock */ + std::unique_lock dns_cache_lock(dns_cache_mutex); + dns_cache_entry* cache_entry = new dns_cache_entry(); + + for (struct addrinfo* rp = addrs; rp != nullptr; rp = rp->ai_next) { + /* Discord only support ipv4, so iterate over any ipv6 results */ + if (rp->ai_family != AF_INET) { + continue; + } + /* Save address family and other metadata for later */ + memcpy(&cache_entry->addr, rp, sizeof(addrinfo)); + char buffer[128]; + sockaddr_in in{}; + std::memcpy(&in, rp->ai_addr, sizeof(sockaddr_in)); + if (inet_ntop(rp->ai_family, &in.sin_addr, buffer, sizeof(buffer))) { + cache_entry->resolved_addr = buffer; + } + break; } + + cache_entry->expire_timestamp = now + one_hour; + dns_cache[hostname] = cache_entry; + + /* Now we're done with this horrible struct, free it and return */ + freeaddrinfo(addrs); + return cache_entry; } -} // namespace dpp +} + +} diff --git a/src/dpp/dtemplate.cpp b/src/dpp/dtemplate.cpp index 8f67e0efd6..1ed6f53905 100644 --- a/src/dpp/dtemplate.cpp +++ b/src/dpp/dtemplate.cpp @@ -57,4 +57,4 @@ json dtemplate::to_json_impl(bool with_id) const { }; } -} // namespace dpp +} diff --git a/src/dpp/emoji.cpp b/src/dpp/emoji.cpp index b8d0350e20..4d90982e64 100644 --- a/src/dpp/emoji.cpp +++ b/src/dpp/emoji.cpp @@ -22,7 +22,6 @@ #include #include #include -#include namespace dpp { @@ -130,5 +129,5 @@ std::string emoji::get_url(uint16_t size, const dpp::image_type format, bool pre } -} // namespace dpp +} diff --git a/src/dpp/entitlement.cpp b/src/dpp/entitlement.cpp index f3ee034a04..b30871ce58 100644 --- a/src/dpp/entitlement.cpp +++ b/src/dpp/entitlement.cpp @@ -83,4 +83,4 @@ bool entitlement::is_consumed() const { return flags & entitlement_flags::ent_consumed; } -} // namespace dpp +} diff --git a/src/dpp/etf.cpp b/src/dpp/etf.cpp index a76f5d9c41..2b4c25f657 100644 --- a/src/dpp/etf.cpp +++ b/src/dpp/etf.cpp @@ -33,8 +33,6 @@ ************************************************************************************/ #include #include -#include -#include #include #include #include @@ -729,5 +727,5 @@ etf_buffer::etf_buffer(size_t initial) { etf_buffer::~etf_buffer() = default; -} // namespace dpp +} diff --git a/src/dpp/guild.cpp b/src/dpp/guild.cpp index 2e0729e7b2..0cecaaea4c 100644 --- a/src/dpp/guild.cpp +++ b/src/dpp/guild.cpp @@ -20,9 +20,6 @@ ************************************************************************************/ #include #include -#include -#include -#include #include #include #include @@ -217,7 +214,7 @@ void from_json(const nlohmann::json& j, guild_member& gm) { std::string guild_member::get_avatar_url(uint16_t size, const image_type format, bool prefer_animated) const { if (this->guild_id && this->user_id && !this->avatar.to_string().empty()) { return utility::cdn_endpoint_url_hash({ i_jpg, i_png, i_webp, i_gif }, - "guilds/" + std::to_string(this->guild_id) + "/" + std::to_string(this->user_id), this->avatar.to_string(), + "guilds/" + std::to_string(this->guild_id) + "/users/" + std::to_string(this->user_id) + "/avatars", this->avatar.to_string(), format, size, prefer_animated, has_animated_guild_avatar()); } else { return std::string(); @@ -948,7 +945,7 @@ permission guild::permission_overwrites(const guild_member &member, const channe return permissions; } -bool guild::connect_member_voice(snowflake user_id, bool self_mute, bool self_deaf) { +bool guild::connect_member_voice(snowflake user_id, bool self_mute, bool self_deaf, bool dave) { for (auto & c : channels) { channel* ch = dpp::find_channel(c); if (!ch || (!ch->is_voice_channel() && !ch->is_stage_channel())) { @@ -958,7 +955,7 @@ bool guild::connect_member_voice(snowflake user_id, bool self_mute, bool self_de auto vsi = vcmembers.find(user_id); if (vsi != vcmembers.end()) { if (vsi->second.shard) { - vsi->second.shard->connect_voice(this->id, vsi->second.channel_id, self_mute, self_deaf); + vsi->second.shard->connect_voice(this->id, vsi->second.channel_id, self_mute, self_deaf, dave); return true; } } @@ -1196,4 +1193,4 @@ onboarding &onboarding::set_enabled(const bool is_enabled) { } -} // namespace dpp +} diff --git a/src/dpp/httpsclient.cpp b/src/dpp/httpsclient.cpp index 29598cd375..8c7cff3258 100644 --- a/src/dpp/httpsclient.cpp +++ b/src/dpp/httpsclient.cpp @@ -27,8 +27,6 @@ #include #include #include -#include -#include namespace dpp { @@ -58,7 +56,7 @@ void https_client::connect() map_headers += k + ": " + v + "\r\n"; } if (this->sfd != SOCKET_ERROR) { - this->write( + this->socket_write( this->request_type + " " + this->path + " HTTP/" + http_protocol + "\r\n" "Host: " + this->hostname + "\r\n" "pragma: no-cache\r\n" @@ -284,7 +282,7 @@ bool https_client::handle_buffer(std::string &buffer) case HTTPS_CONTENT: body += buffer; buffer.clear(); - if (body.length() >= content_length) { + if (content_length == ULLONG_MAX || body.length() >= content_length) { state = HTTPS_DONE; this->close(); return false; @@ -358,4 +356,4 @@ http_connect_info https_client::get_host_info(std::string url) { return hci; } -} // namespace dpp +} diff --git a/src/dpp/integration.cpp b/src/dpp/integration.cpp index c8e870fde1..e2cd59b51f 100644 --- a/src/dpp/integration.cpp +++ b/src/dpp/integration.cpp @@ -25,8 +25,6 @@ #include #include - - namespace dpp { using json = nlohmann::json; @@ -150,4 +148,4 @@ connection& connection::fill_from_json_impl(nlohmann::json* j) { return *this; } -} // namespace dpp +} diff --git a/src/dpp/invite.cpp b/src/dpp/invite.cpp index c063ac4aed..b33435f6e9 100644 --- a/src/dpp/invite.cpp +++ b/src/dpp/invite.cpp @@ -23,8 +23,6 @@ #include #include - - namespace dpp { using json = nlohmann::json; @@ -117,4 +115,4 @@ invite &invite::set_unique(const bool is_unique) { return *this; } -} // namespace dpp +} diff --git a/src/dpp/message.cpp b/src/dpp/message.cpp index c92ed7426d..76c14495c4 100644 --- a/src/dpp/message.cpp +++ b/src/dpp/message.cpp @@ -683,11 +683,12 @@ message::message(class cluster* o) : message() { owner = o; } -message& message::set_reference(snowflake _message_id, snowflake _guild_id, snowflake _channel_id, bool fail_if_not_exists) { +message& message::set_reference(snowflake _message_id, snowflake _guild_id, snowflake _channel_id, bool fail_if_not_exists, message_ref_type type) { message_reference.channel_id = _channel_id; message_reference.guild_id = _guild_id; message_reference.message_id = _message_id; message_reference.fail_if_not_exists = fail_if_not_exists; + message_reference.type = type; return *this; } @@ -1125,6 +1126,7 @@ json message::to_json(bool with_id, bool is_interaction_response) const { /* Populate message reference */ if (message_reference.channel_id || message_reference.guild_id || message_reference.message_id) { j["message_reference"] = json::object(); + j["message_reference"]["type"] = static_cast(message_reference.type); if (message_reference.channel_id) { j["message_reference"]["channel_id"] = std::to_string(message_reference.channel_id); } @@ -1419,10 +1421,17 @@ message& message::fill_from_json(json* d, cache_policy_t cp) { } if (d->find("message_reference") != d->end()) { json& mr = (*d)["message_reference"]; + message_reference.type = static_cast(int8_not_null(&mr, "type")); message_reference.channel_id = snowflake_not_null(&mr, "channel_id"); message_reference.guild_id = snowflake_not_null(&mr, "guild_id"); message_reference.message_id = snowflake_not_null(&mr, "message_id"); message_reference.fail_if_not_exists = bool_not_null(&mr, "fail_if_not_exists"); + + if (message_reference.type == mrt_forward) { + for (auto& e : (*d)["message_snapshots"]) { + message_snapshots.messages.emplace_back(message().fill_from_json(&(e["message"]), cp)); + } + } } if (auto it = d->find("poll"); it != d->end()) { from_json(*it, attached_poll.emplace()); @@ -1550,4 +1559,4 @@ sticker& sticker::set_file_content(std::string_view fc) { } -} // namespace dpp +} diff --git a/src/dpp/permissions.cpp b/src/dpp/permissions.cpp index 02dd8325d7..58eefaa3ce 100644 --- a/src/dpp/permissions.cpp +++ b/src/dpp/permissions.cpp @@ -27,4 +27,4 @@ permission::operator nlohmann::json() const { return std::to_string(value); } -} // namespace dpp +} diff --git a/src/dpp/presence.cpp b/src/dpp/presence.cpp index 34809b1c4c..7b40dc9a0d 100644 --- a/src/dpp/presence.cpp +++ b/src/dpp/presence.cpp @@ -21,12 +21,8 @@ ************************************************************************************/ #include #include -#include -#include #include - - namespace dpp { using json = nlohmann::json; @@ -35,8 +31,8 @@ std::string activity::get_large_asset_url(uint16_t size, const image_type format if (!this->assets.large_image.empty() && this->application_id && this->assets.large_image.find(':') == std::string::npos) { // make sure it's not a prefixed proxy image return utility::cdn_endpoint_url({ i_jpg, i_png, i_webp }, - "app-assets/" + std::to_string(this->application_id) + "/" + this->assets.large_image, - format, size); + "app-assets/" + std::to_string(this->application_id) + "/" + this->assets.large_image, + format, size); } else { return std::string(); } @@ -46,8 +42,8 @@ std::string activity::get_small_asset_url(uint16_t size, const image_type format if (!this->assets.small_image.empty() && this->application_id && this->assets.small_image.find(':') == std::string::npos) { // make sure it's not a prefixed proxy image return utility::cdn_endpoint_url({ i_jpg, i_png, i_webp }, - "app-assets/" + std::to_string(this->application_id) + "/" + this->assets.small_image, - format, size); + "app-assets/" + std::to_string(this->application_id) + "/" + this->assets.small_image, + format, size); } else { return std::string(); } @@ -308,4 +304,4 @@ presence_status presence::status() const { return (presence_status)((flags >> PF_SHIFT_MAIN) & PF_STATUS_MASK); } -} // namespace dpp +} diff --git a/src/dpp/prune.cpp b/src/dpp/prune.cpp index afd7f9ae52..befea860f0 100644 --- a/src/dpp/prune.cpp +++ b/src/dpp/prune.cpp @@ -51,4 +51,4 @@ json prune::to_json(bool with_id) const { return to_json_impl(with_id); } -} // namespace dpp +} diff --git a/src/dpp/queues.cpp b/src/dpp/queues.cpp index 8505606556..96aa1d5288 100644 --- a/src/dpp/queues.cpp +++ b/src/dpp/queues.cpp @@ -28,7 +28,6 @@ #include #include #include -#include namespace dpp { @@ -480,4 +479,4 @@ bool request_queue::is_globally_ratelimited() const return this->globally_ratelimited; } -} // namespace dpp +} diff --git a/src/dpp/role.cpp b/src/dpp/role.cpp index 89643f210f..877cf6d0a4 100644 --- a/src/dpp/role.cpp +++ b/src/dpp/role.cpp @@ -23,12 +23,8 @@ #include #include #include -#include -#include #include - - namespace dpp { using json = nlohmann::json; @@ -485,4 +481,4 @@ json application_role_connection::to_json_impl(bool with_id) const { } -} // namespace dpp +} diff --git a/src/dpp/scheduled_event.cpp b/src/dpp/scheduled_event.cpp index fe7e2c7a4c..2af52a4e5f 100644 --- a/src/dpp/scheduled_event.cpp +++ b/src/dpp/scheduled_event.cpp @@ -19,7 +19,6 @@ * ************************************************************************************/ #include -#include #include #include #include @@ -193,4 +192,4 @@ json scheduled_event::to_json_impl(bool with_id) const { return j; } -} // namespace dpp +} diff --git a/src/dpp/sku.cpp b/src/dpp/sku.cpp index 170a158159..52b0b62161 100644 --- a/src/dpp/sku.cpp +++ b/src/dpp/sku.cpp @@ -86,4 +86,4 @@ bool sku::is_user_subscription() const { return flags & sku_flags::sku_user_subscription; } -} // namespace dpp +} diff --git a/src/dpp/slashcommand.cpp b/src/dpp/slashcommand.cpp index 86a97fbbc5..54c1ca364f 100644 --- a/src/dpp/slashcommand.cpp +++ b/src/dpp/slashcommand.cpp @@ -20,7 +20,6 @@ ************************************************************************************/ #include #include -#include #include #include #include @@ -972,4 +971,4 @@ std::string command_interaction::get_mention() const { std::string slashcommand::get_mention() const { return dpp::utility::slashcommand_mention(id, name); } -} // namespace dpp +} diff --git a/src/dpp/snowflake.cpp b/src/dpp/snowflake.cpp index 72d566756c..2257b3158c 100644 --- a/src/dpp/snowflake.cpp +++ b/src/dpp/snowflake.cpp @@ -44,4 +44,4 @@ snowflake::operator json() const { return std::to_string(value); } -} // namespace dpp +} diff --git a/src/dpp/socket.cpp b/src/dpp/socket.cpp new file mode 100644 index 0000000000..9313405b75 --- /dev/null +++ b/src/dpp/socket.cpp @@ -0,0 +1,63 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#include +#include +#include + +namespace dpp { + +address_t::address_t(const std::string_view ip, uint16_t port) { + sockaddr_in address{}; + address.sin_family = AF_INET; + address.sin_port = htons(port); + address.sin_addr.s_addr = inet_addr(ip.data()); + std::memcpy(&socket_addr, &address, sizeof(address)); +} + +sockaddr* address_t::get_socket_address() { + return &socket_addr; +} + +size_t address_t::size() { + return sizeof(sockaddr_in); +} + +uint16_t address_t::get_port(socket fd) { + socklen_t len = size(); + if (getsockname(fd, &socket_addr, &len) > -1) { + sockaddr_in sin{}; + std::memcpy(&sin, &socket_addr, sizeof(sockaddr_in)); + return ntohs(sin.sin_port); + } + return 0; +} + +raii_socket::raii_socket() : fd(::socket(AF_INET, SOCK_DGRAM, 0)) { +} + +raii_socket::~raii_socket() { + close_socket(fd); +} + + +} \ No newline at end of file diff --git a/src/dpp/sslclient.cpp b/src/dpp/sslclient.cpp index e66dd1204a..7cdf92150e 100644 --- a/src/dpp/sslclient.cpp +++ b/src/dpp/sslclient.cpp @@ -37,20 +37,13 @@ #else /* Anyting other than Windows (e.g. sane OSes) */ #include - #include - #include - #include #include - #include #include #endif -#include -#include -#include +#include #include #include -#include -#include +#include #include #include /* Windows specific OpenSSL symbol weirdness */ @@ -70,6 +63,7 @@ #include #include #include +#include #include /* Maximum allowed time in milliseconds for socket read/write timeouts and connect() */ @@ -318,10 +312,11 @@ void ssl_client::connect() /* Resolve hostname to IP */ int err = 0; const dns_cache_entry* addr = resolve_hostname(hostname, port); - sfd = ::socket(addr->addr.ai_family, addr->addr.ai_socktype, addr->addr.ai_protocol); + sfd = addr->make_connecting_socket(); + address_t destination = addr->get_connecting_address(from_string(this->port, std::dec)); if (sfd == ERROR_STATUS) { err = errno; - } else if (connect_with_timeout(sfd, (sockaddr*)&addr->ai_addr, (int)addr->addr.ai_addrlen, SOCKET_OP_TIMEOUT) != 0) { + } else if (connect_with_timeout(sfd, destination.get_socket_address(), destination.size(), SOCKET_OP_TIMEOUT) != 0) { close_socket(sfd); sfd = ERROR_STATUS; } @@ -379,7 +374,7 @@ void ssl_client::connect() } } -void ssl_client::write(const std::string_view data) +void ssl_client::socket_write(const std::string_view data) { /* If we are in nonblocking mode, append to the buffer, * otherwise just use SSL_write directly. The only time we @@ -674,4 +669,4 @@ ssl_client::~ssl_client() cleanup(); } -} // namespace dpp +} diff --git a/src/dpp/stage_instance.cpp b/src/dpp/stage_instance.cpp index 4fde6c9dd3..dc11433e97 100644 --- a/src/dpp/stage_instance.cpp +++ b/src/dpp/stage_instance.cpp @@ -55,4 +55,4 @@ json stage_instance::to_json_impl(bool with_id) const { return j; } -} // namespace dpp +} diff --git a/src/dpp/thread.cpp b/src/dpp/thread.cpp index 8cfe5d618a..5ffc1f44f7 100644 --- a/src/dpp/thread.cpp +++ b/src/dpp/thread.cpp @@ -22,7 +22,6 @@ #include #include - namespace dpp { thread_member& thread_member::fill_from_json_impl(nlohmann::json* j) { @@ -46,12 +45,14 @@ thread& thread::fill_from_json_impl(json* j) { set_int8_not_null(j, "member_count", this->member_count); set_bool_not_null(j, "newly_created", this->newly_created); - auto json_metadata = (*j)["thread_metadata"]; - metadata.archived = bool_not_null(&json_metadata, "archived"); - metadata.archive_timestamp = ts_not_null(&json_metadata, "archive_timestamp"); - metadata.auto_archive_duration = int16_not_null(&json_metadata, "auto_archive_duration"); - metadata.locked = bool_not_null(&json_metadata, "locked"); - metadata.invitable = bool_not_null(&json_metadata, "invitable"); + if (j->contains("thread_metadata")) { + auto json_metadata = (*j)["thread_metadata"]; + metadata.archived = bool_not_null(&json_metadata, "archived"); + metadata.archive_timestamp = ts_not_null(&json_metadata, "archive_timestamp"); + metadata.auto_archive_duration = int16_not_null(&json_metadata, "auto_archive_duration"); + metadata.locked = bool_not_null(&json_metadata, "locked"); + metadata.invitable = bool_not_null(&json_metadata, "invitable"); + } /* Only certain events set this */ if (j->contains("member")) { diff --git a/src/dpp/user.cpp b/src/dpp/user.cpp index 64f21ae898..7f2d08cf4d 100644 --- a/src/dpp/user.cpp +++ b/src/dpp/user.cpp @@ -24,8 +24,8 @@ #include namespace dpp { -using json = nlohmann:: -json; + +using json = nlohmann::json; /* A mapping of discord's flag values to our bitmap (they're different bit positions to fit other stuff in) */ std::map usermap = { @@ -46,12 +46,7 @@ std::map usermap = { { 1 << 22, dpp::u_active_developer}, }; -user::user() : - managed(), - flags(0), - discriminator(0), - refcount(1) -{ +user::user() : managed(), flags(0), discriminator(0), refcount(1) { } std::string user::get_mention(const snowflake& id) { @@ -98,8 +93,8 @@ std::string user::get_avatar_url(uint16_t size, const image_type format, bool pr return get_default_avatar_url(); } else if (this->id) { return utility::cdn_endpoint_url_hash({ i_jpg, i_png, i_webp, i_gif }, - "avatars/" + std::to_string(this->id), this->avatar.to_string(), - format, size, prefer_animated, has_animated_icon()); + "avatars/" + std::to_string(this->id), this->avatar.to_string(), + format, size, prefer_animated, has_animated_icon()); } else { return std::string(); } @@ -108,12 +103,12 @@ std::string user::get_avatar_url(uint16_t size, const image_type format, bool pr std::string user::get_default_avatar_url() const { if (this->discriminator) { return utility::cdn_endpoint_url({ i_png }, - "embed/avatars/" + std::to_string(this->discriminator % 5), - i_png, 0); + "embed/avatars/" + std::to_string(this->discriminator % 5), + i_png, 0); } else if (this->id){ return utility::cdn_endpoint_url({ i_png }, - "embed/avatars/" + std::to_string((this->id >> 22) % 6), - i_png, 0); + "embed/avatars/" + std::to_string((this->id >> 22) % 6), + i_png, 0); } else { return std::string(); } @@ -122,8 +117,8 @@ std::string user::get_default_avatar_url() const { std::string user::get_avatar_decoration_url(uint16_t size) const { if (this->id) { return utility::cdn_endpoint_url_hash({ i_png }, - "avatar-decorations/" + std::to_string(this->id), this->avatar_decoration.to_string(), - i_png, size); + "avatar-decorations/" + std::to_string(this->id), this->avatar_decoration.to_string(), + i_png, size); } else { return std::string(); } @@ -253,8 +248,8 @@ bool user_identified::has_animated_banner() const { std::string user_identified::get_banner_url(uint16_t size, const image_type format, bool prefer_animated) const { if (!this->banner.to_string().empty() && this->id) { return utility::cdn_endpoint_url_hash({ i_jpg, i_png, i_webp, i_gif }, - "banners/" + std::to_string(this->id), this->banner.to_string(), - format, size, prefer_animated, has_animated_banner()); + "banners/" + std::to_string(this->id), this->banner.to_string(), + format, size, prefer_animated, has_animated_banner()); } else { return std::string(); } @@ -309,4 +304,4 @@ void from_json(const nlohmann::json& j, user& u) { } } -} // namespace dpp +} diff --git a/src/dpp/utility.cpp b/src/dpp/utility.cpp index 0b07ec0e8f..922fc4195e 100644 --- a/src/dpp/utility.cpp +++ b/src/dpp/utility.cpp @@ -21,7 +21,6 @@ ************************************************************************************/ #include #include -#include #include #include #include @@ -34,9 +33,6 @@ #include #include #include -#include -#include -#include #ifdef _WIN32 #include @@ -355,25 +351,25 @@ image_data&& icon::as_image_data() && { return std::move(std::get(hash_or_data)); } -std::string debug_dump(uint8_t* data, size_t length) { +std::string debug_dump(const uint8_t* data, size_t length) { std::ostringstream out; size_t addr = (size_t)data; size_t extra = addr % 16; if (extra != 0) { addr -= extra; - out << to_hex(addr); + out << "\n[" << to_hex(addr) << "] : "; } for (size_t n = 0; n < extra; ++n) { out << "-- "; } std::string ascii; - for (uint8_t* ptr = data; ptr < data + length; ++ptr) { + for (const uint8_t* ptr = data; ptr < data + length; ++ptr) { if (((size_t)ptr % 16) == 0) { out << ascii << "\n[" << to_hex((size_t)ptr) << "] : "; ascii.clear(); } ascii.push_back(*ptr >= ' ' && *ptr <= '~' ? *ptr : '.'); - out << to_hex(*ptr); + out << to_hex(*ptr) << " "; } out << " " << ascii; out << "\n"; @@ -936,4 +932,4 @@ void set_thread_name(const std::string& name) { #endif } -} // namespace dpp::utility +} diff --git a/src/dpp/voice/enabled/audio_mix.cpp b/src/dpp/voice/enabled/audio_mix.cpp new file mode 100644 index 0000000000..c43d712313 --- /dev/null +++ b/src/dpp/voice/enabled/audio_mix.cpp @@ -0,0 +1,51 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dpp { + +size_t audio_mix(discord_voice_client &client, audio_mixer &mixer, opus_int32 *pcm_mix, const opus_int16 *pcm, size_t park_count, int samples, int &max_samples) { + /* Mix the combined stream if combined audio is bound */ + if (client.creator->on_voice_receive_combined.empty()) { + return 0; + } + + /* We must upsample the data to 32 bits wide, otherwise we could overflow */ + for (opus_int32 v = 0; v < (samples * opus_channel_count) / mixer.byte_blocks_per_register; ++v) { + mixer.combine_samples(pcm_mix, pcm); + pcm += mixer.byte_blocks_per_register; + pcm_mix += mixer.byte_blocks_per_register; + } + client.moving_average += park_count; + max_samples = (std::max)(samples, max_samples); + return park_count + 1; +} + +}; diff --git a/src/dpp/voice/enabled/cleanup.cpp b/src/dpp/voice/enabled/cleanup.cpp new file mode 100644 index 0000000000..5ae1d9c7e2 --- /dev/null +++ b/src/dpp/voice/enabled/cleanup.cpp @@ -0,0 +1,60 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "../../dave/encryptor.h" +#include "enabled.h" + +namespace dpp { + +void discord_voice_client::cleanup() +{ + if (runner) { + this->terminating = true; + runner->join(); + delete runner; + runner = nullptr; + } + if (encoder) { + opus_encoder_destroy(encoder); + encoder = nullptr; + } + if (repacketizer) { + opus_repacketizer_destroy(repacketizer); + repacketizer = nullptr; + } + if (voice_courier.joinable()) { + { + std::lock_guard lk(voice_courier_shared_state.mtx); + voice_courier_shared_state.terminating = true; + } + voice_courier_shared_state.signal_iteration.notify_one(); + voice_courier.join(); + } +} + +} diff --git a/src/dpp/voice/enabled/constructor.cpp b/src/dpp/voice/enabled/constructor.cpp new file mode 100644 index 0000000000..fc8e16d0d5 --- /dev/null +++ b/src/dpp/voice/enabled/constructor.cpp @@ -0,0 +1,82 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include "../../dave/encryptor.h" +#include "enabled.h" + +namespace dpp { + +discord_voice_client::discord_voice_client(dpp::cluster* _cluster, snowflake _channel_id, snowflake _server_id, const std::string &_token, const std::string &_session_id, const std::string &_host, bool enable_dave) + : websocket_client(_host.substr(0, _host.find(':')), _host.substr(_host.find(':') + 1, _host.length()), "/?v=" + std::to_string(voice_protocol_version), OP_TEXT), + runner(nullptr), + connect_time(0), + mixer(std::make_unique()), + port(0), + ssrc(0), + timescale(1000000), + paused(false), + encoder(nullptr), + repacketizer(nullptr), + fd(INVALID_SOCKET), + sequence(0), + receive_sequence(-1), + timestamp(0), + packet_nonce(1), + last_timestamp(std::chrono::high_resolution_clock::now()), + sending(false), + tracks(0), + dave_version(enable_dave ? dave_version_1 : dave_version_none), + creator(_cluster), + terminating(false), + heartbeat_interval(0), + last_heartbeat(time(nullptr)), + token(_token), + sessionid(_session_id), + server_id(_server_id), + channel_id(_channel_id) +{ + int opusError = 0; + encoder = opus_encoder_create(opus_sample_rate_hz, opus_channel_count, OPUS_APPLICATION_VOIP, &opusError); + if (opusError) { + throw dpp::voice_exception(err_opus, "discord_voice_client::discord_voice_client; opus_encoder_create() failed"); + } + repacketizer = opus_repacketizer_create(); + if (!repacketizer) { + throw dpp::voice_exception(err_opus, "discord_voice_client::discord_voice_client; opus_repacketizer_create() failed"); + } + try { + this->connect(); + } + catch (...) { + cleanup(); + throw; + } +} + +} diff --git a/src/dpp/voice/enabled/courier_loop.cpp b/src/dpp/voice/enabled/courier_loop.cpp new file mode 100644 index 0000000000..72ccccdbfc --- /dev/null +++ b/src/dpp/voice/enabled/courier_loop.cpp @@ -0,0 +1,293 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include "../../dave/encryptor.h" + +#include "enabled.h" + +namespace dpp { + +void discord_voice_client::voice_courier_loop(discord_voice_client& client, courier_shared_state_t& shared_state) { + utility::set_thread_name(std::string("vcourier/") + std::to_string(client.server_id)); + + while (true) { + std::this_thread::sleep_for(std::chrono::milliseconds{client.iteration_interval}); + + struct flush_data_t { + snowflake user_id; + rtp_seq_t min_seq; + std::priority_queue parked_payloads; + std::vector> pending_decoder_ctls; + std::shared_ptr decoder; + }; + std::vector flush_data; + + /* + * Transport the payloads onto this thread, and + * release the lock as soon as possible. + */ + { + std::unique_lock lk(shared_state.mtx); + + /* mitigates vector resizing while holding the mutex */ + flush_data.reserve(shared_state.parked_voice_payloads.size()); + + bool has_payload_to_deliver = false; + for (auto &[user_id, parking_lot]: shared_state.parked_voice_payloads) { + has_payload_to_deliver = has_payload_to_deliver || !parking_lot.parked_payloads.empty(); + + flush_data.push_back(flush_data_t{ + user_id, + parking_lot.range.min_seq, + std::move(parking_lot.parked_payloads), + /* Quickly check if we already have a decoder and only take the pending ctls if so. */ + parking_lot.decoder ? std::move(parking_lot.pending_decoder_ctls) + : decltype(parking_lot.pending_decoder_ctls){}, + parking_lot.decoder + }); + + parking_lot.range.min_seq = parking_lot.range.max_seq + 1; + parking_lot.range.min_timestamp = parking_lot.range.max_timestamp + 1; + } + + if (!has_payload_to_deliver) { + if (shared_state.terminating) { + /* We have delivered all data to handlers. Terminate now. */ + break; + } + + shared_state.signal_iteration.wait(lk, [&shared_state](){ + if (shared_state.terminating) { + return true; + } + + /* + * Actually check the state we're looking for instead of waking up + * everytime read_ready was called. + */ + for (auto &[user_id, parking_lot]: shared_state.parked_voice_payloads) { + if (parking_lot.parked_payloads.empty()) { + continue; + } + return true; + } + return false; + }); + + /* + * More data came or about to terminate, or just a spurious wake. + * We need to collect the payloads again to determine what to do next. + */ + continue; + } + } + + if (client.creator->on_voice_receive.empty() && client.creator->on_voice_receive_combined.empty()) { + /* + * We do this check late, to ensure this thread drains the data + * and prevents accumulating them even when there are no handlers. + */ + continue; + } + + /* This 32 bit PCM audio buffer is an upmixed version of the streams + * combined for all users. This is a wider width audio buffer so that + * there is no clipping when there are many loud audio sources at once. + */ + opus_int32 pcm_mix[23040] = {0}; + size_t park_count = 0; + int max_samples = 0; + int samples = 0; + + opus_int16 flush_data_pcm[23040]; + for (auto &d: flush_data) { + if (!d.decoder) { + continue; + } + for (const auto &decoder_ctl: d.pending_decoder_ctls) { + decoder_ctl(*d.decoder); + } + + for (rtp_seq_t seq = d.min_seq; !d.parked_payloads.empty(); ++seq) { + if (d.parked_payloads.top().seq != seq) { + /* + * Lost a packet with sequence number "seq", + * But Opus decoder might be able to guess something. + */ + if (int lost_packet_samples = opus_decode(d.decoder.get(), nullptr, 0, flush_data_pcm, 5760, 0); + lost_packet_samples >= 0) { + /* + * Since this sample comes from a lost packet, + * we can only pretend there is an event, without any raw payload byte. + */ + voice_receive_t vr(nullptr, "", &client, d.user_id, + reinterpret_cast(flush_data_pcm), + lost_packet_samples * opus_channel_count * sizeof(opus_int16)); + + park_count = audio_mix(client, *client.mixer, pcm_mix, flush_data_pcm, park_count, lost_packet_samples, max_samples); + client.creator->on_voice_receive.call(vr); + } + } else { + voice_receive_t &vr = *d.parked_payloads.top().vr; + + /* + * We do decryption here to avoid blocking ssl_client and saving cpu time by doing it when needed only. + * + * NOTE: You do not want to send audio while also listening for on_voice_receive/on_voice_receive_combined. + * It will cause gaps in your recording, I have no idea why exactly. + */ + + constexpr size_t header_size = 12; + + uint8_t *buffer = vr.audio_data.data(); + size_t packet_size = vr.audio_data.size(); + + constexpr size_t nonce_size = sizeof(uint32_t); + /* Nonce is 4 byte at the end of payload with zero padding */ + uint8_t nonce[24] = { 0 }; + std::memcpy(nonce, buffer + packet_size - nonce_size, nonce_size); + + /* Get the number of CSRC in header */ + const size_t csrc_count = buffer[0] & 0b0000'1111; + /* Skip to the encrypted voice data */ + const ptrdiff_t offset_to_data = header_size + sizeof(uint32_t) * csrc_count; + size_t total_header_len = offset_to_data; + + uint8_t* ciphertext = buffer + offset_to_data; + size_t ciphertext_len = packet_size - offset_to_data - nonce_size; + + size_t ext_len = 0; + if ([[maybe_unused]] const bool uses_extension = (buffer[0] >> 4) & 0b0001) { + /** + * Get the RTP Extensions size, we only get the size here because + * the extension itself is encrypted along with the opus packet + */ + { + uint16_t ext_len_in_words; + memcpy(&ext_len_in_words, &ciphertext[2], sizeof(uint16_t)); + ext_len_in_words = ntohs(ext_len_in_words); + ext_len = sizeof(uint32_t) * ext_len_in_words; + } + constexpr size_t ext_header_len = sizeof(uint16_t) * 2; + ciphertext += ext_header_len; + ciphertext_len -= ext_header_len; + total_header_len += ext_header_len; + } + + uint8_t decrypted[65535] = { 0 }; + unsigned long long opus_packet_len = 0; + if (ssl_crypto_aead_xchacha20poly1305_ietf_decrypt( + decrypted, &opus_packet_len, + nullptr, + ciphertext, ciphertext_len, + buffer, + /** + * Additional Data: + * The whole header (including csrc list) + + * 4 byte extension header (magic 0xBEDE + 16-bit denoting extension length) + */ + total_header_len, + nonce, vr.voice_client->secret_key.data()) != 0) { + /* Invalid Discord RTP payload. */ + return; + } + + uint8_t *opus_packet = decrypted; + if (ext_len > 0) { + /* Skip previously encrypted RTP Header Extension */ + opus_packet += ext_len; + opus_packet_len -= ext_len; + } + + /** + * If DAVE is enabled, use the user's ratchet to decrypt the OPUS audio data + */ + std::vector decrypted_dave_frame; + if (vr.voice_client->is_end_to_end_encrypted()) { + auto decryptor = vr.voice_client->mls_state->decryptors.find(vr.user_id); + + if (decryptor != vr.voice_client->mls_state->decryptors.end()) { + decrypted_dave_frame.resize(decryptor->second->get_max_plaintext_byte_size(dave::media_type::media_audio, opus_packet_len)); + + size_t enc_len = decryptor->second->decrypt( + dave::media_type::media_audio, + dave::make_array_view(opus_packet, opus_packet_len), + dave::make_array_view(decrypted_dave_frame) + ); + + if (enc_len > 0) { + opus_packet = decrypted_dave_frame.data(); + opus_packet_len = enc_len; + } + } + } + + if (opus_packet_len > 0x7FFFFFFF) { + throw dpp::length_exception(err_massive_audio, "audio_data > 2GB! This should never happen!"); + } + + samples = opus_decode(d.decoder.get(), opus_packet, static_cast(opus_packet_len & 0x7FFFFFFF), flush_data_pcm, 5760, 0); + + if (samples >= 0) { + vr.reassign(&client, d.user_id, reinterpret_cast(flush_data_pcm), samples * opus_channel_count * sizeof(opus_int16)); + + client.end_gain = 1.0f / client.moving_average; + park_count = audio_mix(client, *client.mixer, pcm_mix, flush_data_pcm, park_count, samples, max_samples); + + client.creator->on_voice_receive.call(vr); + } + + d.parked_payloads.pop(); + } + } + } + /* If combined receive is bound, dispatch it */ + if (park_count) { + /* Downsample the 32 bit samples back to 16 bit */ + opus_int16 pcm_downsample[23040] = {0}; + opus_int16 *pcm_downsample_ptr = pcm_downsample; + opus_int32 *pcm_mix_ptr = pcm_mix; + client.increment = (client.end_gain - client.current_gain) / static_cast(samples); + + for (int64_t x = 0; x < (samples * opus_channel_count) / client.mixer->byte_blocks_per_register; ++x) { + client.mixer->collect_single_register(pcm_mix_ptr, pcm_downsample_ptr, client.current_gain, client.increment); + client.current_gain += client.increment * static_cast(client.mixer->byte_blocks_per_register); + pcm_mix_ptr += client.mixer->byte_blocks_per_register; + pcm_downsample_ptr += client.mixer->byte_blocks_per_register; + } + + voice_receive_t vr(nullptr, "", &client, 0, reinterpret_cast(pcm_downsample), + max_samples * opus_channel_count * sizeof(opus_int16)); + + client.creator->on_voice_receive_combined.call(vr); + } + } +} + +} diff --git a/src/dpp/voice/enabled/discover_ip.cpp b/src/dpp/voice/enabled/discover_ip.cpp new file mode 100644 index 0000000000..7061c20f08 --- /dev/null +++ b/src/dpp/voice/enabled/discover_ip.cpp @@ -0,0 +1,184 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#include +#include +#include "enabled.h" + +#ifdef _WIN32 + #include + #include + #include + #define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) + #define pollfd WSAPOLLFD +#else + #include + #include + #include +#endif + +namespace dpp { + +/** + * @brief Represents an IP discovery packet sent to Discord or received + * from Discord. + * + * https://discord.com/developers/docs/topics/voice-connections#ip-discovery + */ +struct ip_discovery_packet { + + /** + * @brief Maximum size of packet + */ + static constexpr int DISCOVERY_PACKET_SIZE = 74; + + /** + * @brief Maximum length of IP address string + */ + static constexpr int ADDRESS_BUFFER_SIZE = 64; + + /** + * @brief Type of packet + */ + uint16_t type; + + /** + * @brief Length of packet + */ + uint16_t length; + + /** + * @brief SSRC of sender + */ + uint32_t ssrc; + + /** + * @brief Address buffer, contains IP address in returned packet + */ + char address[ADDRESS_BUFFER_SIZE]{0}; // NOLINT + + /** + * @brief Port number, contains port in returned packet + */ + uint16_t port; + + /** + * @brief Construct discovery packet from inbound recv() buffer contents + * @param packet_buffer recv buffer contents of at least ADDRESS_BUFFER_SIZE bytes + */ + ip_discovery_packet(char* packet_buffer) + : type(ntohs(packet_buffer[0] << 8 | packet_buffer[1])), + length(ntohs(packet_buffer[2] << 8 | packet_buffer[3])), + ssrc(ntohl(packet_buffer[4] << 24 | packet_buffer[5] << 16 | packet_buffer[6] << 8 | packet_buffer[7])), + port(ntohs(packet_buffer[72] << 8 | packet_buffer[73])) + { + std::memcpy(address, packet_buffer + 8, ADDRESS_BUFFER_SIZE); + } + + /** + * @brief Build a const char* buffer for sending with send() to make a request + * @return char buffer + */ + const std::array build_buffer() { + std::array buffer{0}; + buffer[0] = ((type & 0xff00) >> 8) & 0xff; + buffer[1] = type & 0xff; + buffer[2] = (length & 0xff00) >> 8; + buffer[3] = length & 0xff; + buffer[4] = ((ssrc & 0xff000000) >> 24) & 0xff; + buffer[5] = ((ssrc & 0x00ff0000) >> 16) & 0xff; + buffer[6] = ((ssrc & 0x0000ff00) >> 8) & 0xff; + buffer[7] = ssrc & 0x000000ff; + return buffer; + } + + /** + * @brief Deleted default constructor + */ + ip_discovery_packet() = delete; + + /** + * @brief Build a request packet for a given SSRC. + * type and length will be initialised correctly and the address + * buffer will be zeroed. + * @param _ssrc SSRC value + */ + ip_discovery_packet(uint32_t _ssrc) : + /* Packet length is size of header minus type and length fields, usually 70 bytes */ + type(0x01), length(DISCOVERY_PACKET_SIZE - sizeof(type) - sizeof(length)), + ssrc(_ssrc), port(0) { + std::memset(&address, 0, ADDRESS_BUFFER_SIZE); + } +}; + +constexpr int discovery_timeout = 1000; + +std::string discord_voice_client::discover_ip() { + + if (!external_ip.empty()) { + return external_ip; + } + + raii_socket socket; + ip_discovery_packet discovery(this->ssrc); + + if (socket.fd >= 0) { + address_t bind_any; + if (bind(socket.fd, bind_any.get_socket_address(), bind_any.size()) < 0) { + log(ll_warning, "Could not bind socket for IP discovery"); + return ""; + } + address_t bind_port(this->ip, this->port); + if (::connect(socket.fd, bind_port.get_socket_address(), bind_port.size()) < 0) { + log(ll_warning, "Could not connect socket for IP discovery"); + return ""; + } + if (::send(socket.fd, discovery.build_buffer().data(), ip_discovery_packet::DISCOVERY_PACKET_SIZE, 0) == -1) { + log(ll_warning, "Could not send packet for IP discovery"); + return ""; + } + /* Wait one second for receipt of IP detection packet response */ + pollfd pfd{}; + pfd.fd = socket.fd; + pfd.events = POLLIN; + int ret = ::poll(&pfd, 1, discovery_timeout); + switch (ret) { + case -1: + log(ll_warning, "poll() error on IP discovery"); + return ""; + case 0: + log(ll_warning, "Timed out in IP discovery"); + return ""; + default: + char buffer[ip_discovery_packet::DISCOVERY_PACKET_SIZE]{0}; + if (recv(socket.fd, buffer, sizeof(buffer), 0) == -1) { + log(ll_warning, "Could not receive packet for IP discovery"); + return ""; + } + ip_discovery_packet inbound_packet(buffer); + return {inbound_packet.address}; + } + } + return {}; +} + +} diff --git a/src/dpp/voice/enabled/displayable_code.cpp b/src/dpp/voice/enabled/displayable_code.cpp new file mode 100644 index 0000000000..685ff5d204 --- /dev/null +++ b/src/dpp/voice/enabled/displayable_code.cpp @@ -0,0 +1,55 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#include +#include +#include +#include + +#include "../../dave/encryptor.h" + +namespace dpp { + +std::string generate_displayable_code(const std::vector &data, size_t desired_length = 30, size_t group_size = 5) { + + if (data.empty()) { + return ""; + } + + const size_t group_modulus = std::pow(10, group_size); + std::stringstream result; + + for (size_t i = 0; i < desired_length; i += group_size) { + size_t group_value{0}; + + for (size_t j = group_size; j > 0; --j) { + const size_t next_byte = data.at(i + (group_size - j)); + group_value = (group_value << 8) | next_byte; + } + group_value %= group_modulus; + result << std::setw(group_size) << std::setfill('0') << std::to_string(group_value) << " "; + } + + return result.str(); +} + +} diff --git a/src/dpp/voice/enabled/enabled.h b/src/dpp/voice/enabled/enabled.h new file mode 100644 index 0000000000..10df19081c --- /dev/null +++ b/src/dpp/voice/enabled/enabled.h @@ -0,0 +1,201 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../dave/session.h" +#include "../../dave/decryptor.h" +#include "../../dave/encryptor.h" + +#ifdef _WIN32 +#include + #include + #include +#else + #include + #include + #include +#endif + +namespace dpp { + +/** + * @brief A list of MLS decryptors for decrypting inbound audio from users by snowflake id + */ +using decryptor_list = std::map>; + +/** + * @brief Holds all internal DAVE E2EE encryption state + */ +struct dave_state { + /** + * @brief libdave session + */ + std::unique_ptr dave_session{}; + /** + * @brief Our key package + */ + std::shared_ptr<::mlspp::SignaturePrivateKey> mls_key; + /** + * @brief Current transition ID + */ + uint64_t transition_id{0}; + /** + * @brief Have sent ready event to listeners + */ + bool done_ready{false}; + /** + * @brief Details of upcoming transition + */ + struct { + /** + * @brief pending next transition ID + */ + uint64_t id{0}; + /** + * @brief New upcoming protocol version + */ + uint64_t protocol_version{0}; + /** + * @brief True if transition is pending + */ + bool is_pending{false}; + } pending_transition; + /** + * @brief Decryptors for inbound audio streams + */ + decryptor_list decryptors; + /** + * @brief Encryptor for outbound audio stream + */ + std::unique_ptr encryptor; + /** + * @brief Current privacy code, or empty string if + * MLS group is not established. + */ + std::string privacy_code; + + /** + * @brief Cached roster map to track rosters changes. + */ + dave::roster_map cached_roster_map; +}; + +/** + * @brief Represents an RTP packet. Size should always be exactly 12. + */ +struct rtp_header { + uint16_t constant; + uint16_t sequence; + uint32_t timestamp; + uint32_t ssrc; + + rtp_header(uint16_t _seq, uint32_t _ts, uint32_t _ssrc) : constant(htons(0x8078)), sequence(htons(_seq)), timestamp(htonl(_ts)), ssrc(htonl(_ssrc)) { + } +}; + +/** +* @brief Transport encryption type (libssl) +*/ +constexpr std::string_view transport_encryption_protocol = "aead_xchacha20_poly1305_rtpsize"; + +std::string generate_displayable_code(const std::vector &data, size_t desired_length = 30, size_t group_size = 5); + +size_t audio_mix(discord_voice_client &client, audio_mixer &mixer, opus_int32 *pcm_mix, const opus_int16 *pcm, size_t park_count, int samples, int &max_samples); + +} + +/** + * @brief OpenSSL based reimplementation of sodium's crypto_aead_xchacha20poly1305_ietf_encrypt + * @note Parameters and types are intended to match sodium as to be a drop-in replacement. + * @param c Ciphertext + Tag output + * @param clen Ciphertext length output + * @param m Message (plaintext) input + * @param mlen Message length + * @param ad Additional authenticated data (AAD) + * @param adlen Authenticated data length + * @param nsec Secret nonce (optional, nullptr to not use) + * @param npub Public nonce (24 bytes) + * @param k Key (32 bytes) + * @return 0 on success, -1 on error + */ +int ssl_crypto_aead_xchacha20poly1305_ietf_encrypt(unsigned char *c, unsigned long long *clen, const unsigned char *m, unsigned long long mlen, const unsigned char *ad, unsigned long long adlen, const unsigned char *nsec, const unsigned char *npub, const unsigned char *k); + +/** + * @brief OpenSSL based reimplementation of sodium's crypto_aead_xchacha20poly1305_ietf_decrypt + * @note Parameters and types are intended to match sodium as to be a drop-in replacement. + * @param m Message (plaintext) output + * @param mlen message length output + * @param nsec Secret nonce (optional, nullptr to not use) + * @param c Ciphertext + Tag input + * @param clen Ciphertext length + * @param ad Additional authenticated data (AAD) + * @param adlen Additional authenticated data length + * @param npub Public nonce (24 bytes) + * @param k Key (32 bytes) + * @return 0 on success, -1 on error + */ +int ssl_crypto_aead_xchacha20poly1305_ietf_decrypt(unsigned char *m, unsigned long long *mlen, unsigned char *nsec, const unsigned char *c, unsigned long long clen, const unsigned char *ad, unsigned long long adlen, const unsigned char *npub, const unsigned char *k); + +/** + * @brief Size of public nonce (24 bytes) + * @note This constant is a drop-in replacement for one in libsodium + */ +inline constexpr unsigned int ssl_crypto_aead_xchacha20poly1305_ietf_NPUBBYTES = 24U; + +/** + * @brief AAD size + * @note This constant is a drop-in replacement for one in libsodium + */ +inline constexpr unsigned int ssl_crypto_aead_xchacha20poly1305_IETF_ABYTES = 16U; + diff --git a/src/dpp/voice/enabled/handle_frame.cpp b/src/dpp/voice/enabled/handle_frame.cpp new file mode 100644 index 0000000000..0b84cc1ca4 --- /dev/null +++ b/src/dpp/voice/enabled/handle_frame.cpp @@ -0,0 +1,627 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#include +#include +#include +#include +#include +#include "../../dave/encryptor.h" + +#include "enabled.h" + +namespace dpp { + +using namespace std::chrono_literals; + +/** + * @brief How long to wait after deriving new key ratchets to expire the old ones + */ +constexpr dave::decryptor::duration RATCHET_EXPIRY = 10s; + +void discord_voice_client::update_ratchets(bool force) { + + if (!mls_state || !mls_state->dave_session) { + return; + } + + /** + * Update everyone's ratchets including the bot. Whenever a new user joins or a user leaves, this invalidates + * all the old ratchets and they are replaced with new ones and the old ones are invalidated after RATCHET_EXPIRY seconds. + */ + log(ll_debug, "Updating MLS ratchets for " + std::to_string(dave_mls_user_list.size() + 1) + " user(s)"); + for (const auto& user : dave_mls_user_list) { + dpp::snowflake u{user}; + if (u == creator->me.id) { + continue; + } + decryptor_list::iterator decryptor; + /* New user join/old user leave - insert new ratchets if they don't exist */ + decryptor = mls_state->decryptors.find(u); + if (decryptor == mls_state->decryptors.end()) { + log(ll_debug, "Inserting decryptor key ratchet for NEW user: " + user + ", protocol version: " + std::to_string(mls_state->dave_session->get_protocol_version())); + auto [iter, inserted] = mls_state->decryptors.emplace(u, std::make_unique(*creator)); + decryptor = iter; + } + decryptor->second->transition_to_key_ratchet(mls_state->dave_session->get_key_ratchet(user), RATCHET_EXPIRY); + } + + /* + * Encryptor should always be present on execute transition. + * Should we throw error if it's missing here? + */ + if (mls_state->encryptor) { + /* Updating key rachet should always be done on execute transition. Generally after group member add/remove. */ + log(ll_debug, "Setting key ratchet for sending audio..."); + mls_state->encryptor->set_key_ratchet(mls_state->dave_session->get_key_ratchet(creator->me.id.str())); + } + + /** + * https://www.ietf.org/archive/id/draft-ietf-mls-protocol-14.html#name-epoch-authenticators + * 9.7. Epoch Authenticators + * The main MLS key schedule provides a per-epoch epoch_authenticator. If one member of the group is being impersonated by an active attacker, + * the epoch_authenticator computed by their client will differ from those computed by the other group members. + */ + std::string old_code = mls_state->privacy_code; + mls_state->privacy_code = generate_displayable_code(mls_state->dave_session->get_last_epoch_authenticator()); + if (!mls_state->privacy_code.empty() && mls_state->privacy_code != old_code) { + log(ll_info, "New E2EE Privacy Code: " + mls_state->privacy_code); + } + +} + +bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcode) { + json j; + + /** + * MLS frames come in as type OP_BINARY, we can also reply to them as type OP_BINARY. + */ + if (opcode == OP_BINARY && data.size() >= sizeof(dave_binary_header_t)) { + + dave_binary_header_t dave_header(data); + + /* These binaries also contains sequence number we need to save */ + receive_sequence = dave_header.seq; + + switch (dave_header.opcode) { + case voice_client_dave_mls_external_sender: { + log(ll_debug, "voice_client_dave_mls_external_sender"); + + mls_state->dave_session->set_external_sender(dave_header.get_data()); + } + break; + case voice_client_dave_mls_proposals: { + log(ll_debug, "voice_client_dave_mls_proposals"); + + std::optional> response = mls_state->dave_session->process_proposals(dave_header.get_data(), dave_mls_user_list); + if (response.has_value()) { + auto r = response.value(); + r.insert(r.begin(), voice_client_dave_mls_commit_message); + this->write(std::string_view(reinterpret_cast(r.data()), r.size()), OP_BINARY); + } + } + break; + case voice_client_dave_announce_commit_transition: { + this->mls_state->transition_id = dave_header.get_transition_id(); + log(ll_debug, "voice_client_dave_announce_commit_transition"); + auto r = mls_state->dave_session->process_commit(dave_header.get_data()); + + /* + * We need to do recovery here when we failed processing the message + */ + if (!std::holds_alternative(r)) { + log(ll_debug, "Unable to process commit in transition " + std::to_string(this->mls_state->transition_id)); + + this->recover_from_invalid_commit_welcome(); + break; + } + + auto rmap = std::get(r); + this->process_mls_group_rosters(rmap); + + this->ready_for_transition(data); + } + break; + case voice_client_dave_mls_welcome: { + this->mls_state->transition_id = dave_header.get_transition_id(); + log(ll_debug, "voice_client_dave_mls_welcome with transition id " + std::to_string(this->mls_state->transition_id)); + + /* We should always recognize our own selves, but do we? */ + dave_mls_user_list.insert(this->creator->me.id.str()); + + auto r = mls_state->dave_session->process_welcome(dave_header.get_data(), dave_mls_user_list); + + /* + * We need to do recovery here when we failed processing the message + */ + if (!r.has_value()) { + log(ll_debug, "Unable to process welcome in transition " + std::to_string(this->mls_state->transition_id)); + + this->recover_from_invalid_commit_welcome(); + break; + } + + this->process_mls_group_rosters(r.value()); + this->ready_for_transition(data); + } + break; + default: + log(ll_debug, "Unexpected DAVE frame opcode"); + log(dpp::ll_trace, "R: " + dpp::utility::debug_dump(reinterpret_cast(data.data()), data.length())); + break; + } + + return true; + } + + try { + log(dpp::ll_trace, std::string("R: ") + data); + j = json::parse(data); + } + catch (const std::exception &e) { + log(dpp::ll_error, std::string("discord_voice_client::handle_frame ") + e.what() + ": " + data); + return true; + } + + if (j.find("seq") != j.end() && j["seq"].is_number()) { + /** + * Save the sequence number needed for heartbeat and resume payload. + * + * NOTE: Contrary to the documentation, discord does not seem to send messages with sequence number + * in order, should we only save the sequence if it's larger number? + */ + receive_sequence = j["seq"].get(); + } + + if (j.find("op") != j.end()) { + uint32_t op = j["op"]; + + switch (op) { + /* Ping acknowledgement */ + case voice_opcode_connection_heartbeat_ack: + /* These opcodes do not require a response or further action */ + break; + case voice_opcode_media_sink: + case voice_client_flags: { + } + break; + case voice_client_platform: { + voice_client_platform_t vcp(nullptr, data); + vcp.voice_client = this; + vcp.user_id = snowflake_not_null(&j["d"], "user_id"); + vcp.platform = static_cast(int8_not_null(&j["d"], "platform")); + creator->on_voice_client_platform.call(vcp); + } + break; + case voice_opcode_multiple_clients_connect: { + /** + * @brief The list of users that just joined for DAVE + */ + std::set joining_dave_users = j["d"]["user_ids"]; + + dave_mls_user_list.insert(joining_dave_users.begin(), joining_dave_users.end()); + + /* Remove this user from pending remove list if exist */ + for (const auto &user : joining_dave_users) { + dave_mls_pending_remove_list.erase(user); + } + + log(ll_debug, "New of clients in voice channel: " + std::to_string(joining_dave_users.size()) + " total is " + std::to_string(dave_mls_user_list.size())); + } + break; + case voice_client_dave_mls_invalid_commit_welcome: { + this->mls_state->transition_id = j["d"]["transition_id"]; + log(ll_debug, "voice_client_dave_mls_invalid_commit_welcome transition id " + std::to_string(this->mls_state->transition_id)); + } + break; + case voice_client_dave_execute_transition: { + log(ll_debug, "voice_client_dave_execute_transition"); + this->mls_state->transition_id = j["d"]["transition_id"]; + + if (this->mls_state->pending_transition.is_pending && this->execute_pending_upgrade_downgrade()) { + break; + } + + /* + * Execute transition from a commit/welcome message. + */ + update_ratchets(); + } + break; + /* "The protocol only uses this opcode to indicate when a downgrade to protocol version 0 is upcoming." */ + case voice_client_dave_prepare_transition: { + this->mls_state->transition_id = j["d"]["transition_id"]; + uint64_t protocol_version = j["d"]["protocol_version"]; + + this->mls_state->pending_transition = {this->mls_state->transition_id, protocol_version, true}; + + log(ll_debug, "voice_client_dave_prepare_transition version=" + std::to_string(protocol_version) + " for transition " + std::to_string(this->mls_state->transition_id)); + + if (this->mls_state->transition_id == 0) { + this->execute_pending_upgrade_downgrade(); + } else { + json obj = { + { "op", voice_client_dave_transition_ready }, + { + "d", + { + { "transition_id", this->mls_state->transition_id }, + } + } + }; + this->write(obj.dump(-1, ' ', false, json::error_handler_t::replace), OP_TEXT); + } + } + break; + case voice_client_dave_prepare_epoch: { + uint64_t protocol_version = j["d"]["protocol_version"]; + uint32_t epoch = j["d"]["epoch"]; + log(ll_debug, "voice_client_dave_prepare_epoch version=" + std::to_string(protocol_version) + " for epoch " + std::to_string(epoch)); + if (epoch == 1) { + /* An epoch 1 is the start of new dave session, update dave_version */ + dave_version = protocol_version == 1 ? dave_version_1 : dave_version_none; + + this->reinit_dave_mls_group(); + } + } + break; + /* Client Disconnect */ + case voice_opcode_client_disconnect: { + if (j.find("d") != j.end() && j["d"].find("user_id") != j["d"].end() && !j["d"]["user_id"].is_null()) { + snowflake u_id = snowflake_not_null(&j["d"], "user_id"); + + log(ll_debug, "User left voice channel: " + u_id.str()); + + auto it = std::find_if(ssrc_map.begin(), ssrc_map.end(), [&u_id](const auto & p) { return p.second == u_id; }); + + if (it != ssrc_map.end()) { + ssrc_map.erase(it); + } + + /* Mark this user for remove on immediate upgrade */ + dave_mls_pending_remove_list.insert(u_id.str()); + + if (!creator->on_voice_client_disconnect.empty()) { + voice_client_disconnect_t vcd(nullptr, data); + vcd.voice_client = this; + vcd.user_id = u_id; + creator->on_voice_client_disconnect.call(vcd); + } + } + } + break; + /* Speaking */ + case voice_opcode_client_speaking: { + if (j.find("d") != j.end() + && j["d"].find("user_id") != j["d"].end() && !j["d"]["user_id"].is_null() + && j["d"].find("ssrc") != j["d"].end() && !j["d"]["ssrc"].is_null() && j["d"]["ssrc"].is_number_integer()) { + uint32_t u_ssrc = j["d"]["ssrc"].get(); + snowflake u_id = snowflake_not_null(&j["d"], "user_id"); + ssrc_map[u_ssrc] = u_id; + + if (!creator->on_voice_client_speaking.empty()) { + voice_client_speaking_t vcs(nullptr, data); + vcs.voice_client = this; + vcs.user_id = u_id; + vcs.ssrc = u_ssrc; + creator->on_voice_client_speaking.call(vcs); + } + } + } + break; + /* Voice resume */ + case voice_opcode_connection_resumed: + log(ll_debug, "Voice connection resumed"); + break; + /* Voice HELLO */ + case voice_opcode_connection_hello: { + if (j.find("d") != j.end() && j["d"].find("heartbeat_interval") != j["d"].end() && !j["d"]["heartbeat_interval"].is_null()) { + this->heartbeat_interval = j["d"]["heartbeat_interval"].get(); + } + + /* Reset receive_sequence on HELLO */ + receive_sequence = -1; + + if (!modes.empty()) { + log(dpp::ll_debug, "Resuming voice session " + this->sessionid + "..."); + json obj = { + { "op", voice_opcode_connection_resume }, + { + "d", + { + { "server_id", std::to_string(this->server_id) }, + { "session_id", this->sessionid }, + { "token", this->token }, + { "seq_ack", this->receive_sequence }, + } + } + }; + this->write(obj.dump(-1, ' ', false, json::error_handler_t::replace), OP_TEXT); + } else { + log(dpp::ll_debug, "Connecting new voice session (DAVE: " + std::string(dave_version == dave_version_1 ? "Enabled" : "Disabled") + ")..."); + json obj = { + { "op", voice_opcode_connection_identify }, + { + "d", + { + { "user_id", creator->me.id }, + { "server_id", std::to_string(this->server_id) }, + { "session_id", this->sessionid }, + { "token", this->token }, + { "max_dave_protocol_version", dave_version }, + } + } + }; + this->write(obj.dump(-1, ' ', false, json::error_handler_t::replace), OP_TEXT); + } + this->connect_time = time(nullptr); + } + break; + /* Session description */ + case voice_opcode_connection_description: { + json &d = j["d"]; + size_t ofs = 0; + for (auto & c : d["secret_key"]) { + secret_key[ofs++] = (uint8_t)c; + if (ofs > secret_key.size() - 1) { + break; + } + } + has_secret_key = true; + + /* Reset packet_nonce */ + packet_nonce = 1; + + bool ready_now = false; + + if (dave_version != dave_version_none) { + /* DAVE ready later */ + if (j["d"]["dave_protocol_version"] != static_cast(dave_version)) { + log(ll_error, "We requested DAVE E2EE but didn't receive it from the server, downgrading..."); + dave_version = dave_version_none; + ready_now = true; + } else { + if (mls_state == nullptr) { + mls_state = std::make_unique(); + } + if (mls_state->dave_session == nullptr) { + mls_state->dave_session = std::make_unique( + *creator, + nullptr, "", [this](std::string const &s1, std::string const &s2) { + log(ll_debug, "DAVE: " + s1 + ", " + s2); + }); + } + this->reinit_dave_mls_group(); + + /* Ready now if there's no DAVE user waiting in the vc */ + if (dave_mls_user_list.empty()) { + ready_now = true; + } + } + } else { + /* Non-DAVE ready immediately */ + ready_now = true; + } + + if (ready_now) { + /* This is needed to start voice receiving and make sure that the start of sending isn't cut off */ + send_silence(20); + /* Fire on_voice_ready */ + if (!creator->on_voice_ready.empty()) { + voice_ready_t rdy(nullptr, data); + rdy.voice_client = this; + rdy.voice_channel_id = this->channel_id; + creator->on_voice_ready.call(rdy); + } + } + } + break; + /* Voice ready */ + case voice_opcode_connection_ready: { + /* Video stream stuff comes in this frame too, but we can't use it (YET!) */ + json &d = j["d"]; + this->ip = d["ip"].get(); + this->port = d["port"].get(); + this->ssrc = d["ssrc"].get(); + destination = address_t(this->ip, this->port); + + // Modes + for (auto & m : d["modes"]) { + this->modes.push_back(m.get()); + } + log(ll_debug, "Voice websocket established; UDP endpoint: " + ip + ":" + std::to_string(port) + " [ssrc=" + std::to_string(ssrc) + "] with " + std::to_string(modes.size()) + " modes"); + + dpp::socket newfd = 0; + if ((newfd = ::socket(AF_INET, SOCK_DGRAM, 0)) >= 0) { + + address_t bind_any; + if (bind(newfd, bind_any.get_socket_address(), bind_any.size()) < 0) { + throw dpp::connection_exception(err_bind_failure, "Can't bind() client UDP socket"); + } + + if (!set_nonblocking(newfd, true)) { + throw dpp::connection_exception(err_nonblocking_failure, "Can't switch voice UDP socket to non-blocking mode!"); + } + + /* Hook poll() in the ssl_client to add a new file descriptor */ + this->fd = newfd; + this->custom_writeable_fd = [this] { return want_write(); }; + this->custom_readable_fd = [this] { return want_read(); }; + this->custom_writeable_ready = [this] { write_ready(); }; + this->custom_readable_ready = [this] { read_ready(); }; + + int bound_port = address_t().get_port(this->fd); + this->write(json({ + { "op", voice_opcode_connection_select_protocol }, + { "d", { + { "protocol", "udp" }, + { "data", { + { "address", discover_ip() }, + { "port", bound_port }, + { "mode", transport_encryption_protocol } + } + } + } + } + }).dump(-1, ' ', false, json::error_handler_t::replace), OP_TEXT); + } + } + break; + default: { + log(ll_debug, "Unknown voice opcode " + std::to_string(op) + ": " + data); + } + break; + } + } + return true; +} + + +/* + * Handle DAVE frame utilities. + */ + +void discord_voice_client::ready_for_transition(const std::string &data) { + log(ll_debug, "Ready to execute transition " + std::to_string(this->mls_state->transition_id)); + json obj = { + { "op", voice_client_dave_transition_ready }, + { + "d", + { + { "transition_id", this->mls_state->transition_id }, + } + } + }; + this->write(obj.dump(-1, ' ', false, json::error_handler_t::replace), OP_TEXT); + this->mls_state->pending_transition.id = this->mls_state->transition_id; + + /* When the included transition ID is 0, the transition is for (re)initialization, and it can be executed immediately. */ + if (this->mls_state->transition_id == 0) { + /* Mark state ready and update ratchets the first time */ + update_ratchets(); + } + + if (!this->mls_state->done_ready) { + this->mls_state->done_ready = true; + + if (!creator->on_voice_ready.empty()) { + voice_ready_t rdy(nullptr, data); + rdy.voice_client = this; + rdy.voice_channel_id = this->channel_id; + creator->on_voice_ready.call(rdy); + } + } +} + +void discord_voice_client::recover_from_invalid_commit_welcome() { + json obj = { + {"op", voice_client_dave_mls_invalid_commit_welcome}, + { + "d", { + "transition_id", this->mls_state->transition_id + } + } + }; + this->write(obj.dump(-1, ' ', false, json::error_handler_t::replace), OP_TEXT); + this->reinit_dave_mls_group(); +} + +bool discord_voice_client::execute_pending_upgrade_downgrade() { + bool did_upgrade_downgrade = false; + + if (this->mls_state->transition_id != this->mls_state->pending_transition.id) { + log(ll_debug, "execute_pending_upgrade_downgrade unexpected transition_id, we never received voice_client_dave_prepare_transition event with this id: " + std::to_string(this->mls_state->transition_id)); + } else if (dave_version != this->mls_state->pending_transition.protocol_version) { + dave_version = this->mls_state->pending_transition.protocol_version == 1 ? dave_version_1 : dave_version_none; + + if (this->mls_state->pending_transition.protocol_version != 0 && dave_version == dave_version_none) { + log(ll_debug, "execute_pending_upgrade_downgrade unexpected protocol version: " + std::to_string(this->mls_state->pending_transition.protocol_version)+ " in transition " + std::to_string(this->mls_state->transition_id)); + } else { + log(ll_debug, "execute_pending_upgrade_downgrade upgrade/downgrade successful"); + did_upgrade_downgrade = true; + } + } + + this->mls_state->pending_transition.is_pending = false; + return did_upgrade_downgrade; +} + +void discord_voice_client::reinit_dave_mls_group() { + mls_state->dave_session->init(dave::max_protocol_version(), channel_id, creator->me.id.str(), mls_state->mls_key); + + auto key_response = mls_state->dave_session->get_marshalled_key_package(); + key_response.insert(key_response.begin(), voice_client_dave_mls_key_package); + this->write(std::string_view(reinterpret_cast(key_response.data()), key_response.size()), OP_BINARY); + + mls_state->encryptor = std::make_unique(*creator); + mls_state->decryptors.clear(); + + mls_state->cached_roster_map.clear(); + + mls_state->privacy_code.clear(); + + /* Remove any user in pending remove from MLS member list */ + for (const auto &user : dave_mls_pending_remove_list) { + dave_mls_user_list.erase(user); + } + dave_mls_pending_remove_list.clear(); +} + +void discord_voice_client::process_mls_group_rosters(const dave::roster_map &rmap) { + log(ll_debug, "process_mls_group_rosters of size: " + std::to_string(rmap.size())); + + for (const auto &[k, v] : rmap) { + bool user_has_key = !v.empty(); + + /* Debug log for changed and added keys */ + auto cached_user = mls_state->cached_roster_map.find(k); + if (cached_user == mls_state->cached_roster_map.end()) { + log(ll_debug, "Added user to MLS Group: " + std::to_string(k)); + } else if (user_has_key && cached_user->second != v) { + log(ll_debug, "Changed user key in MLS Group: " + std::to_string(k)); + } + + /* + * Remove user from recognized list. + * Do not remove user with non-empty key. + */ + if (user_has_key) { + continue; + } + + dpp::snowflake u_id(k); + auto u_id_str = u_id.str(); + + log(ll_debug, "Removed user from MLS Group: " + u_id_str); + + dave_mls_user_list.erase(u_id_str); + dave_mls_pending_remove_list.erase(u_id_str); + + /* Remove this user's key ratchet */ + mls_state->decryptors.erase(u_id); + } + + mls_state->cached_roster_map = rmap; +} + +} + diff --git a/src/dpp/voice/enabled/opus.cpp b/src/dpp/voice/enabled/opus.cpp new file mode 100644 index 0000000000..a92886ac9c --- /dev/null +++ b/src/dpp/voice/enabled/opus.cpp @@ -0,0 +1,207 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#include +#include +#include +#include +#include +#include "../../dave/array_view.h" +#include "../../dave/encryptor.h" + +#include "enabled.h" + +namespace dpp { + +discord_voice_client& discord_voice_client::send_audio_raw(uint16_t* audio_data, const size_t length) { + if (length < 4) { + throw dpp::voice_exception(err_invalid_voice_packet_length, "Raw audio packet size can't be less than 4"); + } + + if ((length % 4) != 0) { + throw dpp::voice_exception(err_invalid_voice_packet_length, "Raw audio packet size should be divisible by 4"); + } + + if (length > send_audio_raw_max_length) { + std::string s_audio_data(reinterpret_cast(audio_data), length); + + while (s_audio_data.length() > send_audio_raw_max_length) { + std::string packet(s_audio_data.substr(0, send_audio_raw_max_length)); + const auto packet_size = static_cast(packet.size()); + + s_audio_data.erase(s_audio_data.begin(), s_audio_data.begin() + packet_size); + + send_audio_raw(reinterpret_cast(packet.data()), packet_size); + } + + return *this; + } + + if (length < send_audio_raw_max_length) { + std::string packet(reinterpret_cast(audio_data), length); + packet.resize(send_audio_raw_max_length, 0); + + return send_audio_raw(reinterpret_cast(packet.data()), packet.size()); + } + + opus_int32 encoded_audio_max_length = (opus_int32)length; + std::vector encoded_audio(encoded_audio_max_length); + size_t encoded_audio_length = encoded_audio_max_length; + encoded_audio_length = this->encode(reinterpret_cast(audio_data), length, encoded_audio.data(), encoded_audio_length); + send_audio_opus(encoded_audio.data(), encoded_audio_length); + return *this; +} + +discord_voice_client& discord_voice_client::send_audio_opus(const uint8_t* opus_packet, const size_t length) { + int samples = opus_packet_get_nb_samples(opus_packet, (opus_int32)length, opus_sample_rate_hz); + uint64_t duration = (samples / 48) / (timescale / 1000000); + send_audio_opus(opus_packet, length, duration, false); + return *this; +} + +discord_voice_client& discord_voice_client::send_audio_opus(const uint8_t* opus_packet, const size_t length, uint64_t duration, bool send_now) { + int frame_size = (int)(48 * duration * (timescale / 1000000)); + opus_int32 encoded_audio_max_length = (opus_int32)length; + std::vector encoded_audio(encoded_audio_max_length); + size_t encoded_audio_length = encoded_audio_max_length; + + encoded_audio_length = length; + encoded_audio.reserve(length); + memcpy(encoded_audio.data(), opus_packet, length); + + if (this->is_end_to_end_encrypted()) { + + std::vector encrypted_buffer(this->mls_state->encryptor->get_max_ciphertext_byte_size(dave::media_type::media_audio, length)); + size_t out_size{0}; + + auto result = this->mls_state->encryptor->encrypt( + dave::media_type::media_audio, + ssrc, + dave::make_array_view(encoded_audio.data(), length), + dave::make_array_view(encrypted_buffer), + &out_size + ); + encrypted_buffer.resize(out_size); + if (result != dave::encryptor::result_code::rc_success) { + log(ll_warning, "DAVE Encryption failure: " + std::to_string(result)); + } else { + encoded_audio = encrypted_buffer; + encoded_audio_length = encrypted_buffer.size(); + } + } + + ++sequence; + rtp_header header(sequence, timestamp, (uint32_t)ssrc); + + /* Expected payload size is unencrypted header + encrypted opus packet + unencrypted 32 bit nonce */ + size_t packet_siz = sizeof(header) + (encoded_audio_length + ssl_crypto_aead_xchacha20poly1305_IETF_ABYTES) + sizeof(packet_nonce); + + std::vector payload(packet_siz); + + /* Set RTP header */ + std::memcpy(payload.data(), &header, sizeof(header)); + + /* Convert nonce to big-endian */ + uint32_t noncel = htonl(packet_nonce); + + /* 24 byte is needed for encrypting, discord just want 4 byte so just fill up the rest with null */ + unsigned char encrypt_nonce[ssl_crypto_aead_xchacha20poly1305_ietf_NPUBBYTES] = { '\0' }; + memcpy(encrypt_nonce, &noncel, sizeof(noncel)); + + /* Execute */ + unsigned long long int clen{0}; + if (ssl_crypto_aead_xchacha20poly1305_ietf_encrypt( + payload.data() + sizeof(header), + &clen, + encoded_audio.data(), + encoded_audio_length, + /* The RTP Header as Additional Data */ + reinterpret_cast(&header), + sizeof(header), + nullptr, + static_cast(encrypt_nonce), + secret_key.data() + ) != 0) { + log(dpp::ll_debug, "XChaCha20 Encryption failed"); + } + + /* Append the 4 byte nonce to the resulting payload */ + std::memcpy(payload.data() + payload.size() - sizeof(noncel), &noncel, sizeof(noncel)); + + this->send(reinterpret_cast(payload.data()), payload.size(), duration, send_now); + + timestamp += frame_size; + + /* Increment for next packet */ + packet_nonce++; + + speak(); + return *this; +} + +size_t discord_voice_client::encode(uint8_t *input, size_t inDataSize, uint8_t *output, size_t &outDataSize) { + outDataSize = 0; + int mEncFrameBytes = 11520; + int mEncFrameSize = 2880; + if (0 == (inDataSize % mEncFrameBytes)) { + bool isOk = true; + uint8_t *out = encode_buffer; + + memset(out, 0, sizeof(encode_buffer)); + repacketizer = opus_repacketizer_init(repacketizer); + if (!repacketizer) { + log(ll_warning, "opus_repacketizer_init(): failure"); + return outDataSize; + } + for (size_t i = 0; i < (inDataSize / mEncFrameBytes); ++ i) { + const opus_int16* pcm = reinterpret_cast(input + i * mEncFrameBytes); + int ret = opus_encode(encoder, pcm, mEncFrameSize, out, 65536); + if (ret > 0) { + int retval = opus_repacketizer_cat(repacketizer, out, ret); + if (retval != OPUS_OK) { + isOk = false; + log(ll_warning, "opus_repacketizer_cat(): " + std::string(opus_strerror(retval))); + break; + } + out += ret; + } else { + isOk = false; + log(ll_warning, "opus_encode(): " + std::string(opus_strerror(ret))); + break; + } + } + if (isOk) { + int ret = opus_repacketizer_out(repacketizer, output, 65536); + if (ret > 0) { + outDataSize = ret; + } else { + log(ll_warning, "opus_repacketizer_out(): " + std::string(opus_strerror(ret))); + } + } + } else { + throw dpp::voice_exception(err_invalid_voice_packet_length, "Invalid input data length: " + std::to_string(inDataSize) + ", must be n times of " + std::to_string(mEncFrameBytes)); + } + return outDataSize; +} + + +} diff --git a/src/dpp/voice/enabled/read_ready.cpp b/src/dpp/voice/enabled/read_ready.cpp new file mode 100644 index 0000000000..03c79b0486 --- /dev/null +++ b/src/dpp/voice/enabled/read_ready.cpp @@ -0,0 +1,135 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include "../../dave/decryptor.h" + +#include "enabled.h" + +namespace dpp { + +void discord_voice_client::read_ready() +{ + uint8_t buffer[65535]; + int packet_size = this->udp_recv(reinterpret_cast(buffer), sizeof(buffer)); + + bool receive_handler_is_empty = creator->on_voice_receive.empty() && creator->on_voice_receive_combined.empty(); + if (packet_size <= 0 || receive_handler_is_empty) { + /* Nothing to do */ + return; + } + + constexpr size_t header_size = 12; + if (static_cast(packet_size) < header_size) { + /* Invalid RTP payload */ + return; + } + + /* It's a "silence packet" - throw it away. */ + if (packet_size < 44) { + return; + } + + if (uint8_t payload_type = buffer[1] & 0b0111'1111; + 72 <= payload_type && payload_type <= 76) { + /* + * This is an RTCP payload. Discord is known to send + * RTCP Receiver Reports. + * + * See https://datatracker.ietf.org/doc/html/rfc3551#section-6 + */ + return; + } + + voice_payload vp{0, // seq, populate later + 0, // timestamp, populate later + std::make_unique(nullptr, std::string(reinterpret_cast(buffer), packet_size))}; + + vp.vr->voice_client = this; + + uint32_t speaker_ssrc; + { /* Get the User ID of the speaker */ + std::memcpy(&speaker_ssrc, &buffer[8], sizeof(uint32_t)); + speaker_ssrc = ntohl(speaker_ssrc); + vp.vr->user_id = ssrc_map[speaker_ssrc]; + } + + /* Get the sequence number of the voice UDP packet */ + std::memcpy(&vp.seq, &buffer[2], sizeof(rtp_seq_t)); + vp.seq = ntohs(vp.seq); + + /* Get the timestamp of the voice UDP packet */ + std::memcpy(&vp.timestamp, &buffer[4], sizeof(rtp_timestamp_t)); + vp.timestamp = ntohl(vp.timestamp); + + vp.vr->audio_data.assign(buffer, buffer + packet_size); + + { + std::lock_guard lk(voice_courier_shared_state.mtx); + auto& [range, payload_queue, pending_decoder_ctls, decoder] = voice_courier_shared_state.parked_voice_payloads[vp.vr->user_id]; + + if (!decoder) { + /* + * Most likely this is the first time we encounter this speaker. + * Do some initialization for not only the decoder but also the range. + */ + range.min_seq = vp.seq; + range.min_timestamp = vp.timestamp; + + int opus_error = 0; + decoder.reset(opus_decoder_create(opus_sample_rate_hz, opus_channel_count, &opus_error), + &opus_decoder_destroy); + if (opus_error) { + /** + * NOTE: The -10 here makes the opus_error match up with values of exception_error_code, + * which would otherwise conflict as every C library loves to use values from -1 downwards. + */ + throw dpp::voice_exception((exception_error_code)(opus_error - 10), "discord_voice_client::discord_voice_client; opus_decoder_create() failed"); + } + } + + if (vp.seq < range.min_seq && vp.timestamp < range.min_timestamp) { + /* This packet arrived too late. We can only discard it. */ + return; + } + range.max_seq = vp.seq; + range.max_timestamp = vp.timestamp; + payload_queue.push(std::move(vp)); + } + + voice_courier_shared_state.signal_iteration.notify_one(); + + if (!voice_courier.joinable()) { + /* Courier thread is not running, start it */ + voice_courier = std::thread(&voice_courier_loop, + std::ref(*this), + std::ref(voice_courier_shared_state)); + } +} + +} diff --git a/src/dpp/voice/enabled/read_write.cpp b/src/dpp/voice/enabled/read_write.cpp new file mode 100644 index 0000000000..52a09d5a39 --- /dev/null +++ b/src/dpp/voice/enabled/read_write.cpp @@ -0,0 +1,74 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#include +#include +#include +#include "../../dave/encryptor.h" +#include "enabled.h" + +namespace dpp { + +dpp::socket discord_voice_client::want_write() { + std::lock_guard lock(this->stream_mutex); + if (!this->sent_stop_frames && !outbuf.empty()) { + return fd; + } + return INVALID_SOCKET; + +} + +dpp::socket discord_voice_client::want_read() { + return fd; +} + + +void discord_voice_client::send(const char* packet, size_t len, uint64_t duration, bool send_now) { + if (!send_now) [[likely]] { + voice_out_packet frame; + frame.packet.assign(packet, packet + len); + frame.duration = duration; + + std::lock_guard lock(this->stream_mutex); + outbuf.emplace_back(frame); + } else [[unlikely]] { + this->udp_send(packet, len); + } +} + +int discord_voice_client::udp_send(const char* data, size_t length) { + return static_cast(sendto( + this->fd, + data, + static_cast(length), + 0, + destination.get_socket_address(), + destination.size() + )); +} + +int discord_voice_client::udp_recv(char* data, size_t max_length) +{ + return static_cast(recv(this->fd, data, static_cast(max_length), 0)); +} + +} diff --git a/src/dpp/voice/enabled/thread.cpp b/src/dpp/voice/enabled/thread.cpp new file mode 100644 index 0000000000..0082eb0a72 --- /dev/null +++ b/src/dpp/voice/enabled/thread.cpp @@ -0,0 +1,87 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#include +#include +#include +#include +#include "../../dave/encryptor.h" +#include "enabled.h" + +namespace dpp { + +void discord_voice_client::thread_run() +{ + utility::set_thread_name(std::string("vc/") + std::to_string(server_id)); + + size_t times_looped = 0; + time_t last_loop_time = time(nullptr); + + do { + bool error = false; + ssl_client::read_loop(); + ssl_client::close(); + + time_t current_time = time(nullptr); + /* Here, we check if it's been longer than 3 seconds since the previous loop, + * this gives us time to see if it's an actual disconnect, or an error. + * This will prevent us from looping too much, meaning error codes do not cause an infinite loop. + */ + if (current_time - last_loop_time >= 3) + times_looped = 0; + + /* This does mean we'll always have times_looped at a minimum of 1, this is intended. */ + times_looped++; + /* If we've looped 5 or more times, abort the loop. */ + if (times_looped >= 5) { + log(dpp::ll_warning, "Reached max loops whilst attempting to read from the websocket. Aborting websocket."); + break; + } + + last_loop_time = current_time; + + if (!terminating) { + log(dpp::ll_debug, "Attempting to reconnect the websocket..."); + do { + try { + ssl_client::connect(); + websocket_client::connect(); + } + catch (const std::exception &e) { + log(dpp::ll_error, std::string("Error establishing voice websocket connection, retry in 5 seconds: ") + e.what()); + ssl_client::close(); + std::this_thread::sleep_for(std::chrono::seconds(5)); + error = true; + } + } while (error && !terminating); + } + } while(!terminating); +} + +void discord_voice_client::run() +{ + this->runner = new std::thread(&discord_voice_client::thread_run, this); + this->thread_id = runner->native_handle(); +} + + +} diff --git a/src/dpp/voice/enabled/voice_payload.cpp b/src/dpp/voice/enabled/voice_payload.cpp new file mode 100644 index 0000000000..afc871a3d7 --- /dev/null +++ b/src/dpp/voice/enabled/voice_payload.cpp @@ -0,0 +1,69 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#include +#include +#include +#include + +namespace dpp { + +bool discord_voice_client::voice_payload::operator<(const voice_payload& other) const { + if (timestamp != other.timestamp) { + return timestamp > other.timestamp; + } + + constexpr rtp_seq_t wrap_around_test_boundary = 5000; + if ((seq < wrap_around_test_boundary && other.seq >= wrap_around_test_boundary) + || (seq >= wrap_around_test_boundary && other.seq < wrap_around_test_boundary)) { + /* Match the cases where exactly one of the sequence numbers "may have" + * wrapped around. + * + * Examples: + * 1. this->seq = 65530, other.seq = 10 // Did wrap around + * 2. this->seq = 5002, other.seq = 4990 // Not wrapped around + * + * Add 5000 to both sequence numbers to force wrap around so they can be + * compared. This should be fine to do to case 2 as well, as long as the + * addend (5000) is not too large to cause one of them to wrap around. + * + * In practice, we should be unlikely to hit the case where + * + * this->seq = 65530, other.seq = 5001 + * + * because we shouldn't receive more than 5000 payloads in one batch, unless + * the voice courier thread is super slow. Also remember that the timestamp + * is compared first, and payloads this far apart shouldn't have the same + * timestamp. + */ + + /* Casts here ensure the sum wraps around and not implicitly converted to + * wider types. + */ + return static_cast(seq + wrap_around_test_boundary) + > static_cast(other.seq + wrap_around_test_boundary); + } else { + return seq > other.seq; + } +} + +} diff --git a/src/dpp/voice/enabled/write_ready.cpp b/src/dpp/voice/enabled/write_ready.cpp new file mode 100644 index 0000000000..8287dea2a3 --- /dev/null +++ b/src/dpp/voice/enabled/write_ready.cpp @@ -0,0 +1,122 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#include +#include +#include + +#include "../../dave/encryptor.h" + +#include "enabled.h" + +namespace dpp { + +void discord_voice_client::write_ready() { + uint64_t duration = 0; + bool track_marker_found = false; + uint64_t bufsize = 0; + send_audio_type_t type = satype_recorded_audio; + { + std::lock_guard lock(this->stream_mutex); + if (this->paused) { + if (!this->sent_stop_frames) { + this->send_stop_frames(true); + this->sent_stop_frames = true; + } + + /* Fallthrough if paused */ + } else if (!outbuf.empty()) { + type = send_audio_type; + if (outbuf[0].packet.size() == sizeof(uint16_t) && (*(reinterpret_cast(outbuf[0].packet.data()))) == AUDIO_TRACK_MARKER) { + outbuf.erase(outbuf.begin()); + track_marker_found = true; + if (tracks > 0) { + tracks--; + } + } + if (!outbuf.empty()) { + if (this->udp_send(outbuf[0].packet.data(), outbuf[0].packet.length()) == (int)outbuf[0].packet.length()) { + duration = outbuf[0].duration * timescale; + bufsize = outbuf[0].packet.length(); + outbuf.erase(outbuf.begin()); + } + } + } + } + if (duration) { + if (type == satype_recorded_audio) { + std::chrono::nanoseconds latency = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - last_timestamp); + std::chrono::nanoseconds sleep_time = std::chrono::nanoseconds(duration) - latency; + if (sleep_time.count() > 0) { + std::this_thread::sleep_for(sleep_time); + } + } + else if (type == satype_overlap_audio) { + std::chrono::nanoseconds latency = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - last_timestamp); + std::chrono::nanoseconds sleep_time = std::chrono::nanoseconds(duration) + last_sleep_remainder - latency; + std::chrono::nanoseconds sleep_increment = (std::chrono::nanoseconds(duration) - latency) / AUDIO_OVERLAP_SLEEP_SAMPLES; + if (sleep_time.count() > 0) { + uint16_t samples_count = 0; + std::chrono::nanoseconds overshoot_accumulator{}; + + do { + std::chrono::high_resolution_clock::time_point start_sleep = std::chrono::high_resolution_clock::now(); + std::this_thread::sleep_for(sleep_increment); + std::chrono::high_resolution_clock::time_point end_sleep = std::chrono::high_resolution_clock::now(); + + samples_count++; + overshoot_accumulator += std::chrono::duration_cast(end_sleep - start_sleep) - sleep_increment; + sleep_time -= std::chrono::duration_cast(end_sleep - start_sleep); + } while (std::chrono::nanoseconds(overshoot_accumulator.count() / samples_count) + sleep_increment < sleep_time); + last_sleep_remainder = sleep_time; + } else { + last_sleep_remainder = std::chrono::nanoseconds(0); + } + } + + last_timestamp = std::chrono::high_resolution_clock::now(); + if (!creator->on_voice_buffer_send.empty()) { + voice_buffer_send_t snd(nullptr, ""); + snd.buffer_size = bufsize; + snd.packets_left = outbuf.size(); + snd.voice_client = this; + creator->on_voice_buffer_send.call(snd); + } + } + if (track_marker_found) { + if (!creator->on_voice_track_marker.empty()) { + voice_track_marker_t vtm(nullptr, ""); + vtm.voice_client = this; + { + std::lock_guard lock(this->stream_mutex); + if (!track_meta.empty()) { + vtm.track_meta = track_meta[0]; + track_meta.erase(track_meta.begin()); + } + } + creator->on_voice_track_marker.call(vtm); + } + } +} + + +} diff --git a/src/dpp/voice/enabled/xchacha20.cpp b/src/dpp/voice/enabled/xchacha20.cpp new file mode 100644 index 0000000000..cff2cc1eb9 --- /dev/null +++ b/src/dpp/voice/enabled/xchacha20.cpp @@ -0,0 +1,234 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contains macros from BoringSSL, Copyright (c) 2014, Google Inc. + * Adapted from the public domain, estream code by D. Bernstein. + * + ************************************************************************************/ + +#include +#include +#include +#include +#include "enabled.h" + +/** + * @brief ChaCha static constant + * For some reason this is the ASCII string 'expand 32-byte k'. + * It is specified by the standard, and cannot be changed. + */ +constexpr uint8_t CHACHA20_CONSTANT_SEED[16] = {'e', 'x', 'p', 'a', 'n', 'd', ' ', '3', '2', '-', 'b', 'y', 't', 'e', ' ', 'k' }; + +/** + * XChaCha20-Poly1305 key size + */ +constexpr size_t KEY_SIZE = 32; + +/** + * @brief ChaCha20-Poly1305 nonce size + */ +constexpr size_t CHACHA_NONCE_SIZE = 12; + +/** + * @brief Rotates the bits of a 32-bit unsigned integer to the left by a specified number of positions. + * @note From Google's BoringSSL, but made constexpr + * + * @param value value to shift + * @param shift number of bits to shift left by + * @return shifted value + */ +constexpr uint32_t rotl_u32(uint32_t value, int shift) { + return (value << shift) | (value >> ((-shift) & 31)); +} + +/** + * @brief Updates a, b, c, and d with a ChaCha20 quarter round. + * @note This is based on black box voodoo from BoringSSL. + * + * @param x an array forming part of an XChaCha20 nonce + * @param a First word of the quarter-round. + * @param b Second word of the quarter-round. + * @param c Third word of the quarter-round. + * @param d Fourth word of the quarter-round. + */ +constexpr void quarter_round(uint32_t* x, int a, int b, int c, int d) { + x[a] += x[b]; + x[d] = rotl_u32(x[d] ^ x[a], 16); + x[c] += x[d]; + x[b] = rotl_u32(x[b] ^ x[c], 12); + x[a] += x[b]; + x[d] = rotl_u32(x[d] ^ x[a], 8); + x[c] += x[d]; + x[b] = rotl_u32(x[b] ^ x[c], 7); +} + +/** + * @brief key derivation function that takes a 256-bit key and a 128-bit nonce, producing a 256-bit subkey. + * + * This subkey is then used in the XChaCha20 algorithm to extend the nonce size to 192 bits. + * @note This based on the function from BoringSSL. Do not mess with it. + * @param out output 32 byte subkey + * @param key input 32 byte key + * @param nonce input 16 byte nonce + */ +void hchacha20(unsigned char out[KEY_SIZE], const unsigned char key[KEY_SIZE], const unsigned char nonce[16]) { + uint32_t x[16]; + std::memcpy(x, CHACHA20_CONSTANT_SEED, sizeof(CHACHA20_CONSTANT_SEED)); + std::memcpy(&x[4], key, KEY_SIZE); + std::memcpy(&x[CHACHA_NONCE_SIZE], nonce, 16); + for (size_t i = 0; i < 20; i += 2) { + quarter_round(x, 0, 4, 8, 12); + quarter_round(x, 1, 5, 9, 13); + quarter_round(x, 2, 6, 10, 14); + quarter_round(x, 3, 7, 11, 15); + quarter_round(x, 0, 5, 10, 15); + quarter_round(x, 1, 6, 11, 12); + quarter_round(x, 2, 7, 8, 13); + quarter_round(x, 3, 4, 9, 14); + } + std::memcpy(out, &x[0], sizeof(uint32_t) * 4); + std::memcpy(&out[16], &x[CHACHA_NONCE_SIZE], sizeof(uint32_t) * 4); +} + +int ssl_crypto_aead_xchacha20poly1305_ietf_encrypt(unsigned char *c, unsigned long long *clen, const unsigned char *m, unsigned long long mlen, const unsigned char *ad, unsigned long long adlen, const unsigned char *nsec, const unsigned char *npub, const unsigned char *k) { + unsigned char sub_key[KEY_SIZE]; + /* Regular ChaCha20-Poly1305 uses 12-byte nonce */ + unsigned char chacha_nonce[CHACHA_NONCE_SIZE] = {0}; + EVP_CIPHER_CTX *ctx = nullptr; + int len = 0; + int ciphertext_len = 0; + + try { + /* Derive the sub-key using HChaCha20 */ + hchacha20(sub_key, k, npub); + + /* Use the last 8 bytes of the 24-byte XChaCha20 nonce */ + std::memcpy(chacha_nonce + 4, npub + ssl_crypto_aead_xchacha20poly1305_IETF_ABYTES, 8); + + /* Initialize encryption context with ChaCha20-Poly1305 */ + ctx = EVP_CIPHER_CTX_new(); + if ((ctx == nullptr) || (EVP_EncryptInit_ex(ctx, EVP_chacha20_poly1305(), nullptr, nullptr, nullptr) == 0)) { + throw dpp::encryption_exception("Error initializing encryption context"); + } + + /* Set key and nonce */ + if (EVP_EncryptInit_ex(ctx, nullptr, nullptr, sub_key, chacha_nonce) == 0) { + throw dpp::encryption_exception("Error setting key and nonce"); + } + + /* Set additional authenticated data (AAD) */ + if (EVP_EncryptUpdate(ctx, nullptr, &len, ad, static_cast(adlen)) == 0) { + throw dpp::encryption_exception("Error setting additional authenticated data"); + } + + /* Encrypt the plaintext */ + if (EVP_EncryptUpdate(ctx, c, &len, m, static_cast(mlen)) == 0) { + throw dpp::encryption_exception("Error during encryption"); + } + ciphertext_len = len; + + if (EVP_EncryptFinal_ex(ctx, c + len, &len) == 0) { + throw dpp::encryption_exception("Error finalizing encryption"); + } + ciphertext_len += len; + + /* Get the authentication tag */ + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, ssl_crypto_aead_xchacha20poly1305_IETF_ABYTES, c + ciphertext_len) == 0) { + throw dpp::encryption_exception("Error getting authentication tag"); + } + + /* Total ciphertext length (ciphertext + tag) */ + if (clen != nullptr) { + *clen = ciphertext_len + ssl_crypto_aead_xchacha20poly1305_IETF_ABYTES; + } + + } catch (const dpp::encryption_exception& e) { + EVP_CIPHER_CTX_free(ctx); + return -1; + } + + EVP_CIPHER_CTX_free(ctx); + return 0; +} + +int ssl_crypto_aead_xchacha20poly1305_ietf_decrypt(unsigned char *m, unsigned long long *mlen, [[maybe_unused]] unsigned char *nsec, const unsigned char *c, unsigned long long clen, const unsigned char *ad, unsigned long long adlen, const unsigned char *npub, const unsigned char *k) { + unsigned char sub_key[KEY_SIZE]; + /* Regular ChaCha20-Poly1305 uses 12-byte nonce */ + unsigned char chacha_nonce[CHACHA_NONCE_SIZE] = {0}; + EVP_CIPHER_CTX *ctx = nullptr; + int len = 0; + int plaintext_len = 0; + + if (clen < ssl_crypto_aead_xchacha20poly1305_IETF_ABYTES) { + /* Ciphertext length must include at least the tag (16 bytes) */ + return -1; + } + + try { + /* Derive the sub-key using HChaCha20 */ + hchacha20(sub_key, k, npub); + + /* Use the last 8 bytes of the 24-byte XChaCha20 nonce */ + std::memcpy(chacha_nonce + 4, npub + ssl_crypto_aead_xchacha20poly1305_IETF_ABYTES, 8); + + /* Initialize decryption context with ChaCha20-Poly1305 */ + ctx = EVP_CIPHER_CTX_new(); + if (!ctx || (EVP_DecryptInit_ex(ctx, EVP_chacha20_poly1305(), nullptr, nullptr, nullptr) == 0)) { + throw dpp::decryption_exception("Error initializing decryption context"); + } + + /* Set key and nonce */ + if (EVP_DecryptInit_ex(ctx, nullptr, nullptr, sub_key, chacha_nonce) == 0) { + throw dpp::decryption_exception("Error setting key and nonce"); + } + + /* Set additional authenticated data (AAD) */ + if (EVP_DecryptUpdate(ctx, nullptr, &len, ad, static_cast(adlen)) == 0) { + throw dpp::decryption_exception("Error setting additional authenticated data"); + } + + /* Decrypt the ciphertext (excluding the tag) */ + if (EVP_DecryptUpdate(ctx, m, &len, c, static_cast(clen - ssl_crypto_aead_xchacha20poly1305_IETF_ABYTES)) == 0) { + throw dpp::decryption_exception("Error during decryption"); + } + plaintext_len = len; + + /* Set the expected tag */ + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, ssl_crypto_aead_xchacha20poly1305_IETF_ABYTES, const_cast(c + clen - ssl_crypto_aead_xchacha20poly1305_IETF_ABYTES)) == 0) { + throw dpp::decryption_exception("Error setting authentication tag"); + } + + /* Check tag */ + int ret = EVP_DecryptFinal_ex(ctx, m + len, &len); + if (ret > 0) { + /* Tag is valid, finalize plaintext length */ + *mlen = plaintext_len + len; + } else { + throw dpp::decryption_exception("Authentication failed"); + } + + } catch (const dpp::decryption_exception& e) { + EVP_CIPHER_CTX_free(ctx); + return -1; + } + + EVP_CIPHER_CTX_free(ctx); + return 0; +} diff --git a/src/dpp/voice/stub/stub.h b/src/dpp/voice/stub/stub.h new file mode 100644 index 0000000000..92b95f4e4c --- /dev/null +++ b/src/dpp/voice/stub/stub.h @@ -0,0 +1,65 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct OpusDecoder {}; +struct OpusEncoder {}; +struct OpusRepacketizer {}; +namespace dpp::dave { + struct Session {}; + struct encryptor {}; + struct decryptor {}; +}; +namespace dpp { + struct dave_state {}; + struct audio_mixer {}; +} diff --git a/src/dpp/voice/stub/stubs.cpp b/src/dpp/voice/stub/stubs.cpp new file mode 100644 index 0000000000..c027aa8b43 --- /dev/null +++ b/src/dpp/voice/stub/stubs.cpp @@ -0,0 +1,102 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#include +#include + +#include "stub.h" + +namespace dpp { + + discord_voice_client::discord_voice_client(dpp::cluster* _cluster, snowflake _channel_id, snowflake _server_id, const std::string &_token, const std::string &_session_id, const std::string &_host, bool enable_dave) + : websocket_client(_host.substr(0, _host.find(':')), _host.substr(_host.find(':') + 1, _host.length()), "/?v=" + std::to_string(voice_protocol_version), OP_TEXT) + { + throw dpp::voice_exception(err_no_voice_support, "Voice support not enabled in this build of D++"); + } + + void discord_voice_client::voice_courier_loop(discord_voice_client& client, courier_shared_state_t& shared_state) { + } + + void discord_voice_client::cleanup(){ + } + + void discord_voice_client::run() { + } + + void discord_voice_client::thread_run() { + } + + bool discord_voice_client::voice_payload::operator<(const voice_payload& other) const { + return false; + } + + bool discord_voice_client::handle_frame(const std::string &data, ws_opcode opcode) { + return false; + } + + void discord_voice_client::read_ready() { + } + + void discord_voice_client::write_ready() { + } + + discord_voice_client& discord_voice_client::send_audio_raw(uint16_t* audio_data, const size_t length) { + return *this; + } + + discord_voice_client& discord_voice_client::send_audio_opus(const uint8_t* opus_packet, const size_t length, uint64_t duration, bool send_now) { + return *this; + } + + discord_voice_client& discord_voice_client::send_audio_opus(const uint8_t* opus_packet, const size_t length) { + return *this; + } + + dpp::socket discord_voice_client::want_write() { + return INVALID_SOCKET; + } + + dpp::socket discord_voice_client::want_read() { + return INVALID_SOCKET; + } + + + void discord_voice_client::send(const char* packet, size_t len, uint64_t duration, bool send_now) { + } + + int discord_voice_client::udp_send(const char* data, size_t length) { + return -1; + } + + int discord_voice_client::udp_recv(char* data, size_t max_length) { + return -1; + } + + size_t discord_voice_client::encode(uint8_t *input, size_t inDataSize, uint8_t *output, size_t &outDataSize) { + return 0; + } + + std::string discord_voice_client::discover_ip() { + return ""; + } + +} diff --git a/src/dpp/voiceregion.cpp b/src/dpp/voiceregion.cpp index e3c9fc6349..22410228d7 100644 --- a/src/dpp/voiceregion.cpp +++ b/src/dpp/voiceregion.cpp @@ -72,5 +72,4 @@ bool voiceregion::is_custom() const { return flags & v_custom; } -} // namespace dpp - +} diff --git a/src/dpp/voicestate.cpp b/src/dpp/voicestate.cpp index e421cfbd1f..9968890200 100644 --- a/src/dpp/voicestate.cpp +++ b/src/dpp/voicestate.cpp @@ -90,4 +90,4 @@ bool voicestate::is_suppressed() const { return flags & vs_suppress; } -} // namespace dpp +} diff --git a/src/dpp/webhook.cpp b/src/dpp/webhook.cpp index 4c43c4df2c..cc5622fe06 100644 --- a/src/dpp/webhook.cpp +++ b/src/dpp/webhook.cpp @@ -22,7 +22,6 @@ #include #include #include -#include namespace dpp { @@ -101,5 +100,4 @@ webhook& webhook::load_image(const std::string &image_blob, const image_type typ return *this; } -} // namespace dpp - +} diff --git a/src/dpp/wsclient.cpp b/src/dpp/wsclient.cpp index 295bbf9e35..618c7e7282 100644 --- a/src/dpp/wsclient.cpp +++ b/src/dpp/wsclient.cpp @@ -61,7 +61,7 @@ void websocket_client::connect() { state = HTTP_HEADERS; /* Send headers synchronously */ - this->write( + this->socket_write( "GET " + this->path + " HTTP/1.1\r\n" "Host: " + this->hostname + "\r\n" "pragma: no-cache\r\n" @@ -73,7 +73,7 @@ void websocket_client::connect() ); } -bool websocket_client::handle_frame(const std::string& buffer) +bool websocket_client::handle_frame(const std::string& buffer, ws_opcode opcode) { /* This is a stub for classes that derive the websocket client */ return true; @@ -111,17 +111,22 @@ size_t websocket_client::fill_header(unsigned char* outbuf, size_t sendlength, w } -void websocket_client::write(const std::string_view data) +void websocket_client::write(const std::string_view data, ws_opcode _opcode) { + if ((_opcode == OP_AUTO ? this->data_opcode : _opcode) == OP_TEXT) { + log(dpp::ll_trace, std::string("W: ") + data.data()); + } else { + log(dpp::ll_trace, "W: size=" + std::to_string(data.length())); + } if (state == HTTP_HEADERS) { /* Simple write */ - ssl_client::write(data); + ssl_client::socket_write(data); } else { unsigned char out[MAXHEADERSIZE]; - size_t s = this->fill_header(out, data.length(), this->data_opcode); + size_t s = this->fill_header(out, data.length(), _opcode == OP_AUTO ? this->data_opcode : _opcode); std::string header((const char*)out, s); - ssl_client::write(header); - ssl_client::write(data); + ssl_client::socket_write(header); + ssl_client::socket_write(data); } } @@ -175,7 +180,7 @@ bool websocket_client::handle_buffer(std::string& buffer) } } else if (state == CONNECTED) { /* Process packets until we can't (buffer will erase data until parseheader returns false) */ - while (this->parseheader(buffer)){} + while (this->parseheader(buffer)) { } } return true; @@ -249,7 +254,7 @@ bool websocket_client::parseheader(std::string& data) handle_ping(data.substr(payloadstartoffset, len)); } else if ((opcode & ~WS_FINBIT) != OP_PONG) { /* Otherwise, handle everything else apart from a PONG. */ /* Pass this frame to the deriving class */ - this->handle_frame(data.substr(payloadstartoffset, len)); + this->handle_frame(data.substr(payloadstartoffset, len), static_cast(opcode & ~WS_FINBIT)); } /* Remove this frame from the input buffer */ @@ -286,8 +291,8 @@ void websocket_client::one_second_timer() std::string payload = "keepalive"; size_t s = this->fill_header(out, payload.length(), OP_PING); std::string header((const char*)out, s); - ssl_client::write(header); - ssl_client::write(payload); + ssl_client::socket_write(header); + ssl_client::socket_write(payload); } } @@ -297,8 +302,8 @@ void websocket_client::handle_ping(const std::string &payload) unsigned char out[MAXHEADERSIZE]; size_t s = this->fill_header(out, payload.length(), OP_PONG); std::string header((const char*)out, s); - ssl_client::write(header); - ssl_client::write(payload); + ssl_client::socket_write(header); + ssl_client::socket_write(payload); } void websocket_client::send_close_packet() @@ -312,8 +317,8 @@ void websocket_client::send_close_packet() size_t s = this->fill_header(out, payload.length(), OP_CLOSE); std::string header((const char*)out, s); - ssl_client::write(header); - ssl_client::write(payload); + ssl_client::socket_write(header); + ssl_client::socket_write(payload); } void websocket_client::error(uint32_t errorcode) @@ -326,4 +331,4 @@ void websocket_client::close() ssl_client::close(); } -} // namespace dpp +} diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 1af1301003..da96a28096 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -641,7 +641,6 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::thread_member_update_t, success); DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::thread_members_update_t, success); DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::voice_buffer_send_t, success); - DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::voice_user_talking_t, success); DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::voice_ready_t, success); DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::voice_receive_t, success); DPP_CHECK_CONSTRUCT_ASSIGN(EVENT_CLASS, dpp::voice_client_speaking_t, success); @@ -2052,7 +2051,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b .set_user_limit(99); dpp::channel createdChannel; try { - createdChannel = bot.channel_create_sync(channel1); + createdChannel = dpp::sync(&bot, &dpp::cluster::channel_create, channel1); } catch (dpp::rest_exception &exception) { set_test(VOICE_CHANNEL_CREATE, false); } @@ -2075,7 +2074,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } } try { - dpp::channel edited = bot.channel_edit_sync(createdChannel); + dpp::channel edited = dpp::sync(&bot, &dpp::cluster::channel_edit, createdChannel); if (edited.name == "foobar2" && edited.user_limit == 2) { set_test(VOICE_CHANNEL_EDIT, true); } @@ -2085,9 +2084,10 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b // delete the voice channel try { - bot.channel_delete_sync(createdChannel.id); + dpp::sync(&bot, &dpp::cluster::channel_delete, createdChannel.id); set_test(VOICE_CHANNEL_DELETE, true); } catch (dpp::rest_exception &exception) { + bot.log(dpp::ll_warning, "Exception: " + std::string(exception.what())); set_test(VOICE_CHANNEL_DELETE, false); } } @@ -2280,7 +2280,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b r.colour = dpp::colors::moon_yellow; dpp::role createdRole; try { - createdRole = bot.role_create_sync(r); + createdRole = dpp::sync(&bot, &dpp::cluster::role_create, r); if (createdRole.name == r.name && createdRole.has_move_members() && createdRole.flags & dpp::r_mentionable && @@ -2294,7 +2294,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b createdRole.name = "Test-Role-Edited"; createdRole.colour = dpp::colors::light_sea_green; try { - dpp::role edited = bot.role_edit_sync(createdRole); + dpp::role edited = dpp::sync(&bot, &dpp::cluster::role_edit, createdRole); if (createdRole.id == edited.id && edited.name == "Test-Role-Edited") { set_test(ROLE_EDIT, true); } @@ -2302,9 +2302,10 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_test(ROLE_EDIT, false); } try { - bot.role_delete_sync(TEST_GUILD_ID, createdRole.id); + dpp::sync(&bot, &dpp::cluster::role_delete, TEST_GUILD_ID, createdRole.id); set_test(ROLE_DELETE, true); } catch (dpp::rest_exception &exception) { + bot.log(dpp::ll_warning, "Exception: " + std::string(exception.what())); set_test(ROLE_DELETE, false); } } @@ -2335,7 +2336,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_test(USER_GET_CACHED_PRESENT, false); try { - dpp::user_identified u = bot.user_get_cached_sync(TEST_USER_ID); + dpp::user_identified u = dpp::sync(&bot, &dpp::cluster::user_get_cached, TEST_USER_ID); set_test(USER_GET_CACHED_PRESENT, (u.id == TEST_USER_ID)); } catch (const std::exception&) { @@ -2350,7 +2351,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b * If this becomes not true any more, we'll pick another well known * user ID. */ - dpp::user_identified u = bot.user_get_cached_sync(90339695967350784); + dpp::user_identified u = dpp::sync(&bot, &dpp::cluster::user_get_cached, 90339695967350784); set_test(USER_GET_CACHED_ABSENT, (u.id == dpp::snowflake(90339695967350784))); } catch (const std::exception&) { diff --git a/testdata/Robot.pcm b/testdata/Robot.pcm index f9a7525d46..e7c167a754 100755 Binary files a/testdata/Robot.pcm and b/testdata/Robot.pcm differ diff --git a/vcpkg/ports/dpp/portfile.cmake b/vcpkg/ports/dpp/portfile.cmake index af8d57e936..bc77cbaef3 100644 --- a/vcpkg/ports/dpp/portfile.cmake +++ b/vcpkg/ports/dpp/portfile.cmake @@ -2,7 +2,7 @@ vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO brainboxdotcc/DPP REF "v${VERSION}" - SHA512 4842e5e688893275e524f998bdcac1d308165a02c280f69eaa898aa8f9986a97fe687e20e3428f30777c49f1feb47905da462bbdba8c9a5ea00067e560208f91 + SHA512 69fe3323aceb4ad52ec1fcfb38a8770e88c03ae0b6cb49768441b603e13659625720d984b992311fcca8ef863d40b8b7fb082996fae2d396e785b637b673a328 ) vcpkg_cmake_configure( diff --git a/vcpkg/ports/dpp/vcpkg.json b/vcpkg/ports/dpp/vcpkg.json index 5d21e83d5f..eb86f373fe 100644 --- a/vcpkg/ports/dpp/vcpkg.json +++ b/vcpkg/ports/dpp/vcpkg.json @@ -1,12 +1,11 @@ { "name": "dpp", - "version": "10.0.30", + "version": "10.0.33", "description": "D++ Extremely Lightweight C++ Discord Library.", "homepage": "https://dpp.dev/", "license": "Apache-2.0", "supports": "(windows & !static & !uwp) | linux | osx", "dependencies": [ - "libsodium", "nlohmann-json", "openssl", "opus", diff --git a/vcpkg/versions/baseline.json b/vcpkg/versions/baseline.json index 7e91f07b2a..2f5f361937 100644 --- a/vcpkg/versions/baseline.json +++ b/vcpkg/versions/baseline.json @@ -5,19 +5,19 @@ "port-version": 4 }, "7zip": { - "baseline": "23.01", - "port-version": 2 + "baseline": "24.08", + "port-version": 0 }, "ableton": { "baseline": "3.0.6", "port-version": 2 }, "ableton-link": { - "baseline": "3.1.1", + "baseline": "3.1.2", "port-version": 0 }, "abseil": { - "baseline": "20240116.2", + "baseline": "20240722.0", "port-version": 0 }, "absent": { @@ -25,19 +25,19 @@ "port-version": 3 }, "ace": { - "baseline": "7.1.4", + "baseline": "8.0.1", "port-version": 0 }, "acl": { - "baseline": "2.3.1", - "port-version": 2 + "baseline": "2.3.2", + "port-version": 0 }, "activemq-cpp": { "baseline": "3.9.5", - "port-version": 15 + "port-version": 17 }, "ada-url": { - "baseline": "2.7.7", + "baseline": "2.9.2", "port-version": 0 }, "ade": { @@ -61,7 +61,7 @@ "port-version": 1 }, "aklomp-base64": { - "baseline": "0.5.1", + "baseline": "0.5.2", "port-version": 0 }, "alac": { @@ -73,13 +73,17 @@ "port-version": 7 }, "alembic": { - "baseline": "1.8.6", + "baseline": "1.8.7", "port-version": 0 }, "aliyun-oss-c-sdk": { "baseline": "3.10.1", "port-version": 0 }, + "aliyun-oss-cpp-sdk": { + "baseline": "1.10.0", + "port-version": 2 + }, "allegro5": { "baseline": "5.2.9.1", "port-version": 0 @@ -89,48 +93,48 @@ "port-version": 0 }, "alpaka": { - "baseline": "1.1.0", + "baseline": "1.2.0", "port-version": 0 }, "alsa": { "baseline": "1.2.11", - "port-version": 1 + "port-version": 2 }, "amd-adl-sdk": { "baseline": "17.1", "port-version": 0 }, "amd-amf": { - "baseline": "1.4.33", - "port-version": 1 + "baseline": "1.4.35", + "port-version": 0 }, "ampl-asl": { - "baseline": "2020-11-11", - "port-version": 3 + "baseline": "2024-02-01", + "port-version": 0 }, "ampl-mp": { "baseline": "2020-11-11", - "port-version": 4 + "port-version": 5 }, "amqpcpp": { "baseline": "4.3.26", "port-version": 0 }, "anari": { - "baseline": "0.7.0", - "port-version": 1 + "baseline": "0.10.0", + "port-version": 0 }, "anax": { "baseline": "2.1.0", "port-version": 8 }, "angelscript": { - "baseline": "2.36.1", - "port-version": 1 + "baseline": "2.37.0", + "port-version": 0 }, "angle": { "baseline": "chromium_5414", - "port-version": 7 + "port-version": 9 }, "ankurvdev-embedresource": { "baseline": "0.0.11", @@ -141,7 +145,7 @@ "port-version": 0 }, "antlr4": { - "baseline": "4.13.1", + "baseline": "4.13.2", "port-version": 0 }, "any-lite": { @@ -153,8 +157,8 @@ "port-version": 2 }, "aom": { - "baseline": "3.8.1", - "port-version": 1 + "baseline": "3.9.1", + "port-version": 0 }, "apache-datasketches": { "baseline": "5.0.2", @@ -165,15 +169,15 @@ "port-version": 0 }, "apr": { - "baseline": "1.7.4", - "port-version": 0 + "baseline": "1.7.5", + "port-version": 2 }, "apr-util": { "baseline": "1.6.3", "port-version": 0 }, "apriltag": { - "baseline": "3.4.0", + "baseline": "3.4.2", "port-version": 0 }, "apsi": { @@ -186,11 +190,11 @@ }, "arcus": { "baseline": "4.10.0", - "port-version": 2 + "port-version": 3 }, "arg-router": { "baseline": "1.4.0", - "port-version": 0 + "port-version": 1 }, "argagg": { "baseline": "0.4.7", @@ -205,7 +209,7 @@ "port-version": 1 }, "argparse": { - "baseline": "3.0", + "baseline": "3.1", "port-version": 0 }, "args": { @@ -229,7 +233,7 @@ "port-version": 0 }, "armadillo": { - "baseline": "12.6.6", + "baseline": "12.8.4", "port-version": 1 }, "arpack-ng": { @@ -238,14 +242,14 @@ }, "arrayfire": { "baseline": "3.8.0", - "port-version": 5 + "port-version": 7 }, "arrow": { - "baseline": "16.0.0", - "port-version": 1 + "baseline": "17.0.0", + "port-version": 0 }, "arsenalgear": { - "baseline": "2.1.0", + "baseline": "2.1.1", "port-version": 0 }, "ashes": { @@ -253,11 +257,11 @@ "port-version": 0 }, "asio": { - "baseline": "1.30.2", + "baseline": "1.31.0", "port-version": 0 }, "asio-grpc": { - "baseline": "3.1.0", + "baseline": "3.2.0", "port-version": 0 }, "asiochan": { @@ -269,7 +273,7 @@ "port-version": 7 }, "asmjit": { - "baseline": "2023-03-25", + "baseline": "2024-06-28", "port-version": 0 }, "asmtk": { @@ -277,11 +281,15 @@ "port-version": 1 }, "assimp": { - "baseline": "5.4.0", + "baseline": "5.4.3", + "port-version": 0 + }, + "astr": { + "baseline": "0.2.1", "port-version": 0 }, "async-mqtt": { - "baseline": "5.1.1", + "baseline": "9.0.2", "port-version": 0 }, "async-simple": { @@ -325,7 +333,7 @@ "port-version": 3 }, "atomic-queue": { - "baseline": "1.6.3", + "baseline": "1.6.5", "port-version": 0 }, "attr": { @@ -333,27 +341,35 @@ "port-version": 0 }, "aubio": { - "baseline": "2022-01-26", - "port-version": 1 + "baseline": "2024-01-03", + "port-version": 0 }, "audiofile": { "baseline": "1.1.1", "port-version": 0 }, + "audit": { + "baseline": "4.0.2", + "port-version": 0 + }, "aurora": { "baseline": "2017-06-21-c75699d2a8caa726260c29b6d7a0fd35f8f28933", "port-version": 2 }, + "aurora-au": { + "baseline": "0.3.5", + "port-version": 0 + }, "autobahn": { "baseline": "20.8.1", "port-version": 2 }, "autodock-vina": { "baseline": "1.2.5", - "port-version": 2 + "port-version": 3 }, "avcpp": { - "baseline": "2.3.0", + "baseline": "2.4.0", "port-version": 0 }, "avisynthplus": { @@ -365,7 +381,7 @@ "port-version": 0 }, "avro-cpp": { - "baseline": "1.11.3", + "baseline": "1.12.0", "port-version": 0 }, "awlib": { @@ -373,51 +389,51 @@ "port-version": 0 }, "aws-c-auth": { - "baseline": "0.7.16", + "baseline": "0.7.31", "port-version": 0 }, "aws-c-cal": { - "baseline": "0.6.10", + "baseline": "0.7.4", "port-version": 0 }, "aws-c-common": { - "baseline": "0.9.14", + "baseline": "0.9.30", "port-version": 0 }, "aws-c-compression": { - "baseline": "0.2.18", + "baseline": "0.2.19", "port-version": 0 }, "aws-c-event-stream": { - "baseline": "0.4.2", + "baseline": "0.4.3", "port-version": 0 }, "aws-c-http": { - "baseline": "0.8.1", + "baseline": "0.8.10", "port-version": 0 }, "aws-c-io": { - "baseline": "0.14.6", + "baseline": "0.14.18", "port-version": 0 }, "aws-c-mqtt": { - "baseline": "0.10.3", + "baseline": "0.10.7", "port-version": 0 }, "aws-c-s3": { - "baseline": "0.5.4", + "baseline": "0.6.6", "port-version": 0 }, "aws-c-sdkutils": { - "baseline": "0.1.15", + "baseline": "0.1.19", "port-version": 0 }, "aws-checksums": { - "baseline": "0.1.18", + "baseline": "0.1.20", "port-version": 0 }, "aws-crt-cpp": { - "baseline": "0.26.4", + "baseline": "0.28.3", "port-version": 0 }, "aws-lambda-cpp": { @@ -425,44 +441,44 @@ "port-version": 0 }, "aws-sdk-cpp": { - "baseline": "1.11.285", - "port-version": 1 + "baseline": "1.11.428", + "port-version": 0 }, "azmq": { "baseline": "2023-03-23", "port-version": 0 }, "azure-c-shared-utility": { - "baseline": "2024-03-04", + "baseline": "2024-06-24", "port-version": 1 }, "azure-core-amqp-cpp": { - "baseline": "1.0.0-beta.9", - "port-version": 0 + "baseline": "1.0.0-beta.11", + "port-version": 1 }, "azure-core-cpp": { - "baseline": "1.12.0", - "port-version": 0 + "baseline": "1.14.0", + "port-version": 1 }, "azure-core-tracing-opentelemetry-cpp": { "baseline": "1.0.0-beta.4", - "port-version": 3 + "port-version": 5 }, "azure-data-tables-cpp": { - "baseline": "1.0.0-beta.2", - "port-version": 0 + "baseline": "1.0.0-beta.4", + "port-version": 1 }, "azure-identity-cpp": { - "baseline": "1.6.0", - "port-version": 1 + "baseline": "1.10.0", + "port-version": 2 }, "azure-iot-sdk-c": { - "baseline": "2024-03-04", + "baseline": "2024-08-12", "port-version": 0 }, "azure-kinect-sensor-sdk": { "baseline": "1.4.1", - "port-version": 6 + "port-version": 7 }, "azure-macro-utils-c": { "baseline": "2022-01-21", @@ -470,38 +486,38 @@ }, "azure-messaging-eventhubs-checkpointstore-blob-cpp": { "baseline": "1.0.0-beta.1", - "port-version": 2 + "port-version": 4 }, "azure-messaging-eventhubs-cpp": { - "baseline": "1.0.0-beta.8", - "port-version": 0 + "baseline": "1.0.0-beta.9", + "port-version": 2 }, "azure-security-attestation-cpp": { "baseline": "1.1.0", - "port-version": 3 + "port-version": 6 }, "azure-security-keyvault-administration-cpp": { - "baseline": "4.0.0-beta.4", + "baseline": "4.0.0-beta.5", "port-version": 1 }, "azure-security-keyvault-certificates-cpp": { "baseline": "4.2.1", - "port-version": 1 + "port-version": 3 }, "azure-security-keyvault-keys-cpp": { "baseline": "4.4.1", - "port-version": 1 + "port-version": 3 }, "azure-security-keyvault-secrets-cpp": { "baseline": "4.2.1", - "port-version": 1 + "port-version": 3 }, "azure-storage-blobs-cpp": { - "baseline": "12.10.0", + "baseline": "12.13.0", "port-version": 1 }, "azure-storage-common-cpp": { - "baseline": "12.5.0", + "baseline": "12.8.0", "port-version": 1 }, "azure-storage-cpp": { @@ -509,27 +525,27 @@ "port-version": 6 }, "azure-storage-files-datalake-cpp": { - "baseline": "12.9.0", + "baseline": "12.12.0", "port-version": 1 }, "azure-storage-files-shares-cpp": { - "baseline": "12.8.0", + "baseline": "12.11.0", "port-version": 1 }, "azure-storage-queues-cpp": { - "baseline": "12.2.0", + "baseline": "12.4.0", "port-version": 1 }, "azure-uamqp-c": { - "baseline": "2024-03-04", + "baseline": "2024-08-12", "port-version": 0 }, "azure-uhttp-c": { - "baseline": "2024-03-04", + "baseline": "2024-06-24", "port-version": 0 }, "azure-umqtt-c": { - "baseline": "2024-03-04", + "baseline": "2024-06-24", "port-version": 0 }, "b64": { @@ -541,7 +557,7 @@ "port-version": 1 }, "baresip-libre": { - "baseline": "3.11.0", + "baseline": "3.15.0", "port-version": 0 }, "basisu": { @@ -553,15 +569,15 @@ "port-version": 3 }, "bddisasm": { - "baseline": "2.1.0", + "baseline": "2.1.5", "port-version": 0 }, "bde": { - "baseline": "3.124.0.0", + "baseline": "4.14.0.0", "port-version": 0 }, "bdwgc": { - "baseline": "8.2.6", + "baseline": "8.2.8", "port-version": 0 }, "beast": { @@ -573,8 +589,8 @@ "port-version": 0 }, "benchmark": { - "baseline": "1.8.3", - "port-version": 3 + "baseline": "1.9.0", + "port-version": 0 }, "bento4": { "baseline": "1.6.0-641", @@ -597,11 +613,15 @@ "port-version": 0 }, "bext-sml": { - "baseline": "1.1.9", + "baseline": "1.1.11", "port-version": 0 }, "bext-sml2": { - "baseline": "2024-02-02", + "baseline": "2.0.0", + "port-version": 0 + }, + "bext-text": { + "baseline": "2024-01-19", "port-version": 0 }, "bext-ut": { @@ -617,7 +637,7 @@ "port-version": 0 }, "bgfx": { - "baseline": "1.122.8595-458", + "baseline": "1.128.8786-481", "port-version": 0 }, "bigint": { @@ -632,12 +652,16 @@ "baseline": "3.0", "port-version": 3 }, + "bit7z": { + "baseline": "4.0.8", + "port-version": 0 + }, "bitmagic": { "baseline": "7.13.4", "port-version": 0 }, "bitserializer": { - "baseline": "0.65", + "baseline": "0.70", "port-version": 0 }, "bitserializer-cpprestjson": { @@ -657,24 +681,24 @@ "port-version": 3 }, "bitsery": { - "baseline": "5.2.3", + "baseline": "5.2.4", "port-version": 0 }, "blake3": { - "baseline": "1.5.1", + "baseline": "1.5.4", "port-version": 0 }, "blas": { "baseline": "2023-04-14", - "port-version": 0 + "port-version": 1 }, "blaze": { "baseline": "3.8.2", "port-version": 1 }, "blend2d": { - "baseline": "2023-06-16", - "port-version": 1 + "baseline": "2024-07-08", + "port-version": 0 }, "blingfire": { "baseline": "0.1.8.1", @@ -682,638 +706,642 @@ }, "blitz": { "baseline": "2020-03-25", - "port-version": 6 + "port-version": 7 }, "bloomberg-quantum": { "baseline": "2023-02-03", "port-version": 0 }, "blosc": { - "baseline": "1.21.5", + "baseline": "1.21.6", "port-version": 0 }, "blpapi": { - "baseline": "3.20.2", + "baseline": "3.24.6", + "port-version": 0 + }, + "bluescarni-tanuki": { + "baseline": "2024-08-17", "port-version": 0 }, "boinc": { - "baseline": "8.0.0", + "baseline": "8.0.4", "port-version": 0 }, "bond": { - "baseline": "10.0.0", - "port-version": 3 + "baseline": "13.0.1", + "port-version": 0 }, "boolinq": { "baseline": "3.0.4", "port-version": 0 }, "boost": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-accumulators": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-algorithm": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-align": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-any": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-array": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-asio": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-assert": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-assign": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-atomic": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-beast": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-bimap": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-bind": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-build": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-callable-traits": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-charconv": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-chrono": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-circular-buffer": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-cmake": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-cobalt": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-compat": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-compatibility": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-compute": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-concept-check": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-config": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-container": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-container-hash": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-context": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-contract": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-conversion": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-convert": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-core": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-coroutine": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-coroutine2": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-crc": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-date-time": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-describe": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-detail": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-dll": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-dynamic-bitset": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-endian": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-exception": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-fiber": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-filesystem": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-flyweight": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-foreach": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-format": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-function": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-function-types": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-functional": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-fusion": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-geometry": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-gil": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-graph": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-graph-parallel": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-hana": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-headers": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-heap": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-histogram": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-hof": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-icl": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-integer": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-interprocess": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-interval": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-intrusive": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-io": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-iostreams": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-iterator": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-json": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-lambda": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-lambda2": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-leaf": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-lexical-cast": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-local-function": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-locale": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-lockfree": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-log": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-logic": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-math": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-metaparse": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-move": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-mp11": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-mpi": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-mpl": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-msm": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-multi-array": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-multi-index": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-multiprecision": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-mysql": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-nowide": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-numeric-conversion": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-odeint": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-optional": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-outcome": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-parameter": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-parameter-python": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-pfr": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-phoenix": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-poly-collection": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-polygon": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-pool": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-predef": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-preprocessor": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-process": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-program-options": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-property-map": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-property-map-parallel": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-property-tree": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-proto": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-ptr-container": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-python": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-qvm": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-random": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-range": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-ratio": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-rational": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-redis": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-regex": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-safe-numerics": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-scope": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-scope-exit": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-serialization": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-signals2": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-smart-ptr": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-sort": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-spirit": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-stacktrace": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-statechart": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-static-assert": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-static-string": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-stl-interfaces": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-system": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-test": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-thread": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-throw-exception": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-timer": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-tokenizer": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-tti": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-tuple": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-type-erasure": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-type-index": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-type-traits": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-typeof": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-ublas": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-uninstall": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-units": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-unordered": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-url": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-utility": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-uuid": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-variant": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-variant2": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-vcpkg-helpers": { @@ -1321,32 +1349,32 @@ "port-version": 0 }, "boost-vmd": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-wave": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-winapi": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-xpressive": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boost-yap": { - "baseline": "1.85.0", + "baseline": "1.86.0", "port-version": 0 }, "boringssl": { - "baseline": "2023-10-13", + "baseline": "2024-09-13", "port-version": 0 }, "botan": { - "baseline": "3.3.0", - "port-version": 0 + "baseline": "3.5.0", + "port-version": 1 }, "box2d": { "baseline": "2.4.1", @@ -1354,11 +1382,11 @@ }, "braft": { "baseline": "2021-26-04", - "port-version": 4 + "port-version": 5 }, "breakpad": { "baseline": "2023-06-01", - "port-version": 0 + "port-version": 2 }, "brigand": { "baseline": "1.3.0", @@ -1369,8 +1397,8 @@ "port-version": 1 }, "brpc": { - "baseline": "1.6.1", - "port-version": 3 + "baseline": "1.10.0", + "port-version": 0 }, "brunocodutra-metal": { "baseline": "2.1.4", @@ -1389,12 +1417,12 @@ "port-version": 0 }, "buck-yeh-bux": { - "baseline": "1.6.8", - "port-version": 1 + "baseline": "1.8.1", + "port-version": 0 }, "buck-yeh-bux-mariadb-client": { - "baseline": "1.0.3", - "port-version": 1 + "baseline": "1.0.4", + "port-version": 0 }, "buck-yeh-bux-sqlite": { "baseline": "1.0.1", @@ -1402,7 +1430,7 @@ }, "bullet3": { "baseline": "3.25", - "port-version": 1 + "port-version": 2 }, "bustache": { "baseline": "1.1.0", @@ -1413,8 +1441,8 @@ "port-version": 4 }, "bxzstr": { - "baseline": "1.2.0", - "port-version": 1 + "baseline": "1.2.2", + "port-version": 0 }, "byte-lite": { "baseline": "0.3.0", @@ -1422,10 +1450,10 @@ }, "bzip2": { "baseline": "1.0.8", - "port-version": 5 + "port-version": 6 }, "c-ares": { - "baseline": "1.28.1", + "baseline": "1.34.1", "port-version": 0 }, "c-dbg-macro": { @@ -1433,7 +1461,7 @@ "port-version": 0 }, "c4core": { - "baseline": "0.1.11", + "baseline": "0.2.1", "port-version": 0 }, "c89stringutils": { @@ -1445,27 +1473,23 @@ "port-version": 0 }, "cachelib": { - "baseline": "2024.05.06.00", + "baseline": "2024.07.15.00", "port-version": 0 }, "caf": { - "baseline": "0.19.3", + "baseline": "1.0.1", "port-version": 0 }, - "caffe2": { - "baseline": "0.8.1", - "port-version": 8 - }, "cairo": { "baseline": "1.18.0", "port-version": 1 }, "cairomm": { - "baseline": "1.17.1", - "port-version": 1 + "baseline": "1.18.0", + "port-version": 0 }, "calceph": { - "baseline": "4.0.0", + "baseline": "4.0.1", "port-version": 0 }, "camport3": { @@ -1481,20 +1505,20 @@ "port-version": 0 }, "capstone": { - "baseline": "5.0.1", - "port-version": 1 + "baseline": "5.0.3", + "port-version": 0 }, "cargs": { - "baseline": "1.1.0", + "baseline": "1.2.0", "port-version": 0 }, "cartographer": { "baseline": "1.0.0", - "port-version": 5 + "port-version": 6 }, "casclib": { - "baseline": "2021-11-16", - "port-version": 1 + "baseline": "2024-06-05", + "port-version": 0 }, "catch": { "baseline": "alias", @@ -1505,7 +1529,7 @@ "port-version": 2 }, "catch2": { - "baseline": "3.6.0", + "baseline": "3.7.1", "port-version": 0 }, "cblas": { @@ -1522,18 +1546,22 @@ }, "ccfits": { "baseline": "2.5", - "port-version": 11 + "port-version": 13 }, "cctag": { - "baseline": "1.0.2", - "port-version": 6 + "baseline": "1.0.4", + "port-version": 0 }, "cctz": { "baseline": "2.4", "port-version": 0 }, + "cddlib": { + "baseline": "0.94m", + "port-version": 0 + }, "cdt": { - "baseline": "1.4.0", + "baseline": "1.4.1", "port-version": 0 }, "celero": { @@ -1542,7 +1570,7 @@ }, "cello": { "baseline": "2019-07-23", - "port-version": 3 + "port-version": 4 }, "cereal": { "baseline": "1.3.2", @@ -1554,22 +1582,22 @@ }, "cfitsio": { "baseline": "3.49", - "port-version": 5 + "port-version": 6 }, "cgal": { - "baseline": "5.6.1", + "baseline": "6.0", "port-version": 0 }, "cgicc": { "baseline": "3.2.20", - "port-version": 0 + "port-version": 1 }, "cglm": { "baseline": "0.9.4", "port-version": 0 }, "cgltf": { - "baseline": "1.13", + "baseline": "1.14", "port-version": 0 }, "cgns": { @@ -1602,7 +1630,7 @@ }, "chmlib": { "baseline": "0.40", - "port-version": 7 + "port-version": 8 }, "chromaprint": { "baseline": "1.5.1", @@ -1610,7 +1638,7 @@ }, "chromium-base": { "baseline": "86.0.4199.1", - "port-version": 5 + "port-version": 6 }, "chronoengine": { "baseline": "8.0.0", @@ -1621,11 +1649,11 @@ "port-version": 0 }, "cimg": { - "baseline": "3.3.2", + "baseline": "3.4.1", "port-version": 0 }, "cista": { - "baseline": "0.14", + "baseline": "0.15", "port-version": 0 }, "cityhash": { @@ -1637,7 +1665,7 @@ "port-version": 2 }, "cjson": { - "baseline": "1.7.17", + "baseline": "1.7.18", "port-version": 0 }, "clamav": { @@ -1645,7 +1673,7 @@ "port-version": 0 }, "clap-cleveraudio": { - "baseline": "1.2.0", + "baseline": "1.2.2", "port-version": 0 }, "clapack": { @@ -1661,7 +1689,7 @@ "port-version": 7 }, "clblast": { - "baseline": "1.6.1", + "baseline": "1.6.3", "port-version": 0 }, "cld3": { @@ -1670,15 +1698,15 @@ }, "clfft": { "baseline": "2.12.2", - "port-version": 6 + "port-version": 7 }, "cli": { "baseline": "2.1.0", "port-version": 0 }, "cli11": { - "baseline": "2.4.1", - "port-version": 0 + "baseline": "2.4.2", + "port-version": 1 }, "clickhouse-cpp": { "baseline": "2.5.1", @@ -1693,12 +1721,12 @@ "port-version": 2 }, "clipper2": { - "baseline": "1.3.0", - "port-version": 1 + "baseline": "1.4.0", + "port-version": 0 }, "clockutils": { "baseline": "1.1.1", - "port-version": 1 + "port-version": 2 }, "clrng": { "baseline": "2020-12-01", @@ -1713,7 +1741,7 @@ "port-version": 0 }, "cmark": { - "baseline": "0.30.3", + "baseline": "0.31.1", "port-version": 0 }, "cminpack": { @@ -1725,7 +1753,7 @@ "port-version": 3 }, "cnats": { - "baseline": "3.8.0", + "baseline": "3.8.2", "port-version": 0 }, "cnl": { @@ -1741,15 +1769,15 @@ "port-version": 1 }, "coin": { - "baseline": "4.0.2", - "port-version": 2 + "baseline": "4.0.3", + "port-version": 0 }, "coin-or-buildtools": { "baseline": "2023-02-02", "port-version": 0 }, "coin-or-cbc": { - "baseline": "2023-02-01", + "baseline": "2024-06-04", "port-version": 0 }, "coin-or-cgl": { @@ -1765,19 +1793,19 @@ "port-version": 0 }, "coin-or-osi": { - "baseline": "2023-02-01", + "baseline": "2024-04-16", "port-version": 0 }, "coinutils": { - "baseline": "2023-02-02", - "port-version": 1 + "baseline": "2024-04-08", + "port-version": 0 }, "collada-dom": { "baseline": "2.5.0", "port-version": 11 }, "colmap": { - "baseline": "2023-10-01", + "baseline": "3.10", "port-version": 0 }, "color-console": { @@ -1793,7 +1821,7 @@ "port-version": 0 }, "commsdsl": { - "baseline": "4.0.0", + "baseline": "6.3.3", "port-version": 0 }, "compoundfilereader": { @@ -1802,14 +1830,18 @@ }, "concurrencpp": { "baseline": "0.1.7", - "port-version": 0 + "port-version": 1 }, "concurrentqueue": { "baseline": "1.0.4", "port-version": 0 }, "configcat": { - "baseline": "4.0.1", + "baseline": "4.0.3", + "port-version": 0 + }, + "conjure-enum": { + "baseline": "1.0.1", "port-version": 0 }, "console-bridge": { @@ -1861,7 +1893,7 @@ "port-version": 0 }, "cpp-httplib": { - "baseline": "0.15.3", + "baseline": "0.18.0", "port-version": 0 }, "cpp-ipc": { @@ -1877,7 +1909,7 @@ "port-version": 9 }, "cpp-peglib": { - "baseline": "1.8.6", + "baseline": "1.9.0", "port-version": 0 }, "cpp-redis": { @@ -1885,7 +1917,7 @@ "port-version": 5 }, "cpp-sort": { - "baseline": "1.15.0", + "baseline": "1.16.0", "port-version": 0 }, "cpp-taskflow": { @@ -1893,11 +1925,11 @@ "port-version": 2 }, "cpp-timsort": { - "baseline": "2.1.0", + "baseline": "3.0.0", "port-version": 0 }, "cppad": { - "baseline": "20240000.2", + "baseline": "20240000.7", "port-version": 0 }, "cppcms": { @@ -1921,8 +1953,8 @@ "port-version": 3 }, "cppgraphqlgen": { - "baseline": "4.5.5", - "port-version": 0 + "baseline": "4.5.7", + "port-version": 1 }, "cppitertools": { "baseline": "2.1", @@ -1940,10 +1972,14 @@ "baseline": "2.1.0", "port-version": 0 }, - "cpprestsdk": { - "baseline": "2.10.19", + "cpprealm": { + "baseline": "2.2.0", "port-version": 0 }, + "cpprestsdk": { + "baseline": "2.10.19", + "port-version": 2 + }, "cppslippi": { "baseline": "1.4.3.16", "port-version": 0 @@ -1953,7 +1989,7 @@ "port-version": 4 }, "cpptrace": { - "baseline": "0.5.4", + "baseline": "0.7.2", "port-version": 0 }, "cppunit": { @@ -1965,7 +2001,7 @@ "port-version": 3 }, "cppwinrt": { - "baseline": "2.0.240111.5", + "baseline": "2.0.240405.15", "port-version": 0 }, "cppxaml": { @@ -1998,7 +2034,7 @@ }, "crashpad": { "baseline": "2024-04-11", - "port-version": 0 + "port-version": 4 }, "crashrpt": { "baseline": "1.4.3", @@ -2010,7 +2046,7 @@ }, "crfsuite": { "baseline": "2020-08-27", - "port-version": 0 + "port-version": 1 }, "croncpp": { "baseline": "2023-03-30", @@ -2021,28 +2057,28 @@ "port-version": 3 }, "crow": { - "baseline": "1.1.0", + "baseline": "1.2.0", "port-version": 0 }, "cryptopp": { "baseline": "8.9.0", - "port-version": 0 + "port-version": 1 }, "cserialport": { - "baseline": "4.3.0", - "port-version": 1 + "baseline": "4.3.1", + "port-version": 0 }, "cspice": { "baseline": "67", "port-version": 3 }, "ctbench": { - "baseline": "1.3.3", - "port-version": 0 + "baseline": "1.3.4", + "port-version": 1 }, "ctbignum": { "baseline": "2019-08-02", - "port-version": 4 + "port-version": 5 }, "ctemplate": { "baseline": "2020-09-14", @@ -2057,7 +2093,7 @@ "port-version": 2 }, "ctre": { - "baseline": "3.8.1", + "baseline": "3.9.0", "port-version": 0 }, "ctstraffic": { @@ -2077,24 +2113,28 @@ "port-version": 13 }, "cuda-api-wrappers": { - "baseline": "0.6.9", + "baseline": "0.7.1", "port-version": 0 }, "cudnn": { "baseline": "7.6.5", - "port-version": 11 + "port-version": 14 }, "cunit": { "baseline": "2.1.3", "port-version": 8 }, "curl": { - "baseline": "8.7.1", - "port-version": 3 + "baseline": "8.10.1", + "port-version": 0 + }, + "curlcpp": { + "baseline": "3.1", + "port-version": 1 }, "curlpp": { "baseline": "2018-06-15", - "port-version": 9 + "port-version": 10 }, "cute-headers": { "baseline": "2019-09-20", @@ -2113,7 +2153,7 @@ "port-version": 0 }, "cxxgraph": { - "baseline": "2.0.0", + "baseline": "4.1.0", "port-version": 0 }, "cxxopts": { @@ -2121,13 +2161,17 @@ "port-version": 0 }, "cyclonedds": { - "baseline": "0.10.4", + "baseline": "0.10.5", "port-version": 0 }, "cyclonedds-cxx": { - "baseline": "0.10.4", + "baseline": "0.10.5", "port-version": 0 }, + "cyrus-sasl": { + "baseline": "2.1.28", + "port-version": 2 + }, "czmq": { "baseline": "4.2.1", "port-version": 4 @@ -2153,19 +2197,19 @@ "port-version": 3 }, "dataframe": { - "baseline": "3.1.0", + "baseline": "3.3.0", "port-version": 0 }, "date": { - "baseline": "3.0.1", - "port-version": 5 + "baseline": "2024-05-14", + "port-version": 0 }, "datraw": { "baseline": "1.0.9", "port-version": 0 }, "dav1d": { - "baseline": "1.4.0", + "baseline": "1.4.3", "port-version": 0 }, "daw-header-libraries": { @@ -2177,16 +2221,16 @@ "port-version": 0 }, "daw-utf-range": { - "baseline": "2.2.4", + "baseline": "2.2.5", "port-version": 0 }, "daxa": { - "baseline": "2.0.0", - "port-version": 0 + "baseline": "3.0.2", + "port-version": 1 }, "dbg-macro": { "baseline": "0.5.1", - "port-version": 0 + "port-version": 1 }, "dbghelp": { "baseline": "0", @@ -2202,15 +2246,15 @@ }, "dbus": { "baseline": "1.15.8", - "port-version": 4 + "port-version": 5 }, "dcmtk": { "baseline": "3.6.8", - "port-version": 5 + "port-version": 8 }, "debug-assert": { - "baseline": "1.3.3", - "port-version": 2 + "baseline": "1.3.4", + "port-version": 0 }, "decimal-for-cpp": { "baseline": "1.18", @@ -2229,27 +2273,31 @@ "port-version": 13 }, "dimcli": { - "baseline": "7.2.0", + "baseline": "7.3.0", "port-version": 0 }, - "directx-dxc": { - "baseline": "2024-03-29", + "dingo": { + "baseline": "0.1.0", "port-version": 0 }, + "directx-dxc": { + "baseline": "2024-07-31", + "port-version": 1 + }, "directx-headers": { - "baseline": "1.613.0", + "baseline": "1.614.1", "port-version": 0 }, "directx12-agility": { - "baseline": "1.613.0", + "baseline": "1.614.1", "port-version": 0 }, "directxmath": { - "baseline": "2024-02-14", - "port-version": 1 + "baseline": "2024-10-15", + "port-version": 0 }, "directxmesh": { - "baseline": "2024-02-21", + "baseline": "2024-09-04", "port-version": 0 }, "directxsdk": { @@ -2257,15 +2305,15 @@ "port-version": 8 }, "directxtex": { - "baseline": "2024-03-06", + "baseline": "2024-09-04", "port-version": 0 }, "directxtk": { - "baseline": "2024-02-21", + "baseline": "2024-09-04", "port-version": 0 }, "directxtk12": { - "baseline": "2024-02-21", + "baseline": "2024-09-04", "port-version": 0 }, "dirent": { @@ -2281,11 +2329,11 @@ "port-version": 4 }, "discordcoreapi": { - "baseline": "2.0.6", + "baseline": "2.0.7", "port-version": 0 }, "discount": { - "baseline": "3.0.0a", + "baseline": "3.0.0d", "port-version": 0 }, "discreture": { @@ -2301,8 +2349,8 @@ "port-version": 0 }, "dlib": { - "baseline": "19.24", - "port-version": 4 + "baseline": "19.24.6", + "port-version": 0 }, "dlpack": { "baseline": "0.8", @@ -2326,14 +2374,14 @@ }, "dp-thread-pool": { "baseline": "0.6.2", - "port-version": 0 + "port-version": 1 }, "dpdk": { - "baseline": "22.07", - "port-version": 1 + "baseline": "24.07", + "port-version": 0 }, "dpp": { - "baseline": "10.0.30", + "baseline": "10.0.33", "port-version": 0 }, "draco": { @@ -2349,15 +2397,15 @@ "port-version": 0 }, "drogon": { - "baseline": "1.9.4", + "baseline": "1.9.7", "port-version": 0 }, "dstorage": { - "baseline": "1.2.2", - "port-version": 0 + "baseline": "1.2.3", + "port-version": 1 }, "dtl": { - "baseline": "1.20", + "baseline": "1.21", "port-version": 0 }, "duckx": { @@ -2374,7 +2422,7 @@ }, "duktape": { "baseline": "2.7.0", - "port-version": 1 + "port-version": 2 }, "dumb": { "baseline": "2.0.3", @@ -2382,7 +2430,7 @@ }, "dv-processing": { "baseline": "1.7.9", - "port-version": 1 + "port-version": 2 }, "dx": { "baseline": "1.0.1", @@ -2393,8 +2441,8 @@ "port-version": 7 }, "dxut": { - "baseline": "11.31", - "port-version": 3 + "baseline": "11.32", + "port-version": 0 }, "dylib": { "baseline": "2.2.1", @@ -2426,7 +2474,7 @@ }, "easyhook": { "baseline": "2.7.7097.0", - "port-version": 7 + "port-version": 8 }, "easyloggingpp": { "baseline": "9.97.1", @@ -2441,27 +2489,23 @@ "port-version": 0 }, "ecal": { - "baseline": "5.12.0", + "baseline": "5.13.2", "port-version": 0 }, "ecm": { - "baseline": "5.98.0", + "baseline": "6.4.0", "port-version": 0 }, "ecos": { "baseline": "2.0.10", "port-version": 0 }, - "ecsutil": { - "baseline": "1.0.7.15", - "port-version": 5 - }, "ed25519": { "baseline": "2017-02-10", - "port-version": 0 + "port-version": 1 }, "edflib": { - "baseline": "1.25", + "baseline": "1.26", "port-version": 0 }, "edlib": { @@ -2477,7 +2521,7 @@ "port-version": 0 }, "efsw": { - "baseline": "1.3.1", + "baseline": "1.4.0", "port-version": 0 }, "egl": { @@ -2501,12 +2545,12 @@ "port-version": 0 }, "elfutils": { - "baseline": "0.186", - "port-version": 4 + "baseline": "0.191", + "port-version": 0 }, "embree3": { "baseline": "3.13.5", - "port-version": 3 + "port-version": 4 }, "enet": { "baseline": "1.3.17", @@ -2514,15 +2558,15 @@ }, "enkits": { "baseline": "1.11", - "port-version": 2 + "port-version": 3 }, "ensmallen": { - "baseline": "2.19.1", + "baseline": "2.21.1", "port-version": 0 }, "entityx": { "baseline": "1.3.0", - "port-version": 5 + "port-version": 6 }, "entt": { "baseline": "3.13.2", @@ -2545,8 +2589,8 @@ "port-version": 0 }, "etl": { - "baseline": "20.38.10", - "port-version": 1 + "baseline": "20.39.4", + "port-version": 0 }, "eve": { "baseline": "2023.2.15", @@ -2561,15 +2605,15 @@ "port-version": 8 }, "exiv2": { - "baseline": "0.28.1", - "port-version": 1 + "baseline": "0.28.3", + "port-version": 2 }, "expat": { - "baseline": "2.6.2", + "baseline": "2.6.3", "port-version": 0 }, "expected-lite": { - "baseline": "0.6.3", + "baseline": "0.8.0", "port-version": 0 }, "exprtk": { @@ -2594,11 +2638,11 @@ }, "faiss": { "baseline": "1.7.4", - "port-version": 0 + "port-version": 1 }, "fakeit": { - "baseline": "2.4.0", - "port-version": 2 + "baseline": "2.4.1", + "port-version": 0 }, "fameta-counter": { "baseline": "2021-02-13", @@ -2616,28 +2660,36 @@ "baseline": "2021-01-03", "port-version": 2 }, + "fast-double-parser": { + "baseline": "0.8.0", + "port-version": 0 + }, "fast-float": { - "baseline": "6.1.1", + "baseline": "6.1.6", "port-version": 0 }, "fastcdr": { - "baseline": "1.1.0", + "baseline": "2.2.4", "port-version": 0 }, "fastcgi": { "baseline": "2020-09-11", "port-version": 5 }, + "fastdds": { + "baseline": "3.1.0", + "port-version": 0 + }, "fastfeat": { "baseline": "391d5e9", "port-version": 4 }, "fastgltf": { - "baseline": "0.7.1", + "baseline": "0.7.2", "port-version": 0 }, "fastio": { - "baseline": "2023-11-06", + "baseline": "2024-07-05", "port-version": 0 }, "fastlz": { @@ -2648,12 +2700,8 @@ "baseline": "2021-11-22", "port-version": 1 }, - "fastrtps": { - "baseline": "2.7.0", - "port-version": 5 - }, "faudio": { - "baseline": "24.03", + "baseline": "24.09", "port-version": 0 }, "fawdlstty-libfv": { @@ -2662,10 +2710,10 @@ }, "fbgemm": { "baseline": "0.4.1", - "port-version": 0 + "port-version": 1 }, "fbthrift": { - "baseline": "2024.05.06.00", + "baseline": "2024.10.07.00", "port-version": 0 }, "fcl": { @@ -2674,23 +2722,27 @@ }, "fdk-aac": { "baseline": "2.0.2", - "port-version": 3 + "port-version": 4 }, "fdlibm": { "baseline": "5.3", "port-version": 7 }, + "fenster": { + "baseline": "2024-08-19", + "port-version": 0 + }, "ffmpeg": { - "baseline": "6.1.1", - "port-version": 3 + "baseline": "7.0.2", + "port-version": 5 }, "ffnvcodec": { - "baseline": "12.1.14.0", + "baseline": "12.2.72.0", "port-version": 0 }, "fftw3": { "baseline": "3.3.10", - "port-version": 8 + "port-version": 9 }, "fftwpp": { "baseline": "2019-12-19", @@ -2709,7 +2761,7 @@ "port-version": 0 }, "fizz": { - "baseline": "2024.05.06.00", + "baseline": "2024.10.07.00", "port-version": 0 }, "flagpp": { @@ -2749,11 +2801,11 @@ "port-version": 0 }, "flatbush": { - "baseline": "1.2.0", + "baseline": "1.2.1", "port-version": 0 }, "flecs": { - "baseline": "3.2.11", + "baseline": "4.0.2", "port-version": 0 }, "flint": { @@ -2762,14 +2814,14 @@ }, "fltk": { "baseline": "1.3.9", - "port-version": 0 + "port-version": 1 }, "fluidlite": { "baseline": "2023-04-18", "port-version": 0 }, "fluidsynth": { - "baseline": "2.3.5", + "baseline": "2.3.6", "port-version": 0 }, "flux": { @@ -2789,11 +2841,11 @@ "port-version": 2 }, "fmt": { - "baseline": "10.2.1", - "port-version": 2 + "baseline": "11.0.2", + "port-version": 1 }, "folly": { - "baseline": "2024.05.06.00", + "baseline": "2024.10.07.00", "port-version": 0 }, "font-chef": { @@ -2805,7 +2857,7 @@ "port-version": 0 }, "fontconfig": { - "baseline": "2.14.2", + "baseline": "2.15.0", "port-version": 1 }, "foonathan-lexy": { @@ -2814,7 +2866,7 @@ }, "foonathan-memory": { "baseline": "0.7.3", - "port-version": 1 + "port-version": 2 }, "forge": { "baseline": "1.0.8", @@ -2834,11 +2886,11 @@ }, "freeglut": { "baseline": "3.4.0", - "port-version": 1 + "port-version": 3 }, "freeimage": { "baseline": "3.18.0", - "port-version": 26 + "port-version": 27 }, "freeopcua": { "baseline": "20190125", @@ -2846,15 +2898,15 @@ }, "freerdp": { "baseline": "3.4.0", - "port-version": 1 + "port-version": 3 }, "freetds": { "baseline": "1.3.10", "port-version": 2 }, "freetype": { - "baseline": "2.13.2", - "port-version": 1 + "baseline": "2.13.3", + "port-version": 0 }, "freetype-gl": { "baseline": "2022-01-17", @@ -2865,24 +2917,24 @@ "port-version": 0 }, "fribidi": { - "baseline": "1.0.13", + "baseline": "1.0.16", "port-version": 0 }, "frozen": { - "baseline": "1.1.1", + "baseline": "1.2.0", "port-version": 0 }, "frugally-deep": { - "baseline": "0.15.31", - "port-version": 1 + "baseline": "0.16.0", + "port-version": 0 }, "fruit": { "baseline": "3.7.1", "port-version": 0 }, "ftgl": { - "baseline": "2022-05-18", - "port-version": 1 + "baseline": "2.4.0", + "port-version": 5 }, "ftxui": { "baseline": "5.0.0", @@ -2893,7 +2945,7 @@ "port-version": 0 }, "functionalplus": { - "baseline": "0.2.24", + "baseline": "0.2.25", "port-version": 0 }, "functions-framework-cpp": { @@ -2953,24 +3005,20 @@ "port-version": 5 }, "gcem": { - "baseline": "1.17.0", + "baseline": "1.18.0", "port-version": 0 }, "gdal": { - "baseline": "3.8.5", + "baseline": "3.9.3", "port-version": 0 }, "gdcm": { - "baseline": "3.0.23", + "baseline": "3.0.24", "port-version": 0 }, - "gdcm2": { - "baseline": "deprecated", - "port-version": 1 - }, "gdk-pixbuf": { - "baseline": "2.42.10", - "port-version": 6 + "baseline": "2.42.12", + "port-version": 1 }, "gemmlowp": { "baseline": "2021-09-28", @@ -2982,15 +3030,15 @@ }, "geogram": { "baseline": "1.8.3", - "port-version": 1 + "port-version": 3 }, "geographiclib": { - "baseline": "2.3", - "port-version": 1 + "baseline": "2.4", + "port-version": 0 }, "geos": { - "baseline": "3.11.3", - "port-version": 1 + "baseline": "3.13.0", + "port-version": 0 }, "geotrans": { "baseline": "3.9", @@ -3010,11 +3058,11 @@ }, "gettext": { "baseline": "0.22.5", - "port-version": 0 + "port-version": 1 }, "gettext-libintl": { "baseline": "0.22.5", - "port-version": 0 + "port-version": 2 }, "gettimeofday": { "baseline": "2017-10-14", @@ -3037,7 +3085,7 @@ "port-version": 0 }, "ginkgo": { - "baseline": "1.7.0", + "baseline": "1.8.0", "port-version": 0 }, "gklib": { @@ -3057,7 +3105,7 @@ "port-version": 0 }, "glaze": { - "baseline": "2.6.1", + "baseline": "3.6.1", "port-version": 0 }, "glbinding": { @@ -3070,14 +3118,14 @@ }, "glfw3": { "baseline": "3.4", - "port-version": 0 + "port-version": 1 }, "gli": { "baseline": "2021-07-06", "port-version": 2 }, "glib": { - "baseline": "2.78.4", + "baseline": "2.80.0", "port-version": 1 }, "glib-networking": { @@ -3090,26 +3138,26 @@ }, "glm": { "baseline": "1.0.1", - "port-version": 2 + "port-version": 3 }, "globjects": { "baseline": "1.1.0", "port-version": 6 }, "glog": { - "baseline": "0.7.0", - "port-version": 1 + "baseline": "0.7.1", + "port-version": 0 }, "gloo": { - "baseline": "20201203", - "port-version": 3 + "baseline": "20240626", + "port-version": 0 }, "glpk": { "baseline": "5.0", "port-version": 3 }, "glslang": { - "baseline": "14.2.0", + "baseline": "15.0.0", "port-version": 0 }, "glui": { @@ -3121,7 +3169,7 @@ "port-version": 6 }, "gmmlib": { - "baseline": "22.3.17", + "baseline": "22.5.2", "port-version": 0 }, "gmp": { @@ -3130,14 +3178,18 @@ }, "gmsh": { "baseline": "4.12.2", - "port-version": 0 + "port-version": 1 }, "gobject-introspection": { "baseline": "1.72.0", "port-version": 8 }, + "godot-cpp": { + "baseline": "4.3", + "port-version": 0 + }, "google-cloud-cpp": { - "baseline": "2.24.0", + "baseline": "2.30.0", "port-version": 0 }, "google-cloud-cpp-common": { @@ -3188,29 +3240,29 @@ "baseline": "1.3.14", "port-version": 4 }, - "graphqlparser": { - "baseline": "0.7.0", - "port-version": 4 - }, "graphviz": { "baseline": "10.0.1", - "port-version": 1 + "port-version": 2 }, "greatest": { "baseline": "1.5.0", "port-version": 0 }, "grpc": { - "baseline": "1.51.1", - "port-version": 3 + "baseline": "1.60.0", + "port-version": 1 }, "grppi": { "baseline": "0.4.0", "port-version": 2 }, + "gsasl": { + "baseline": "2.2.1", + "port-version": 0 + }, "gsl": { - "baseline": "2.7.1", - "port-version": 3 + "baseline": "2.8", + "port-version": 0 }, "gsl-lite": { "baseline": "0.41.0", @@ -3225,27 +3277,27 @@ "port-version": 2 }, "gstreamer": { - "baseline": "1.22.5", - "port-version": 8 + "baseline": "1.24.7", + "port-version": 0 }, "gtest": { - "baseline": "1.14.0", - "port-version": 1 + "baseline": "1.15.2", + "port-version": 0 }, "gtk": { - "baseline": "4.10.5", - "port-version": 2 + "baseline": "4.14.0", + "port-version": 1 }, "gtk3": { "baseline": "3.24.38", "port-version": 1 }, "gtkmm": { - "baseline": "4.10.0", - "port-version": 1 + "baseline": "4.14.0", + "port-version": 0 }, "gtl": { - "baseline": "1.1.8", + "baseline": "1.2.0", "port-version": 0 }, "gts": { @@ -3253,15 +3305,15 @@ "port-version": 9 }, "gtsam": { - "baseline": "4.2a9", - "port-version": 1 + "baseline": "4.2.0", + "port-version": 0 }, "guetzli": { "baseline": "2020-09-14", "port-version": 2 }, "guile": { - "baseline": "3.0.9", + "baseline": "3.0.10", "port-version": 0 }, "guilite": { @@ -3282,7 +3334,7 @@ }, "gz-common5": { "baseline": "5.4.1", - "port-version": 1 + "port-version": 2 }, "gz-fuel-tools8": { "baseline": "8.1.0", @@ -3321,7 +3373,7 @@ "port-version": 1 }, "gz-transport12": { - "baseline": "12.2.0", + "baseline": "12.2.1", "port-version": 0 }, "gz-utils2": { @@ -3340,9 +3392,13 @@ "baseline": "2022-05-24", "port-version": 0 }, + "half": { + "baseline": "2.2.0", + "port-version": 0 + }, "halide": { "baseline": "17.0.1", - "port-version": 0 + "port-version": 1 }, "happly": { "baseline": "2021-03-19", @@ -3353,27 +3409,31 @@ "port-version": 0 }, "harfbuzz": { - "baseline": "8.4.0", - "port-version": 1 + "baseline": "10.0.1", + "port-version": 0 }, "hash-library": { "baseline": "8", - "port-version": 2 + "port-version": 3 }, "hashids": { - "baseline": "1.2.1", + "baseline": "1.2.2", "port-version": 0 }, "hayai": { "baseline": "2019-08-10", - "port-version": 3 + "port-version": 4 }, "hazelcast-cpp-client": { "baseline": "5.3.0", - "port-version": 0 + "port-version": 1 }, "hdf5": { - "baseline": "1.14.2", + "baseline": "1.14.4.3", + "port-version": 2 + }, + "hdr-histogram": { + "baseline": "0.11.8", "port-version": 0 }, "healpix": { @@ -3385,15 +3445,15 @@ "port-version": 0 }, "hello-imgui": { - "baseline": "1.4.2", - "port-version": 1 + "baseline": "1.5.2", + "port-version": 0 }, "hexl": { "baseline": "1.2.5", "port-version": 0 }, "hffix": { - "baseline": "1.3.0", + "baseline": "1.4.1", "port-version": 0 }, "hfsm2": { @@ -3402,18 +3462,18 @@ }, "hidapi": { "baseline": "0.14.0", - "port-version": 0 + "port-version": 1 }, "highfive": { - "baseline": "2.9.0", + "baseline": "2.10.0", "port-version": 0 }, "highs": { - "baseline": "1.6.0", - "port-version": 1 + "baseline": "1.7.2", + "port-version": 0 }, "highway": { - "baseline": "1.1.0", + "baseline": "1.2.0", "port-version": 0 }, "hikogui": { @@ -3428,6 +3488,10 @@ "baseline": "2.4.1", "port-version": 0 }, + "hlslpp": { + "baseline": "3.5.3", + "port-version": 0 + }, "hnswlib": { "baseline": "0.8.0", "port-version": 0 @@ -3437,9 +3501,17 @@ "port-version": 0 }, "hpx": { - "baseline": "1.9.1", + "baseline": "1.10.0", "port-version": 1 }, + "htscodecs": { + "baseline": "1.6.1", + "port-version": 0 + }, + "htslib": { + "baseline": "1.21", + "port-version": 0 + }, "http-parser": { "baseline": "2.9.4", "port-version": 3 @@ -3453,7 +3525,7 @@ "port-version": 1 }, "hwloc": { - "baseline": "2.10.0", + "baseline": "2.11.2", "port-version": 0 }, "hyperscan": { @@ -3461,7 +3533,7 @@ "port-version": 0 }, "hypodermic": { - "baseline": "2.5.3", + "baseline": "2023-03-03", "port-version": 0 }, "hypre": { @@ -3469,12 +3541,12 @@ "port-version": 1 }, "iceoryx": { - "baseline": "2.0.5", + "baseline": "2.0.6", "port-version": 0 }, "icu": { "baseline": "74.2", - "port-version": 1 + "port-version": 4 }, "ideviceinstaller": { "baseline": "2023-07-21", @@ -3485,7 +3557,7 @@ "port-version": 0 }, "idyntree": { - "baseline": "12.1.0", + "baseline": "12.4.0", "port-version": 0 }, "if97": { @@ -3534,15 +3606,15 @@ }, "ignition-msgs1": { "baseline": "1.0.0", - "port-version": 6 + "port-version": 7 }, "ignition-msgs5": { "baseline": "5.3.0", - "port-version": 6 + "port-version": 7 }, "ignition-msgs6": { "baseline": "6.0.0", - "port-version": 5 + "port-version": 6 }, "ignition-plugin1": { "baseline": "1.4.0", @@ -3554,50 +3626,50 @@ }, "ignition-transport4": { "baseline": "4.0.0", - "port-version": 6 + "port-version": 7 }, "ignition-transport8": { "baseline": "8.1.0", - "port-version": 4 + "port-version": 5 }, "ignition-transport9": { "baseline": "9.0.0", - "port-version": 4 + "port-version": 5 }, "ignition-utils1": { "baseline": "1.5.1", "port-version": 0 }, "igraph": { - "baseline": "0.10.12", + "baseline": "0.10.13", "port-version": 0 }, "iir1": { - "baseline": "1.9.4", + "baseline": "1.9.5", "port-version": 0 }, "ijg-libjpeg": { "baseline": "9e", - "port-version": 1 - }, - "ilmbase": { - "baseline": "3", - "port-version": 0 + "port-version": 2 }, "im3d": { "baseline": "2022-10-11", "port-version": 0 }, "imageinfo": { - "baseline": "2024-02-21", + "baseline": "2024-08-05", "port-version": 0 }, "imath": { - "baseline": "3.1.11", + "baseline": "3.1.12", + "port-version": 0 + }, + "imcce-openfa": { + "baseline": "20231011.0.3", "port-version": 0 }, "imgui": { - "baseline": "1.90.2", + "baseline": "1.91.0", "port-version": 0 }, "imgui-node-editor": { @@ -3609,7 +3681,7 @@ "port-version": 0 }, "imguizmo": { - "baseline": "1.83", + "baseline": "2024-05-29", "port-version": 1 }, "immer": { @@ -3658,7 +3730,7 @@ }, "intel-mkl": { "baseline": "2023.0.0", - "port-version": 3 + "port-version": 4 }, "intelrdfpmathlib": { "baseline": "20U2", @@ -3685,7 +3757,7 @@ "port-version": 0 }, "ismrmrd": { - "baseline": "1.13.7", + "baseline": "1.14.1", "port-version": 0 }, "itay-grudev-singleapplication": { @@ -3693,12 +3765,12 @@ "port-version": 0 }, "itk": { - "baseline": "5.3rc02", - "port-version": 1 + "baseline": "5.4.0", + "port-version": 0 }, "itpp": { "baseline": "4.3.1", - "port-version": 10 + "port-version": 11 }, "itsy-bitsy": { "baseline": "2022-08-02", @@ -3721,7 +3793,7 @@ "port-version": 1 }, "jasper": { - "baseline": "4.2.1", + "baseline": "4.2.4", "port-version": 0 }, "jbig2dec": { @@ -3740,6 +3812,10 @@ "baseline": "2023-12-27", "port-version": 0 }, + "jigson": { + "baseline": "0.1.3", + "port-version": 0 + }, "jinja2cpplight": { "baseline": "2018-05-08", "port-version": 3 @@ -3749,7 +3825,7 @@ "port-version": 1 }, "joltphysics": { - "baseline": "5.0.0", + "baseline": "5.1.0", "port-version": 1 }, "josuttis-jthread": { @@ -3765,11 +3841,11 @@ "port-version": 0 }, "json-dto": { - "baseline": "0.3.3", + "baseline": "0.3.4", "port-version": 0 }, "json-rpc-cxx": { - "baseline": "0.3.1", + "baseline": "0.3.2", "port-version": 0 }, "json-schema-validator": { @@ -3786,18 +3862,18 @@ }, "json5-parser": { "baseline": "1.0.0", - "port-version": 6 + "port-version": 7 }, "jsoncons": { - "baseline": "0.175.0", + "baseline": "0.177.0", "port-version": 0 }, "jsoncpp": { - "baseline": "1.9.5", - "port-version": 4 + "baseline": "1.9.6", + "port-version": 0 }, "jsonifier": { - "baseline": "0.9.95", + "baseline": "0.9.97", "port-version": 0 }, "jsonnet": { @@ -3810,11 +3886,11 @@ }, "jwt-cpp": { "baseline": "0.7.0", - "port-version": 0 + "port-version": 1 }, "jxrlib": { "baseline": "2019.10.9", - "port-version": 6 + "port-version": 7 }, "kaitai-struct-cpp-stl-runtime": { "baseline": "0.10.1", @@ -3833,11 +3909,11 @@ "port-version": 0 }, "kdbindings": { - "baseline": "1.0.3", + "baseline": "1.0.5", "port-version": 0 }, "kddockwidgets": { - "baseline": "2.0.0", + "baseline": "2.1.0", "port-version": 0 }, "kdsoap": { @@ -3850,14 +3926,14 @@ }, "keccak-tiny": { "baseline": "2014-09-08", - "port-version": 0 + "port-version": 2 }, "kenlm": { "baseline": "20230531", "port-version": 1 }, "kerbal": { - "baseline": "2024.4.1", + "baseline": "2024.8.1", "port-version": 0 }, "keystone": { @@ -3954,7 +4030,7 @@ }, "kf5kio": { "baseline": "5.98.0", - "port-version": 0 + "port-version": 1 }, "kf5newstuff": { "baseline": "5.98.0", @@ -3994,7 +4070,7 @@ }, "kf5texteditor": { "baseline": "5.98.0", - "port-version": 0 + "port-version": 1 }, "kf5textwidgets": { "baseline": "5.98.0", @@ -4049,12 +4125,12 @@ "port-version": 0 }, "krb5": { - "baseline": "1.21.2", - "port-version": 0 + "baseline": "1.21.3", + "port-version": 1 }, "ktx": { - "baseline": "4.3.1", - "port-version": 1 + "baseline": "4.3.2", + "port-version": 0 }, "kubazip": { "baseline": "0.2.6", @@ -4082,15 +4158,15 @@ }, "lapack": { "baseline": "2023-06-10", - "port-version": 0 + "port-version": 2 }, "lapack-reference": { "baseline": "3.11.0", "port-version": 6 }, "lastools": { - "baseline": "2.0.2+20230206", - "port-version": 1 + "baseline": "2.0.3", + "port-version": 0 }, "laszip": { "baseline": "3.4.4", @@ -4130,15 +4206,19 @@ }, "leptonica": { "baseline": "1.84.1", - "port-version": 0 + "port-version": 1 }, "lerc": { "baseline": "4.0.4", "port-version": 0 }, "lest": { - "baseline": "1.35.1", - "port-version": 3 + "baseline": "1.35.2", + "port-version": 0 + }, + "level-zero": { + "baseline": "1.17.45", + "port-version": 0 }, "leveldb": { "baseline": "1.23", @@ -4152,6 +4232,14 @@ "baseline": "2.3.0", "port-version": 0 }, + "lfreist-hwinfo": { + "baseline": "2024-09-01", + "port-version": 0 + }, + "lib3mf": { + "baseline": "2.3.2", + "port-version": 0 + }, "libaaplus": { "baseline": "2.36", "port-version": 1 @@ -4172,6 +4260,10 @@ "baseline": "5.0", "port-version": 9 }, + "libaio": { + "baseline": "0.3.113", + "port-version": 0 + }, "libalkimia": { "baseline": "8.1.72", "port-version": 0 @@ -4181,15 +4273,15 @@ "port-version": 5 }, "libarchive": { - "baseline": "3.7.2", + "baseline": "3.7.7", "port-version": 0 }, "libass": { - "baseline": "0.17.1", + "baseline": "0.17.3", "port-version": 0 }, "libassert": { - "baseline": "2.0.2", + "baseline": "2.1.1", "port-version": 0 }, "libassuan": { @@ -4201,7 +4293,7 @@ "port-version": 0 }, "libavif": { - "baseline": "1.0.4", + "baseline": "1.1.1", "port-version": 0 }, "libb2": { @@ -4210,14 +4302,14 @@ }, "libbacktrace": { "baseline": "2023-11-30", - "port-version": 0 + "port-version": 1 }, "libbf": { "baseline": "1.0.0", "port-version": 4 }, "libbson": { - "baseline": "1.27.1", + "baseline": "1.28.1", "port-version": 0 }, "libcaer": { @@ -4229,8 +4321,8 @@ "port-version": 4 }, "libcap": { - "baseline": "2.69", - "port-version": 5 + "baseline": "2.70", + "port-version": 0 }, "libcbor": { "baseline": "0.11.0", @@ -4241,23 +4333,27 @@ "port-version": 4 }, "libcerf": { - "baseline": "1.13", - "port-version": 4 + "baseline": "2.4", + "port-version": 0 + }, + "libcgroup": { + "baseline": "3.1.0", + "port-version": 0 }, "libconfig": { "baseline": "1.7.3", "port-version": 5 }, "libconfuse": { - "baseline": "2019-07-14", - "port-version": 4 + "baseline": "3.3", + "port-version": 0 }, "libcopp": { "baseline": "2.2.0", - "port-version": 0 + "port-version": 1 }, "libcoro": { - "baseline": "0.11.1", + "baseline": "0.12.1", "port-version": 0 }, "libcorrect": { @@ -4272,9 +4368,13 @@ "baseline": "1.0", "port-version": 2 }, + "libcred": { + "baseline": "1.0.0", + "port-version": 0 + }, "libcroco": { "baseline": "0.6.13", - "port-version": 6 + "port-version": 7 }, "libcsv": { "baseline": "3.0.3", @@ -4289,8 +4389,8 @@ "port-version": 0 }, "libdatachannel": { - "baseline": "0.20.3", - "port-version": 0 + "baseline": "0.21.2", + "port-version": 1 }, "libdatrie": { "baseline": "0.2.13", @@ -4305,7 +4405,7 @@ "port-version": 0 }, "libdeflate": { - "baseline": "1.20", + "baseline": "1.22", "port-version": 0 }, "libdisasm": { @@ -4313,11 +4413,11 @@ "port-version": 11 }, "libdivide": { - "baseline": "5.0", - "port-version": 1 + "baseline": "5.1", + "port-version": 0 }, "libdjinterop": { - "baseline": "0.20.3", + "baseline": "0.22.1", "port-version": 0 }, "libdmx": { @@ -4328,9 +4428,13 @@ "baseline": "0.6.0", "port-version": 3 }, + "libdvdcss": { + "baseline": "1.4.3", + "port-version": 0 + }, "libdwarf": { - "baseline": "0.9.1", - "port-version": 1 + "baseline": "0.11.0", + "port-version": 0 }, "libe57": { "baseline": "1.1.332", @@ -4344,8 +4448,12 @@ "baseline": "1.2.6", "port-version": 2 }, + "libedit": { + "baseline": "2024-08-08", + "port-version": 0 + }, "libenvpp": { - "baseline": "1.4.0", + "baseline": "1.4.1", "port-version": 0 }, "libepoxy": { @@ -4353,23 +4461,23 @@ "port-version": 2 }, "liberasurecode": { - "baseline": "1.6.3", + "baseline": "1.6.4", "port-version": 0 }, "libev": { "baseline": "4.33", - "port-version": 2 + "port-version": 3 }, "libevent": { "baseline": "2.1.12+20230128", - "port-version": 0 + "port-version": 1 }, "libeventheader-decode": { - "baseline": "1.3.3", + "baseline": "1.4.0", "port-version": 0 }, "libeventheader-tracepoint": { - "baseline": "1.3.3", + "baseline": "1.4.0", "port-version": 0 }, "libevhtp": { @@ -4401,7 +4509,7 @@ "port-version": 0 }, "libfork": { - "baseline": "3.7.1", + "baseline": "3.8.0", "port-version": 0 }, "libfort": { @@ -4422,7 +4530,7 @@ }, "libftdi1": { "baseline": "1.5", - "port-version": 4 + "port-version": 5 }, "libfuse": { "baseline": "3.16.2", @@ -4437,8 +4545,8 @@ "port-version": 3 }, "libgeotiff": { - "baseline": "1.7.1", - "port-version": 3 + "baseline": "1.7.3", + "port-version": 1 }, "libgig": { "baseline": "4.4.1", @@ -4453,7 +4561,7 @@ "port-version": 0 }, "libgnutls": { - "baseline": "3.8.5", + "baseline": "3.8.7.1", "port-version": 0 }, "libgo": { @@ -4465,7 +4573,7 @@ "port-version": 0 }, "libgpiod": { - "baseline": "2.1", + "baseline": "2.1.3", "port-version": 0 }, "libgpod": { @@ -4486,7 +4594,7 @@ }, "libgxps": { "baseline": "0.3.2", - "port-version": 3 + "port-version": 4 }, "libharu": { "baseline": "2.4.4", @@ -4497,15 +4605,15 @@ "port-version": 6 }, "libheif": { - "baseline": "1.17.6", - "port-version": 1 + "baseline": "1.18.2", + "port-version": 0 }, "libhsplasma": { "baseline": "2024-03-07", "port-version": 0 }, "libhv": { - "baseline": "1.3.2", + "baseline": "1.3.3", "port-version": 0 }, "libhydrogen": { @@ -4513,19 +4621,19 @@ "port-version": 0 }, "libical": { - "baseline": "3.0.17", + "baseline": "3.0.18", "port-version": 0 }, "libice": { - "baseline": "1.0.10", - "port-version": 1 + "baseline": "1.1.1", + "port-version": 0 }, "libiconv": { "baseline": "1.17", - "port-version": 3 + "port-version": 4 }, "libics": { - "baseline": "1.6.6", + "baseline": "1.6.8", "port-version": 0 }, "libid3tag": { @@ -4537,12 +4645,12 @@ "port-version": 1 }, "libidn2": { - "baseline": "2.3.4", - "port-version": 3 + "baseline": "2.3.7", + "port-version": 1 }, "libigl": { "baseline": "2.5.0", - "port-version": 0 + "port-version": 2 }, "libilbc": { "baseline": "3.0.4", @@ -4561,16 +4669,16 @@ "port-version": 2 }, "libjpeg-turbo": { - "baseline": "3.0.2", + "baseline": "3.0.4", "port-version": 0 }, "libjuice": { - "baseline": "1.3.4", - "port-version": 1 + "baseline": "1.5.4", + "port-version": 0 }, "libjxl": { - "baseline": "0.10.2", - "port-version": 2 + "baseline": "0.11.0", + "port-version": 0 }, "libkeyfinder": { "baseline": "2.2.8", @@ -4582,7 +4690,7 @@ }, "liblas": { "baseline": "1.8.1", - "port-version": 14 + "port-version": 15 }, "liblbfgs": { "baseline": "1.10", @@ -4614,14 +4722,14 @@ }, "liblsquic": { "baseline": "3.3.2", - "port-version": 0 + "port-version": 1 }, "liblzf": { "baseline": "3.6", "port-version": 1 }, "liblzma": { - "baseline": "5.4.4", + "baseline": "5.6.3", "port-version": 0 }, "libmad": { @@ -4630,18 +4738,18 @@ }, "libmagic": { "baseline": "5.45", - "port-version": 2 + "port-version": 3 }, "libmariadb": { - "baseline": "3.3.1", - "port-version": 3 + "baseline": "3.4.1", + "port-version": 0 }, "libmaxminddb": { - "baseline": "1.9.1", + "baseline": "1.11.0", "port-version": 0 }, "libmediainfo": { - "baseline": "24.3", + "baseline": "24.6", "port-version": 0 }, "libmesh": { @@ -4657,7 +4765,7 @@ "port-version": 1 }, "libmidi2": { - "baseline": "0.10", + "baseline": "0.13", "port-version": 0 }, "libmikmod": { @@ -4674,7 +4782,7 @@ }, "libmodplug": { "baseline": "0.8.9.0", - "port-version": 11 + "port-version": 13 }, "libmorton": { "baseline": "0.2.12", @@ -4697,23 +4805,27 @@ "port-version": 0 }, "libmupdf": { - "baseline": "1.23.11", + "baseline": "1.24.10", + "port-version": 0 + }, + "libmysofa": { + "baseline": "1.3.2", "port-version": 0 }, "libmysql": { - "baseline": "8.0.34", - "port-version": 1 + "baseline": "8.0.39", + "port-version": 0 }, "libnice": { - "baseline": "0.1.21", - "port-version": 2 + "baseline": "0.1.22", + "port-version": 0 }, "libnice-gst": { - "baseline": "0.1.21", - "port-version": 4 + "baseline": "0.1.22", + "port-version": 0 }, "libnick": { - "baseline": "2024.3.1", + "baseline": "2024.10.0", "port-version": 0 }, "libnoise": { @@ -4725,12 +4837,12 @@ "port-version": 0 }, "libobfuscate": { - "baseline": "2023-03-23", + "baseline": "2024-02-11", "port-version": 0 }, "libodb": { "baseline": "2.4.0", - "port-version": 10 + "port-version": 12 }, "libodb-boost": { "baseline": "2.4.0", @@ -4742,11 +4854,11 @@ }, "libodb-pgsql": { "baseline": "2.4.0", - "port-version": 7 + "port-version": 8 }, "libodb-sqlite": { "baseline": "2.4.0", - "port-version": 11 + "port-version": 12 }, "libofx": { "baseline": "0.10.9", @@ -4757,7 +4869,7 @@ "port-version": 1 }, "libopenmpt": { - "baseline": "0.7.4", + "baseline": "0.7.10", "port-version": 0 }, "libopensp": { @@ -4773,8 +4885,8 @@ "port-version": 3 }, "liborigin": { - "baseline": "3.0.2", - "port-version": 1 + "baseline": "3.0.3", + "port-version": 0 }, "libosdp": { "baseline": "3.0.5", @@ -4782,7 +4894,7 @@ }, "libosip2": { "baseline": "5.3.1", - "port-version": 0 + "port-version": 1 }, "libosmium": { "baseline": "2.20.0", @@ -4790,7 +4902,7 @@ }, "libosmscout": { "baseline": "1.1.1", - "port-version": 4 + "port-version": 5 }, "libp7-baical": { "baseline": "replaced", @@ -4798,18 +4910,18 @@ }, "libp7client": { "baseline": "5.6", - "port-version": 4 + "port-version": 5 }, "libpcap": { - "baseline": "1.10.4", - "port-version": 1 + "baseline": "1.10.5", + "port-version": 0 }, "libpff": { "baseline": "2021-11-14", "port-version": 2 }, "libphonenumber": { - "baseline": "8.13.31", + "baseline": "8.13.45", "port-version": 0 }, "libplist": { @@ -4822,22 +4934,22 @@ }, "libpng": { "baseline": "1.6.43", - "port-version": 1 + "port-version": 3 }, "libpopt": { "baseline": "1.16", - "port-version": 16 + "port-version": 17 }, "libpq": { - "baseline": "16.2", - "port-version": 1 + "baseline": "16.4", + "port-version": 0 }, "libpqxx": { - "baseline": "7.9.0", + "baseline": "7.9.2", "port-version": 0 }, "libprotobuf-mutator": { - "baseline": "1.1", + "baseline": "1.3", "port-version": 0 }, "libproxy": { @@ -4846,7 +4958,7 @@ }, "libpsl": { "baseline": "0.21.5", - "port-version": 0 + "port-version": 1 }, "libqcow": { "baseline": "20221124", @@ -4869,32 +4981,32 @@ "port-version": 0 }, "libraqm": { - "baseline": "0.10.1", + "baseline": "0.10.2", "port-version": 0 }, "libraw": { - "baseline": "0.21.2", + "baseline": "0.21.3", "port-version": 0 }, "librdkafka": { - "baseline": "2.3.0", + "baseline": "2.6.0", "port-version": 0 }, "libredwg": { "baseline": "0.13.3", - "port-version": 0 + "port-version": 1 }, "libremidi": { - "baseline": "4.3.0", + "baseline": "4.5.0", "port-version": 0 }, "libressl": { - "baseline": "3.8.2", - "port-version": 0 + "baseline": "3.9.2", + "port-version": 2 }, "librsvg": { "baseline": "2.40.20", - "port-version": 10 + "port-version": 11 }, "librsync": { "baseline": "2.3.4", @@ -4904,6 +5016,10 @@ "baseline": "2019-11-11", "port-version": 4 }, + "librtpi": { + "baseline": "1.0.0", + "port-version": 1 + }, "librttopo": { "baseline": "1.1.0", "port-version": 8 @@ -4932,16 +5048,20 @@ "baseline": "1.3.2", "port-version": 1 }, + "libsersi": { + "baseline": "0.1.0", + "port-version": 0 + }, "libsigcpp": { "baseline": "3.6.0", - "port-version": 0 + "port-version": 1 }, "libsigcpp-3": { "baseline": "3.0.3", "port-version": 1 }, "libslirp": { - "baseline": "4.7.0", + "baseline": "4.8.0", "port-version": 0 }, "libsm": { @@ -4965,8 +5085,8 @@ "port-version": 2 }, "libsodium": { - "baseline": "1.0.19", - "port-version": 2 + "baseline": "1.0.20", + "port-version": 3 }, "libsonic": { "baseline": "0.2.0", @@ -4977,7 +5097,7 @@ "port-version": 7 }, "libsoup": { - "baseline": "3.4.4", + "baseline": "3.6.0", "port-version": 0 }, "libspatialindex": { @@ -4986,7 +5106,7 @@ }, "libspatialite": { "baseline": "5.1.0", - "port-version": 1 + "port-version": 2 }, "libspnav": { "baseline": "0.2.3", @@ -4998,39 +5118,39 @@ }, "libsquish": { "baseline": "1.15", - "port-version": 13 + "port-version": 14 }, "libsrt": { "baseline": "1.5.3", - "port-version": 0 + "port-version": 3 }, "libsrtp": { "baseline": "2.5.0", "port-version": 1 }, "libssh": { - "baseline": "0.10.5", - "port-version": 1 + "baseline": "0.10.6", + "port-version": 0 }, "libssh2": { "baseline": "1.11.0", - "port-version": 1 + "port-version": 2 }, "libstemmer": { - "baseline": "2017-9", - "port-version": 8 + "baseline": "2021.2.2.0", + "port-version": 0 }, "libstk": { "baseline": "4.6.1", "port-version": 3 }, "libsvm": { - "baseline": "3.32", + "baseline": "3.35", "port-version": 0 }, "libsystemd": { - "baseline": "255", - "port-version": 2 + "baseline": "256.4", + "port-version": 0 }, "libtar": { "baseline": "1.2.20", @@ -5061,27 +5181,27 @@ "port-version": 3 }, "libtommath": { - "baseline": "1.2.1", + "baseline": "1.3.0", "port-version": 0 }, "libtorch": { "baseline": "2.1.2", - "port-version": 2 + "port-version": 7 }, "libtorrent": { "baseline": "2.0.10", "port-version": 0 }, "libtracepoint": { - "baseline": "1.3.3", + "baseline": "1.4.0", "port-version": 0 }, "libtracepoint-control": { - "baseline": "1.3.3", + "baseline": "1.4.0", "port-version": 0 }, "libtracepoint-decode": { - "baseline": "1.3.3", + "baseline": "1.4.0", "port-version": 0 }, "libu2f-server": { @@ -5090,11 +5210,11 @@ }, "libudis86": { "baseline": "2018-01-28", - "port-version": 3 + "port-version": 4 }, "libudns": { "baseline": "0.4", - "port-version": 5 + "port-version": 6 }, "libui": { "baseline": "2018-11-03", @@ -5114,15 +5234,15 @@ }, "libunwind": { "baseline": "1.8.1", - "port-version": 0 + "port-version": 1 }, "liburing": { - "baseline": "2.6", - "port-version": 0 + "baseline": "2.7", + "port-version": 1 }, "libusb": { "baseline": "1.0.27", - "port-version": 1 + "port-version": 2 }, "libusb-win32": { "baseline": "1.2.6.0", @@ -5132,33 +5252,41 @@ "baseline": "2023-06-21", "port-version": 1 }, + "libusbp": { + "baseline": "1.3.1", + "port-version": 0 + }, "libuuid": { "baseline": "1.0.3", "port-version": 14 }, "libuv": { - "baseline": "1.46.0", - "port-version": 1 + "baseline": "1.49.1", + "port-version": 0 }, "libuvc": { "baseline": "0.0.7", - "port-version": 0 + "port-version": 1 }, "libvault": { - "baseline": "0.56.0", + "baseline": "0.61.0", "port-version": 0 }, "libvhdi": { "baseline": "20231127", "port-version": 0 }, + "libvmaf": { + "baseline": "3.0.0", + "port-version": 0 + }, "libvmdk": { "baseline": "20221124", - "port-version": 0 + "port-version": 1 }, "libvorbis": { "baseline": "1.3.7", - "port-version": 2 + "port-version": 3 }, "libvpx": { "baseline": "1.13.1", @@ -5169,16 +5297,16 @@ "port-version": 6 }, "libwebm": { - "baseline": "1.0.0.28", - "port-version": 1 + "baseline": "1.0.0.31", + "port-version": 0 }, "libwebp": { "baseline": "1.4.0", "port-version": 1 }, "libwebsockets": { - "baseline": "4.3.2", - "port-version": 0 + "baseline": "4.3.3", + "port-version": 1 }, "libx11": { "baseline": "1.8.1", @@ -5245,19 +5373,19 @@ "port-version": 0 }, "libxkbcommon": { - "baseline": "1.4.1", - "port-version": 1 + "baseline": "1.7.0", + "port-version": 0 }, "libxkbfile": { "baseline": "1.1.0", "port-version": 0 }, "libxlsxwriter": { - "baseline": "1.1.5", - "port-version": 2 + "baseline": "1.1.8", + "port-version": 1 }, "libxml2": { - "baseline": "2.11.7", + "baseline": "2.11.9", "port-version": 0 }, "libxmlmm": { @@ -5277,8 +5405,8 @@ "port-version": 1 }, "libxpm": { - "baseline": "3.5.16", - "port-version": 1 + "baseline": "3.5.17", + "port-version": 0 }, "libxpresent": { "baseline": "1.0.0", @@ -5302,7 +5430,7 @@ }, "libxslt": { "baseline": "1.1.37", - "port-version": 3 + "port-version": 4 }, "libxt": { "baseline": "1.3.0", @@ -5322,11 +5450,11 @@ }, "libyaml": { "baseline": "0.2.5", - "port-version": 4 + "port-version": 5 }, "libyuv": { - "baseline": "1857", - "port-version": 0 + "baseline": "1896", + "port-version": 1 }, "libzen": { "baseline": "0.4.41", @@ -5348,6 +5476,10 @@ "baseline": "2020-11-24", "port-version": 0 }, + "lightgbm": { + "baseline": "4.5.0", + "port-version": 0 + }, "lightningscanner": { "baseline": "1.0.1", "port-version": 0 @@ -5373,11 +5505,11 @@ "port-version": 0 }, "live555": { - "baseline": "2023-11-30", - "port-version": 1 + "baseline": "2024-09-29", + "port-version": 0 }, "llfio": { - "baseline": "2023-11-06", + "baseline": "2024-09-05", "port-version": 0 }, "llgi": { @@ -5389,7 +5521,7 @@ "port-version": 0 }, "llhttp": { - "baseline": "9.2.0", + "baseline": "9.2.1", "port-version": 0 }, "llnl-units": { @@ -5397,15 +5529,15 @@ "port-version": 0 }, "llvm": { - "baseline": "17.0.2", - "port-version": 5 + "baseline": "18.1.6", + "port-version": 1 }, "lmdb": { - "baseline": "0.9.31", + "baseline": "0.9.33", "port-version": 0 }, "lockpp": { - "baseline": "2.6", + "baseline": "3.0", "port-version": 0 }, "lodepng": { @@ -5430,10 +5562,14 @@ }, "loguru": { "baseline": "2.1.0", - "port-version": 3 + "port-version": 4 }, "lpeg": { "baseline": "1.1.0", + "port-version": 1 + }, + "ls-qpack": { + "baseline": "2.5.5", "port-version": 0 }, "ltla-aarand": { @@ -5461,7 +5597,7 @@ "port-version": 0 }, "lua": { - "baseline": "5.4.6", + "baseline": "5.4.7", "port-version": 0 }, "lua-compat53": { @@ -5478,7 +5614,7 @@ }, "luafilesystem": { "baseline": "1.8.0", - "port-version": 5 + "port-version": 7 }, "luajit": { "baseline": "2023-01-04", @@ -5497,11 +5633,11 @@ "port-version": 1 }, "lunarg-vulkantools": { - "baseline": "1.3.280.0", + "baseline": "1.3.296.0", "port-version": 0 }, "lunasvg": { - "baseline": "2.3.9", + "baseline": "2.4.1", "port-version": 0 }, "luv": { @@ -5513,11 +5649,11 @@ "port-version": 1 }, "lz4": { - "baseline": "1.9.4", - "port-version": 1 + "baseline": "1.10.0", + "port-version": 0 }, "lzav": { - "baseline": "3.13", + "baseline": "4.0", "port-version": 0 }, "lzfse": { @@ -5537,20 +5673,20 @@ "port-version": 0 }, "magic-enum": { - "baseline": "0.9.5", - "port-version": 0 + "baseline": "0.9.6", + "port-version": 1 }, "magic-get": { "baseline": "2019-09-02", "port-version": 3 }, "magma": { - "baseline": "2.7.2", - "port-version": 0 + "baseline": "2.8.0", + "port-version": 1 }, "magnum": { "baseline": "2020.06", - "port-version": 17 + "port-version": 19 }, "magnum-extras": { "baseline": "2020.06", @@ -5562,7 +5698,7 @@ }, "magnum-plugins": { "baseline": "2020.06", - "port-version": 12 + "port-version": 13 }, "mailio": { "baseline": "0.23.0", @@ -5589,7 +5725,7 @@ "port-version": 0 }, "mapbox-polylabel": { - "baseline": "1.1.0", + "baseline": "2.0.1", "port-version": 0 }, "mapbox-variant": { @@ -5605,7 +5741,11 @@ "port-version": 0 }, "marble": { - "baseline": "24.02.0", + "baseline": "24.08.2", + "port-version": 0 + }, + "mariadb-connector-cpp": { + "baseline": "1.1.5", "port-version": 0 }, "marisa-trie": { @@ -5621,7 +5761,7 @@ "port-version": 0 }, "materialx": { - "baseline": "1.38.9", + "baseline": "1.39.1", "port-version": 0 }, "mathc": { @@ -5630,10 +5770,14 @@ }, "mathgl": { "baseline": "8.0.1", - "port-version": 5 + "port-version": 7 + }, + "mathter": { + "baseline": "2.0.0", + "port-version": 0 }, "matio": { - "baseline": "1.5.26", + "baseline": "1.5.27", "port-version": 0 }, "matplotlib-cpp": { @@ -5649,7 +5793,7 @@ "port-version": 2 }, "mbedtls": { - "baseline": "2.28.7", + "baseline": "3.6.1", "port-version": 0 }, "mchehab-zbar": { @@ -5660,6 +5804,10 @@ "baseline": "2.7.2.14", "port-version": 5 }, + "md4c": { + "baseline": "0.5.2", + "port-version": 0 + }, "mdl-sdk": { "baseline": "2021.1.2", "port-version": 5 @@ -5678,22 +5826,22 @@ }, "mecab": { "baseline": "2019-09-25", - "port-version": 5 + "port-version": 6 }, "memorymodule": { "baseline": "2019-12-31", "port-version": 3 }, "mesa": { - "baseline": "23.2.1", - "port-version": 1 + "baseline": "24.0.7", + "port-version": 2 }, "meschach": { "baseline": "1.2b", "port-version": 6 }, "meshoptimizer": { - "baseline": "0.20", + "baseline": "0.21", "port-version": 0 }, "metis": { @@ -5710,7 +5858,7 @@ }, "mfx-dispatch": { "baseline": "1.35.1", - "port-version": 3 + "port-version": 4 }, "mgnlibs": { "baseline": "2019-09-29", @@ -5720,9 +5868,17 @@ "baseline": "2.5.1", "port-version": 3 }, + "michaelmiller-sec21": { + "baseline": "1.0.1", + "port-version": 0 + }, + "micro-gl": { + "baseline": "2024-06-18", + "port-version": 0 + }, "microsoft-signalr": { "baseline": "0.1.0-alpha4", - "port-version": 8 + "port-version": 12 }, "mikktspace": { "baseline": "2020-10-06", @@ -5733,8 +5889,8 @@ "port-version": 6 }, "mimalloc": { - "baseline": "2.1.2", - "port-version": 2 + "baseline": "2.1.7", + "port-version": 0 }, "minc": { "baseline": "2.4.03", @@ -5757,7 +5913,7 @@ "port-version": 0 }, "minio-cpp": { - "baseline": "0.2.0", + "baseline": "0.3.0", "port-version": 0 }, "miniply": { @@ -5773,19 +5929,19 @@ "port-version": 0 }, "miniupnpc": { - "baseline": "2.2.6", + "baseline": "2.3.7", "port-version": 0 }, "miniz": { "baseline": "3.0.2", - "port-version": 0 + "port-version": 1 }, "minizip": { "baseline": "1.3.1", - "port-version": 0 + "port-version": 1 }, "minizip-ng": { - "baseline": "4.0.5", + "baseline": "4.0.7", "port-version": 0 }, "mio": { @@ -5793,7 +5949,7 @@ "port-version": 0 }, "mlpack": { - "baseline": "4.3.0", + "baseline": "4.5.0", "port-version": 0 }, "mman": { @@ -5810,7 +5966,7 @@ }, "mnn": { "baseline": "1.1.0", - "port-version": 5 + "port-version": 6 }, "modern-cpp-kafka": { "baseline": "2023.03.07", @@ -5821,20 +5977,20 @@ "port-version": 2 }, "mongo-c-driver": { - "baseline": "1.27.1", + "baseline": "1.28.1", "port-version": 0 }, "mongo-cxx-driver": { - "baseline": "3.10.1", + "baseline": "3.11.0", "port-version": 0 }, "mongoose": { - "baseline": "7.13", + "baseline": "7.15", "port-version": 0 }, "monkeys-audio": { "baseline": "10.08", - "port-version": 1 + "port-version": 2 }, "moos-core": { "baseline": "10.4.0", @@ -5858,15 +6014,15 @@ }, "mozjpeg": { "baseline": "4.1.5", - "port-version": 0 + "port-version": 1 }, "mp-units": { - "baseline": "2.1.0", + "baseline": "2.3.0", "port-version": 0 }, "mp3lame": { "baseline": "3.100", - "port-version": 13 + "port-version": 15 }, "mpark-patterns": { "baseline": "2019-10-03", @@ -5885,8 +6041,8 @@ "port-version": 0 }, "mpg123": { - "baseline": "1.31.3", - "port-version": 4 + "baseline": "1.32.7", + "port-version": 0 }, "mpi": { "baseline": "1", @@ -5924,26 +6080,26 @@ "baseline": "0.43.1", "port-version": 0 }, - "ms-quic": { - "baseline": "1.2.0", - "port-version": 0 - }, "msdfgen": { - "baseline": "1.11.0", + "baseline": "1.12", "port-version": 0 }, "msgpack": { - "baseline": "6.0.0", - "port-version": 1 + "baseline": "6.1.1", + "port-version": 0 }, "msgpack-c": { - "baseline": "6.0.0", + "baseline": "6.1.0", "port-version": 0 }, "msgpack11": { "baseline": "0.0.10", "port-version": 4 }, + "msh3": { + "baseline": "0.6.0", + "port-version": 1 + }, "msinttypes": { "baseline": "2018-02-25", "port-version": 2 @@ -5956,6 +6112,10 @@ "baseline": "10.1.12498", "port-version": 4 }, + "msquic": { + "baseline": "2.4.5", + "port-version": 1 + }, "mstch": { "baseline": "1.0.2", "port-version": 5 @@ -5969,7 +6129,7 @@ "port-version": 1 }, "mujs": { - "baseline": "1.3.4", + "baseline": "1.3.5", "port-version": 0 }, "munit": { @@ -5989,7 +6149,7 @@ "port-version": 7 }, "mvfst": { - "baseline": "2024.05.06.00", + "baseline": "2024.10.07.00", "port-version": 0 }, "mygui": { @@ -5998,7 +6158,7 @@ }, "mysql-connector-cpp": { "baseline": "8.0.32", - "port-version": 1 + "port-version": 2 }, "nameof": { "baseline": "0.10.4", @@ -6025,7 +6185,7 @@ "port-version": 8 }, "nanoflann": { - "baseline": "1.5.1", + "baseline": "1.6.1", "port-version": 0 }, "nanogui": { @@ -6038,10 +6198,10 @@ }, "nanomsg": { "baseline": "1.2.1", - "port-version": 1 + "port-version": 2 }, "nanopb": { - "baseline": "0.4.8", + "baseline": "0.4.9", "port-version": 0 }, "nanoprintf": { @@ -6064,8 +6224,8 @@ "baseline": "2019-08-30", "port-version": 6 }, - "nativefiledialog": { - "baseline": "2022-01-20", + "nativefiledialog-extended": { + "baseline": "1.2.1", "port-version": 0 }, "nayuki-qr-code-generator": { @@ -6108,16 +6268,20 @@ "baseline": "4.3.1", "port-version": 5 }, + "netcpp": { + "baseline": "0.5.0", + "port-version": 0 + }, "netgen": { "baseline": "6.2.2401", - "port-version": 1 + "port-version": 2 }, "nethost": { "baseline": "8.0.3", "port-version": 0 }, "nettle": { - "baseline": "3.9.1", + "baseline": "3.10", "port-version": 0 }, "networkdirect-sdk": { @@ -6125,7 +6289,7 @@ "port-version": 4 }, "nghttp2": { - "baseline": "1.61.0", + "baseline": "1.63.0", "port-version": 0 }, "nghttp2-asio": { @@ -6133,7 +6297,7 @@ "port-version": 1 }, "nghttp3": { - "baseline": "1.1.0", + "baseline": "1.6.0", "port-version": 0 }, "ngspice": { @@ -6141,8 +6305,8 @@ "port-version": 0 }, "ngtcp2": { - "baseline": "1.2.0", - "port-version": 0 + "baseline": "1.7.0", + "port-version": 1 }, "nifly": { "baseline": "1.0.0", @@ -6158,22 +6322,18 @@ }, "nlohmann-json": { "baseline": "3.11.3", - "port-version": 0 - }, - "nlopt": { - "baseline": "2.7.1", "port-version": 1 }, - "nmap": { - "baseline": "7.70", - "port-version": 11 + "nlopt": { + "baseline": "2.8.0", + "port-version": 0 }, "nmslib": { "baseline": "2.1.1", "port-version": 1 }, "nng": { - "baseline": "1.7.3", + "baseline": "1.9.0", "port-version": 0 }, "nngpp": { @@ -6214,10 +6374,10 @@ }, "nss": { "baseline": "3.99", - "port-version": 0 + "port-version": 1 }, "nsync": { - "baseline": "1.26.0", + "baseline": "1.29.2", "port-version": 0 }, "nt-wrapper": { @@ -6242,10 +6402,14 @@ }, "numcpp": { "baseline": "2.12.1", - "port-version": 1 + "port-version": 2 }, "nuspell": { - "baseline": "5.1.4", + "baseline": "5.1.6", + "port-version": 0 + }, + "nvidia-cutlass": { + "baseline": "3.3.0", "port-version": 0 }, "nvtt": { @@ -6274,7 +6438,7 @@ }, "oatpp-mbedtls": { "baseline": "1.3.0", - "port-version": 0 + "port-version": 1 }, "oatpp-mongo": { "baseline": "1.3.0", @@ -6320,13 +6484,17 @@ "baseline": "4.7.6", "port-version": 0 }, - "octomap": { + "octave": { + "baseline": "9.2.0", + "port-version": 1 + }, + "octomap": { "baseline": "1.10.0", "port-version": 0 }, "ode": { - "baseline": "0.16.4", - "port-version": 1 + "baseline": "0.16.5", + "port-version": 0 }, "offscale-libetcd-cpp": { "baseline": "2019-07-10", @@ -6338,7 +6506,7 @@ }, "ogre": { "baseline": "14.2.2", - "port-version": 0 + "port-version": 1 }, "ogre-next": { "baseline": "2.3.3", @@ -6350,34 +6518,34 @@ }, "omniorb": { "baseline": "4.3.0", - "port-version": 2 + "port-version": 3 }, "ompl": { "baseline": "1.6.0", - "port-version": 0 + "port-version": 2 }, "omplapp": { "baseline": "1.5.1", "port-version": 5 }, "onednn": { - "baseline": "3.4", - "port-version": 0 + "baseline": "3.5.3", + "port-version": 1 }, "oniguruma": { "baseline": "6.9.7.1", "port-version": 1 }, "onnx": { - "baseline": "1.15.0", - "port-version": 1 + "baseline": "1.16.2", + "port-version": 0 }, "onnx-optimizer": { - "baseline": "0.3.18", + "baseline": "0.3.19", "port-version": 0 }, "onnxruntime-gpu": { - "baseline": "1.16.3", + "baseline": "1.19.2", "port-version": 0 }, "oof": { @@ -6389,40 +6557,40 @@ "port-version": 0 }, "open62541": { - "baseline": "1.3.9", + "baseline": "1.3.12", "port-version": 0 }, "open62541pp": { - "baseline": "0.12.0", + "baseline": "0.15.0", "port-version": 0 }, "openal-soft": { "baseline": "1.23.1", - "port-version": 0 + "port-version": 2 }, "openblas": { - "baseline": "0.3.27", + "baseline": "0.3.28", "port-version": 0 }, "opencascade": { "baseline": "7.8.1", - "port-version": 0 + "port-version": 1 }, "opencc": { - "baseline": "1.1.6", - "port-version": 1 + "baseline": "1.1.9", + "port-version": 0 }, "opencensus-cpp": { "baseline": "2021-08-26", "port-version": 2 }, "opencl": { - "baseline": "v2023.02.06", - "port-version": 2 + "baseline": "v2024.05.08", + "port-version": 0 }, "opencolorio": { "baseline": "2.2.1", - "port-version": 1 + "port-version": 3 }, "opencsg": { "baseline": "1.6.0", @@ -6442,19 +6610,19 @@ }, "opencv3": { "baseline": "3.4.18", - "port-version": 14 + "port-version": 16 }, "opencv4": { "baseline": "4.8.0", - "port-version": 19 + "port-version": 22 }, "opendnp3": { "baseline": "3.1.1", "port-version": 1 }, "openexr": { - "baseline": "3.2.3", - "port-version": 1 + "baseline": "3.3.1", + "port-version": 0 }, "openfbx": { "baseline": "2022-07-18", @@ -6481,15 +6649,15 @@ "port-version": 4 }, "openimageio": { - "baseline": "2.5.8.0", - "port-version": 3 + "baseline": "2.5.16.0", + "port-version": 0 }, "openjpeg": { "baseline": "2.5.2", "port-version": 1 }, "openldap": { - "baseline": "2.5.17", + "baseline": "2.5.18", "port-version": 0 }, "openmama": { @@ -6506,47 +6674,39 @@ }, "openmvg": { "baseline": "2.0", - "port-version": 10 + "port-version": 11 }, "openmvs": { "baseline": "2.1.0", - "port-version": 5 + "port-version": 6 }, "openni2": { "baseline": "2.2.0.33", "port-version": 15 }, "openscap": { - "baseline": "1.3.7", - "port-version": 2 + "baseline": "1.4.0", + "port-version": 1 }, "openslide": { "baseline": "3.4.1", "port-version": 4 }, "openssl": { - "baseline": "3.3.0", - "port-version": 0 - }, - "openssl-unix": { - "baseline": "deprecated", - "port-version": 0 - }, - "openssl-uwp": { - "baseline": "deprecated", - "port-version": 0 - }, - "openssl-windows": { - "baseline": "deprecated", - "port-version": 0 + "baseline": "3.3.2", + "port-version": 1 }, "opensubdiv": { "baseline": "3.5.0", "port-version": 2 }, "opentelemetry-cpp": { - "baseline": "1.14.2", - "port-version": 1 + "baseline": "1.17.0", + "port-version": 0 + }, + "opentelemetry-cpp-contrib-version": { + "baseline": "2024-06-17", + "port-version": 0 }, "opentracing": { "baseline": "1.6.0", @@ -6561,12 +6721,12 @@ "port-version": 0 }, "openvino": { - "baseline": "2024.1.0", - "port-version": 0 + "baseline": "2024.4.0", + "port-version": 1 }, "openvpn3": { - "baseline": "3.7.0", - "port-version": 2 + "baseline": "3.10", + "port-version": 1 }, "openvr": { "baseline": "2.5.1", @@ -6589,7 +6749,7 @@ "port-version": 0 }, "opus": { - "baseline": "1.5.1", + "baseline": "1.5.2", "port-version": 0 }, "opusfile": { @@ -6606,15 +6766,15 @@ }, "osg": { "baseline": "3.6.5", - "port-version": 23 + "port-version": 25 }, "osg-qt": { "baseline": "Qt5", "port-version": 3 }, "osgearth": { - "baseline": "3.4", - "port-version": 1 + "baseline": "3.7", + "port-version": 0 }, "osmanip": { "baseline": "4.6.1", @@ -6625,7 +6785,7 @@ "port-version": 0 }, "outcome": { - "baseline": "2.2.8", + "baseline": "2.2.9", "port-version": 0 }, "p-ranav-csv": { @@ -6637,35 +6797,35 @@ "port-version": 4 }, "pagmo2": { - "baseline": "2.19.0", - "port-version": 2 + "baseline": "2.19.1", + "port-version": 0 }, "paho-mqtt": { "baseline": "1.3.13", "port-version": 1 }, "paho-mqttpp3": { - "baseline": "1.3.2", - "port-version": 0 + "baseline": "1.4.1", + "port-version": 1 }, "palsigslot": { "baseline": "1.2.2", "port-version": 0 }, "pango": { - "baseline": "1.50.14", - "port-version": 4 + "baseline": "1.54.0", + "port-version": 0 }, "pangolin": { - "baseline": "0.8", - "port-version": 3 + "baseline": "0.9.2", + "port-version": 0 }, "pangomm": { "baseline": "2.50.1", "port-version": 3 }, "parallel-hashmap": { - "baseline": "1.3.12", + "baseline": "1.4.0", "port-version": 0 }, "parallelstl": { @@ -6673,8 +6833,8 @@ "port-version": 3 }, "paraview": { - "baseline": "5.12.0", - "port-version": 0 + "baseline": "5.12.1", + "port-version": 2 }, "parmetis": { "baseline": "2022-07-27", @@ -6684,8 +6844,12 @@ "baseline": "0", "port-version": 2 }, + "parsi": { + "baseline": "0.1.0", + "port-version": 0 + }, "parson": { - "baseline": "2022-11-13", + "baseline": "2023-10-31", "port-version": 0 }, "pbc": { @@ -6693,8 +6857,8 @@ "port-version": 9 }, "pcapplusplus": { - "baseline": "23.9", - "port-version": 1 + "baseline": "24.9", + "port-version": 0 }, "pcg": { "baseline": "2021-04-06", @@ -6706,7 +6870,7 @@ }, "pcl": { "baseline": "1.14.1", - "port-version": 0 + "port-version": 2 }, "pcre": { "baseline": "8.45", @@ -6737,7 +6901,7 @@ "port-version": 0 }, "pegtl": { - "baseline": "3.2.7", + "baseline": "3.2.8", "port-version": 0 }, "pegtl-2": { @@ -6745,7 +6909,7 @@ "port-version": 3 }, "perfetto": { - "baseline": "45.0", + "baseline": "48.1", "port-version": 0 }, "pffft": { @@ -6761,7 +6925,7 @@ "port-version": 3 }, "phnt": { - "baseline": "2020-12-21", + "baseline": "2024-05-22", "port-version": 0 }, "physac": { @@ -6789,7 +6953,7 @@ "port-version": 2 }, "pipewire": { - "baseline": "1.0.4", + "baseline": "1.2.5", "port-version": 0 }, "pistache": { @@ -6802,14 +6966,14 @@ }, "pixman": { "baseline": "0.43.4", - "port-version": 0 + "port-version": 1 }, "pkgconf": { - "baseline": "2.2.0", + "baseline": "2.3.0", "port-version": 0 }, "plasma-wayland-protocols": { - "baseline": "1.8.0", + "baseline": "1.14.0", "port-version": 0 }, "platform-folders": { @@ -6881,15 +7045,15 @@ "port-version": 0 }, "pocketpy": { - "baseline": "1.4.5", - "port-version": 0 + "baseline": "1.4.6", + "port-version": 1 }, "poco": { "baseline": "1.13.3", "port-version": 0 }, "podofo": { - "baseline": "0.10.3", + "baseline": "0.10.4", "port-version": 0 }, "poissonrecon": { @@ -6901,7 +7065,7 @@ "port-version": 12 }, "polyhook2": { - "baseline": "2024-02-08", + "baseline": "2024-06-03", "port-version": 0 }, "polymorphic-value": { @@ -6912,6 +7076,10 @@ "baseline": "3.0.0", "port-version": 5 }, + "poolstl": { + "baseline": "0.3.5", + "port-version": 0 + }, "poppler": { "baseline": "24.3.0", "port-version": 1 @@ -6940,6 +7108,10 @@ "baseline": "239", "port-version": 0 }, + "poselib": { + "baseline": "2.0.4", + "port-version": 0 + }, "ppconsul": { "baseline": "0.5", "port-version": 5 @@ -6954,7 +7126,7 @@ }, "pqp": { "baseline": "1.3", - "port-version": 7 + "port-version": 8 }, "pravila00-enum-string": { "baseline": "2023-10-16", @@ -6969,16 +7141,16 @@ "port-version": 0 }, "presentmon": { - "baseline": "1.10.0", + "baseline": "2.1.1", "port-version": 0 }, "proj": { - "baseline": "9.4.0", + "baseline": "9.5.0", "port-version": 0 }, "projectm-eval": { "baseline": "1.0.0", - "port-version": 0 + "port-version": 1 }, "prometheus-cpp": { "baseline": "1.2.4", @@ -6989,12 +7161,12 @@ "port-version": 0 }, "protobuf": { - "baseline": "3.21.12", - "port-version": 2 + "baseline": "4.25.1", + "port-version": 1 }, "protobuf-c": { - "baseline": "1.4.1", - "port-version": 1 + "baseline": "1.5.0", + "port-version": 0 }, "protopuf": { "baseline": "2.2.1", @@ -7009,11 +7181,11 @@ "port-version": 0 }, "proxy": { - "baseline": "2.4.0", + "baseline": "3.0.0", "port-version": 0 }, "proxygen": { - "baseline": "2024.05.06.00", + "baseline": "2024.10.07.00", "port-version": 0 }, "psimd": { @@ -7025,7 +7197,7 @@ "port-version": 1 }, "ptex": { - "baseline": "2.4.2", + "baseline": "2.4.3", "port-version": 0 }, "pthread": { @@ -7053,20 +7225,20 @@ "port-version": 0 }, "pulsar-client-cpp": { - "baseline": "3.4.2", - "port-version": 2 + "baseline": "3.5.1", + "port-version": 1 }, "pulseaudio": { "baseline": "17.0", - "port-version": 0 + "port-version": 2 }, "pulzed-mini": { "baseline": "0.9.14", "port-version": 0 }, "pybind11": { - "baseline": "2.12.0", - "port-version": 1 + "baseline": "2.13.6", + "port-version": 0 }, "pystring": { "baseline": "1.1.4", @@ -7077,16 +7249,16 @@ "port-version": 7 }, "python3": { - "baseline": "3.11.8", - "port-version": 2 + "baseline": "3.11.10", + "port-version": 0 }, "qca": { "baseline": "2.3.7", - "port-version": 1 + "port-version": 2 }, "qcoro": { "baseline": "0.10.0", - "port-version": 0 + "port-version": 1 }, "qcustomplot": { "baseline": "2.1.1", @@ -7114,306 +7286,306 @@ }, "qpid-proton": { "baseline": "0.38.0", - "port-version": 1 + "port-version": 2 }, "qscintilla": { "baseline": "2.14.1", "port-version": 1 }, "qt": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qt-advanced-docking-system": { - "baseline": "4.3.0", + "baseline": "4.3.1", "port-version": 0 }, "qt3d": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qt5": { - "baseline": "5.15.13", - "port-version": 2 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-3d": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-activeqt": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-androidextras": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-base": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-canvas3d": { "baseline": "0", "port-version": 3 }, "qt5-charts": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-connectivity": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-datavis3d": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-declarative": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-doc": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-gamepad": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-graphicaleffects": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-imageformats": { - "baseline": "5.15.13", - "port-version": 2 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-location": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-macextras": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-modularscripts": { "baseline": "deprecated", "port-version": 1 }, "qt5-mqtt": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-multimedia": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-networkauth": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-purchasing": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-quickcontrols": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-quickcontrols2": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-remoteobjects": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-script": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-scxml": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-sensors": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-serialbus": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-serialport": { - "baseline": "5.15.13", + "baseline": "5.15.15", "port-version": 0 }, "qt5-speech": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-svg": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-tools": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-translations": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-virtualkeyboard": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-wayland": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-webchannel": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-webengine": { - "baseline": "5.15.13", - "port-version": 3 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-webglplugin": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-websockets": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-webview": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-winextras": { - "baseline": "5.15.13", - "port-version": 2 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-x11extras": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5-xmlpatterns": { - "baseline": "5.15.13", - "port-version": 1 + "baseline": "5.15.15", + "port-version": 0 }, "qt5compat": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtactiveqt": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtapplicationmanager": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtbase": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtcharts": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtcoap": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtconnectivity": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtdatavis3d": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtdeclarative": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtdeviceutilities": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtdoc": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtgraphs": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtgrpc": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qthttpserver": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtimageformats": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtinterfaceframework": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtkeychain": { - "baseline": "0.14.1", + "baseline": "0.14.3", "port-version": 0 }, "qtkeychain-qt6": { - "baseline": "0.14.1", - "port-version": 1 + "baseline": "0.14.3", + "port-version": 0 }, "qtlanguageserver": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtlocation": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtlottie": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtmqtt": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtmultimedia": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtnetworkauth": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtopcua": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtpositioning": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtquick3d": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtquick3dphysics": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtquickcontrols2": { @@ -7421,75 +7593,75 @@ "port-version": 1 }, "qtquickeffectmaker": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtquicktimeline": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtremoteobjects": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtscxml": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtsensors": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtserialbus": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtserialport": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtshadertools": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtspeech": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtsvg": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qttools": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qttranslations": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtvirtualkeyboard": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtwayland": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtwebchannel": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtwebengine": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtwebsockets": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "qtwebview": { - "baseline": "6.7.0", + "baseline": "6.7.3", "port-version": 0 }, "quadtree": { @@ -7497,7 +7669,7 @@ "port-version": 0 }, "quantlib": { - "baseline": "1.34", + "baseline": "1.36", "port-version": 0 }, "quaternions": { @@ -7517,7 +7689,7 @@ "port-version": 9 }, "quill": { - "baseline": "3.8.0", + "baseline": "7.3.0", "port-version": 0 }, "quirc": { @@ -7554,22 +7726,26 @@ }, "range-v3": { "baseline": "0.12.0", - "port-version": 2 + "port-version": 4 }, "range-v3-vs2015": { "baseline": "20151130-vcpkg5", - "port-version": 2 + "port-version": 3 }, "rapidcheck": { "baseline": "2023-12-14", "port-version": 0 }, "rapidcsv": { - "baseline": "8.80", + "baseline": "8.84", "port-version": 0 }, "rapidfuzz": { - "baseline": "3.0.2", + "baseline": "3.0.5", + "port-version": 0 + }, + "rapidhash": { + "baseline": "2024-06-08", "port-version": 0 }, "rapidjson": { @@ -7594,22 +7770,22 @@ }, "raylib": { "baseline": "5.0", - "port-version": 0 + "port-version": 2 }, "rbdl": { "baseline": "3.3.0", - "port-version": 6 + "port-version": 7 }, "rbdl-orb": { "baseline": "3.2.0", "port-version": 2 }, "re2": { - "baseline": "2024-04-01", - "port-version": 2 + "baseline": "2024-07-02", + "port-version": 0 }, "reactiveplusplus": { - "baseline": "2.0.0", + "baseline": "2.1.1", "port-version": 0 }, "readerwriterqueue": { @@ -7636,9 +7812,13 @@ "baseline": "1.1.0a", "port-version": 4 }, + "realm-core": { + "baseline": "14.10.4", + "port-version": 0 + }, "realsense2": { "baseline": "2.54.2", - "port-version": 2 + "port-version": 3 }, "recast": { "baseline": "deprecated", @@ -7653,7 +7833,7 @@ "port-version": 0 }, "redis-plus-plus": { - "baseline": "1.3.12", + "baseline": "1.3.13", "port-version": 0 }, "refl-cpp": { @@ -7673,12 +7853,12 @@ "port-version": 1 }, "reproc": { - "baseline": "14.2.4", - "port-version": 2 + "baseline": "14.2.5", + "port-version": 0 }, "rest-rpc": { - "baseline": "0.07", - "port-version": 2 + "baseline": "0.12", + "port-version": 0 }, "restbed": { "baseline": "4.8", @@ -7686,7 +7866,7 @@ }, "restc-cpp": { "baseline": "0.10.0", - "port-version": 2 + "port-version": 4 }, "restclient-cpp": { "baseline": "2022-02-09", @@ -7701,8 +7881,8 @@ "port-version": 0 }, "rhash": { - "baseline": "1.4.4", - "port-version": 1 + "baseline": "1.4.5", + "port-version": 0 }, "rhasheq": { "baseline": "2023-06-17", @@ -7713,8 +7893,8 @@ "port-version": 3 }, "ring-span-lite": { - "baseline": "0.6.0", - "port-version": 1 + "baseline": "0.7.0", + "port-version": 0 }, "rioki-glow": { "baseline": "0.2.1", @@ -7725,19 +7905,19 @@ "port-version": 0 }, "rkcommon": { - "baseline": "1.10.0", + "baseline": "1.14.2", "port-version": 0 }, "rmlui": { - "baseline": "5.1", + "baseline": "6.0", "port-version": 1 }, "rmqcpp": { "baseline": "1.0.0", - "port-version": 0 + "port-version": 1 }, "roaring": { - "baseline": "3.0.1", + "baseline": "4.2.1", "port-version": 0 }, "robin-hood-hashing": { @@ -7745,19 +7925,19 @@ "port-version": 0 }, "robin-map": { - "baseline": "1.2.2", + "baseline": "1.3.0", "port-version": 0 }, "robotraconteur": { - "baseline": "1.2.0", - "port-version": 1 + "baseline": "1.2.3", + "port-version": 0 }, "robotraconteur-companion": { - "baseline": "0.3.1", + "baseline": "0.4.1", "port-version": 0 }, "rocksdb": { - "baseline": "9.1.0", + "baseline": "9.7.2", "port-version": 0 }, "rpclib": { @@ -7790,7 +7970,7 @@ }, "rsocket": { "baseline": "2021.08.30.00", - "port-version": 4 + "port-version": 5 }, "rtabmap": { "baseline": "0.21.4.1", @@ -7824,12 +8004,16 @@ "baseline": "3.3.0", "port-version": 1 }, + "ruckig": { + "baseline": "0.14.0", + "port-version": 0 + }, "rxcpp": { "baseline": "4.1.1", "port-version": 1 }, "rxqt": { - "baseline": "bb2138c", + "baseline": "d0b1535", "port-version": 1 }, "rxspencer": { @@ -7838,7 +8022,7 @@ }, "ryml": { "baseline": "0.5.0", - "port-version": 0 + "port-version": 1 }, "ryu": { "baseline": "2.0", @@ -7849,7 +8033,7 @@ "port-version": 0 }, "s2n": { - "baseline": "1.4.8", + "baseline": "1.5.5", "port-version": 0 }, "safeint": { @@ -7857,8 +8041,8 @@ "port-version": 0 }, "sail": { - "baseline": "0.9.4", - "port-version": 1 + "baseline": "0.9.6", + "port-version": 0 }, "sajson": { "baseline": "2018-09-21", @@ -7878,11 +8062,11 @@ }, "sassc": { "baseline": "3.6.2", - "port-version": 0 + "port-version": 1 }, "saucer": { - "baseline": "2.1.0", - "port-version": 0 + "baseline": "2.3.0", + "port-version": 1 }, "sbp": { "baseline": "3.4.10", @@ -7893,8 +8077,8 @@ "port-version": 1 }, "scintilla": { - "baseline": "4.4.6", - "port-version": 3 + "baseline": "5.5.1", + "port-version": 0 }, "sciplot": { "baseline": "0.3.1", @@ -7905,12 +8089,12 @@ "port-version": 1 }, "sciter-js": { - "baseline": "5.0.3.0", + "baseline": "5.0.3.14", "port-version": 0 }, "scnlib": { - "baseline": "2.0.2", - "port-version": 2 + "baseline": "3.0.1", + "port-version": 0 }, "scope-guard": { "baseline": "1.1.0", @@ -7929,12 +8113,12 @@ "port-version": 3 }, "sdbus-cpp": { - "baseline": "1.5.0", + "baseline": "2.0.0", "port-version": 0 }, "sdformat10": { "baseline": "10.0.0", - "port-version": 4 + "port-version": 5 }, "sdformat13": { "baseline": "13.6.0", @@ -7942,11 +8126,11 @@ }, "sdformat6": { "baseline": "6.2.0", - "port-version": 7 + "port-version": 8 }, "sdformat9": { "baseline": "9.8.0", - "port-version": 2 + "port-version": 3 }, "sdl1": { "baseline": "1.2.15", @@ -7961,7 +8145,7 @@ "port-version": 6 }, "sdl2": { - "baseline": "2.30.1", + "baseline": "2.30.8", "port-version": 0 }, "sdl2-gfx": { @@ -7990,15 +8174,15 @@ }, "sdl2pp": { "baseline": "0.16.1", - "port-version": 8 + "port-version": 11 }, "seacas": { "baseline": "2022-11-22", - "port-version": 5 + "port-version": 7 }, "seal": { - "baseline": "4.1.1", - "port-version": 2 + "baseline": "4.1.2", + "port-version": 0 }, "seasocks": { "baseline": "1.4.6", @@ -8017,7 +8201,7 @@ "port-version": 0 }, "sentry-native": { - "baseline": "0.7.2", + "baseline": "0.7.10", "port-version": 0 }, "septag-dmon": { @@ -8040,6 +8224,10 @@ "baseline": "0.1.4.1", "port-version": 0 }, + "sese": { + "baseline": "2.3.0", + "port-version": 1 + }, "sf2cute": { "baseline": "0.2.0", "port-version": 4 @@ -8057,7 +8245,7 @@ "port-version": 0 }, "shader-slang": { - "baseline": "2024.1.12", + "baseline": "2024.11", "port-version": 0 }, "shaderc": { @@ -8069,15 +8257,15 @@ "port-version": 0 }, "shapelib": { - "baseline": "1.6.0", + "baseline": "1.6.1", "port-version": 0 }, "shiftmedia-libgcrypt": { "baseline": "1.10.3-1", - "port-version": 0 + "port-version": 1 }, "shiftmedia-libgnutls": { - "baseline": "3.7.6", + "baseline": "3.8.4", "port-version": 3 }, "shiftmedia-libgpg-error": { @@ -8089,8 +8277,8 @@ "port-version": 7 }, "shogun": { - "baseline": "6.1.4", - "port-version": 10 + "baseline": "2023-12-19", + "port-version": 1 }, "si": { "baseline": "2.5.1", @@ -8102,7 +8290,7 @@ }, "signalrclient": { "baseline": "1.0.0-beta1-9", - "port-version": 5 + "port-version": 6 }, "sigslot": { "baseline": "1.0.0", @@ -8114,22 +8302,22 @@ }, "simbody": { "baseline": "2023-01-10", - "port-version": 0 + "port-version": 1 }, "simd": { - "baseline": "5.3.128", - "port-version": 1 + "baseline": "6.1.142", + "port-version": 0 }, "simde": { "baseline": "0.8.2", "port-version": 0 }, "simdjson": { - "baseline": "3.8.0", + "baseline": "3.10.1", "port-version": 0 }, "simdutf": { - "baseline": "5.2.5", + "baseline": "5.6.0", "port-version": 0 }, "simonbrunel-qtpromise": { @@ -8149,7 +8337,7 @@ "port-version": 0 }, "simsimd": { - "baseline": "1.4.0", + "baseline": "5.4.4", "port-version": 0 }, "sjpeg": { @@ -8161,8 +8349,8 @@ "port-version": 0 }, "skia": { - "baseline": "124", - "port-version": 0 + "baseline": "129", + "port-version": 2 }, "skyr-url": { "baseline": "1.13.0", @@ -8178,34 +8366,34 @@ }, "slikenet": { "baseline": "2021-06-07", - "port-version": 2 + "port-version": 3 }, "sltbench": { "baseline": "2.4.0", "port-version": 3 }, + "small-gicp": { + "baseline": "1.0.0", + "port-version": 0 + }, "smf": { "baseline": "0.1.1", "port-version": 0 }, "smpeg2": { "baseline": "2.0.0", - "port-version": 10 + "port-version": 11 }, "snap7": { "baseline": "1.4.2", - "port-version": 1 + "port-version": 2 }, "snappy": { - "baseline": "1.1.10", + "baseline": "1.2.1", "port-version": 0 }, - "sndfile": { - "baseline": "0", - "port-version": 2 - }, "snitch": { - "baseline": "1.2.4", + "baseline": "1.2.5", "port-version": 0 }, "snowhouse": { @@ -8216,6 +8404,10 @@ "baseline": "1.6.1", "port-version": 0 }, + "soapysdr": { + "baseline": "0.8.1", + "port-version": 0 + }, "sobjectizer": { "baseline": "5.8.2", "port-version": 0 @@ -8230,7 +8422,7 @@ }, "sockpp": { "baseline": "1.0.0", - "port-version": 0 + "port-version": 1 }, "soem": { "baseline": "2023-06-09", @@ -8257,7 +8449,7 @@ "port-version": 2 }, "sophus": { - "baseline": "1.22.10", + "baseline": "1.24.6-r1", "port-version": 0 }, "soqt": { @@ -8278,7 +8470,7 @@ }, "spaceland": { "baseline": "7.8.2", - "port-version": 8 + "port-version": 9 }, "span-lite": { "baseline": "0.11.0", @@ -8328,6 +8520,10 @@ "baseline": "1.2.1", "port-version": 1 }, + "spglib": { + "baseline": "2.4.0", + "port-version": 0 + }, "spine-runtimes": { "baseline": "4.1.0", "port-version": 0 @@ -8337,19 +8533,19 @@ "port-version": 4 }, "spirv-cross": { - "baseline": "1.3.280.0", + "baseline": "1.3.296.0", "port-version": 0 }, "spirv-headers": { - "baseline": "1.3.280.0", + "baseline": "1.3.296.0", "port-version": 0 }, "spirv-reflect": { - "baseline": "1.3.280.0", - "port-version": 1 + "baseline": "1.3.296.0", + "port-version": 0 }, "spirv-tools": { - "baseline": "1.3.280.0", + "baseline": "1.3.296.0", "port-version": 0 }, "spout2": { @@ -8365,7 +8561,7 @@ "port-version": 3 }, "sqlcipher": { - "baseline": "4.5.6", + "baseline": "4.6.1", "port-version": 0 }, "sqlite-modern-cpp": { @@ -8374,19 +8570,19 @@ }, "sqlite-orm": { "baseline": "1.8.2", - "port-version": 1 + "port-version": 2 }, "sqlite3": { - "baseline": "3.45.3", + "baseline": "3.46.1", "port-version": 0 }, "sqlitecpp": { - "baseline": "3.3.1", + "baseline": "3.3.2", "port-version": 0 }, "sqlpp11": { "baseline": "0.64", - "port-version": 0 + "port-version": 2 }, "sqlpp11-connector-mysql": { "baseline": "0.61", @@ -8409,7 +8605,7 @@ "port-version": 0 }, "srpc": { - "baseline": "0.9.3", + "baseline": "0.10.1", "port-version": 0 }, "sse2neon": { @@ -8437,12 +8633,12 @@ "port-version": 3 }, "stb": { - "baseline": "2023-04-11", + "baseline": "2024-07-29", "port-version": 1 }, "stdexec": { - "baseline": "2023-09-06", - "port-version": 3 + "baseline": "2024-06-16", + "port-version": 2 }, "stduuid": { "baseline": "1.2.3", @@ -8454,11 +8650,15 @@ }, "stlab": { "baseline": "1.7.1", - "port-version": 1 + "port-version": 2 }, "stormlib": { - "baseline": "2019-05-10", - "port-version": 5 + "baseline": "9.26", + "port-version": 0 + }, + "str-view": { + "baseline": "0.5.0", + "port-version": 0 }, "strict-variant": { "baseline": "0.5", @@ -8469,7 +8669,7 @@ "port-version": 0 }, "string-view-lite": { - "baseline": "1.7.0", + "baseline": "1.8.0", "port-version": 1 }, "stringzilla": { @@ -8494,18 +8694,18 @@ }, "stxxl": { "baseline": "2018-11-15", - "port-version": 7 + "port-version": 8 }, "suitesparse": { "baseline": "5.8.0", "port-version": 2 }, "sundials": { - "baseline": "6.2.0", + "baseline": "7.1.1", "port-version": 0 }, "superlu": { - "baseline": "6.0.1", + "baseline": "7.0.0", "port-version": 0 }, "swenson-sort": { @@ -8514,7 +8714,7 @@ }, "symengine": { "baseline": "0.11.2", - "port-version": 0 + "port-version": 2 }, "systemc": { "baseline": "2.3.3", @@ -8534,7 +8734,7 @@ }, "taglib": { "baseline": "2.0", - "port-version": 1 + "port-version": 2 }, "talib": { "baseline": "0.4.0", @@ -8542,7 +8742,7 @@ }, "taocpp-json": { "baseline": "2020-09-14", - "port-version": 3 + "port-version": 4 }, "tap-windows6": { "baseline": "9.21.2-0e30f5c", @@ -8553,11 +8753,11 @@ "port-version": 0 }, "taskflow": { - "baseline": "3.6.0", + "baseline": "3.8.0", "port-version": 0 }, "tbb": { - "baseline": "2021.11.0", + "baseline": "2021.13.0", "port-version": 0 }, "tcb-span": { @@ -8566,7 +8766,7 @@ }, "tcl": { "baseline": "core-9-0-a1", - "port-version": 6 + "port-version": 8 }, "tclap": { "baseline": "1.2.5", @@ -8576,9 +8776,13 @@ "baseline": "1.0.3", "port-version": 0 }, + "tdscpp": { + "baseline": "20240707", + "port-version": 0 + }, "telnetpp": { - "baseline": "2.1.2", - "port-version": 3 + "baseline": "3.1.0", + "port-version": 0 }, "tensorflow": { "baseline": "2.10.0", @@ -8590,7 +8794,7 @@ }, "tensorflow-common": { "baseline": "2.10.0", - "port-version": 2 + "port-version": 3 }, "tensorpipe": { "baseline": "2022-03-16", @@ -8601,7 +8805,11 @@ "port-version": 0 }, "tesseract": { - "baseline": "5.3.4", + "baseline": "5.4.1", + "port-version": 1 + }, + "tevclient": { + "baseline": "2023-12-04", "port-version": 0 }, "tfhe": { @@ -8622,7 +8830,7 @@ }, "theia": { "baseline": "0.8", - "port-version": 10 + "port-version": 11 }, "think-cell-range": { "baseline": "2023.1", @@ -8637,7 +8845,7 @@ "port-version": 0 }, "thorvg": { - "baseline": "0.13.2", + "baseline": "0.14.10", "port-version": 0 }, "threadpool": { @@ -8646,15 +8854,15 @@ }, "thrift": { "baseline": "0.20.0", - "port-version": 0 + "port-version": 1 }, "tidy-html5": { "baseline": "5.8.0", - "port-version": 1 + "port-version": 2 }, "tiff": { - "baseline": "4.6.0", - "port-version": 4 + "baseline": "4.7.0", + "port-version": 0 }, "tinkerforge": { "baseline": "2.1.25", @@ -8693,7 +8901,7 @@ "port-version": 0 }, "tinyexif": { - "baseline": "2022-02-15", + "baseline": "2024-09-03", "port-version": 0 }, "tinyexpr": { @@ -8713,7 +8921,7 @@ "port-version": 0 }, "tinygltf": { - "baseline": "2.8.21", + "baseline": "2.9.3", "port-version": 0 }, "tinynpy": { @@ -8740,6 +8948,10 @@ "baseline": "1.1", "port-version": 6 }, + "tinytiff": { + "baseline": "4.0.1.0", + "port-version": 0 + }, "tinytoml": { "baseline": "20180219", "port-version": 3 @@ -8753,8 +8965,8 @@ "port-version": 10 }, "tinyxml2": { - "baseline": "9.0.0", - "port-version": 2 + "baseline": "10.0.0", + "port-version": 0 }, "tl-expected": { "baseline": "1.1.0", @@ -8790,10 +9002,14 @@ }, "tmxparser": { "baseline": "2019-10-14", + "port-version": 1 + }, + "tobias-loew-flags": { + "baseline": "2024-09-10", "port-version": 0 }, "toml11": { - "baseline": "3.8.1", + "baseline": "4.2.0", "port-version": 0 }, "tomlplusplus": { @@ -8805,15 +9021,15 @@ "port-version": 4 }, "tracy": { - "baseline": "0.10.0", - "port-version": 2 + "baseline": "0.11.1", + "port-version": 1 }, "transwarp": { "baseline": "2.2.3", "port-version": 0 }, "trantor": { - "baseline": "1.5.18", + "baseline": "1.5.21", "port-version": 0 }, "tre": { @@ -8838,14 +9054,14 @@ }, "triangle": { "baseline": "1.6", - "port-version": 3 + "port-version": 4 }, "triton": { "baseline": "2023-08-16", "port-version": 0 }, "trompeloeil": { - "baseline": "47", + "baseline": "48", "port-version": 0 }, "try-catcher": { @@ -8877,7 +9093,7 @@ "port-version": 3 }, "tvision": { - "baseline": "2024-02-28", + "baseline": "2024-05-22", "port-version": 0 }, "tweeny": { @@ -8885,11 +9101,11 @@ "port-version": 1 }, "type-lite": { - "baseline": "0.1.0", - "port-version": 3 + "baseline": "0.2.0", + "port-version": 0 }, "type-safe": { - "baseline": "0.2.3", + "baseline": "0.2.4", "port-version": 0 }, "uchardet": { @@ -8922,7 +9138,7 @@ }, "unittest-cpp": { "baseline": "2.0.0", - "port-version": 5 + "port-version": 6 }, "unixodbc": { "baseline": "2.3.11", @@ -8940,8 +9156,12 @@ "baseline": "7.0.7", "port-version": 0 }, + "upa-url": { + "baseline": "1.0.1", + "port-version": 0 + }, "upb": { - "baseline": "2022-06-21", + "baseline": "4.25.1", "port-version": 1 }, "urdfdom": { @@ -8952,12 +9172,8 @@ "baseline": "1.1.1", "port-version": 0 }, - "urho3d": { - "baseline": "2021-03-01", - "port-version": 5 - }, "uriparser": { - "baseline": "0.9.7", + "baseline": "0.9.8", "port-version": 0 }, "usbmuxd": { @@ -8978,10 +9194,10 @@ }, "usrsctp": { "baseline": "0.9.5.0", - "port-version": 3 + "port-version": 4 }, "utf8-range": { - "baseline": "2023-11-09", + "baseline": "4.25.1", "port-version": 0 }, "utf8h": { @@ -9006,10 +9222,10 @@ }, "uthenticode": { "baseline": "2.0.1", - "port-version": 0 + "port-version": 1 }, "uvatlas": { - "baseline": "2024-02-21", + "baseline": "2024-09-04", "port-version": 0 }, "uvw": { @@ -9017,7 +9233,7 @@ "port-version": 0 }, "uwebsockets": { - "baseline": "20.62.0", + "baseline": "20.67.0", "port-version": 0 }, "v-hacd": { @@ -9026,7 +9242,7 @@ }, "v8": { "baseline": "9.1.269.39", - "port-version": 7 + "port-version": 8 }, "valijson": { "baseline": "1.0.2", @@ -9049,27 +9265,31 @@ "port-version": 0 }, "vcglib": { - "baseline": "2022.02", + "baseline": "2023.12", "port-version": 0 }, "vcpkg-boost": { - "baseline": "2024-04-25", + "baseline": "2024-05-15", "port-version": 0 }, "vcpkg-cmake": { - "baseline": "2024-04-18", + "baseline": "2024-04-23", "port-version": 0 }, "vcpkg-cmake-config": { - "baseline": "2022-02-06", - "port-version": 1 + "baseline": "2024-05-23", + "port-version": 0 }, "vcpkg-cmake-get-vars": { - "baseline": "2023-12-31", + "baseline": "2024-09-22", + "port-version": 0 + }, + "vcpkg-get-python": { + "baseline": "2024-06-22", "port-version": 0 }, "vcpkg-get-python-packages": { - "baseline": "2024-01-24", + "baseline": "2024-09-29", "port-version": 0 }, "vcpkg-gfortran": { @@ -9090,7 +9310,7 @@ }, "vcpkg-qmake": { "baseline": "2023-03-22", - "port-version": 1 + "port-version": 3 }, "vcpkg-tool-bazel": { "baseline": "5.2.0", @@ -9109,8 +9329,8 @@ "port-version": 1 }, "vcpkg-tool-meson": { - "baseline": "1.3.2", - "port-version": 2 + "baseline": "1.5.2", + "port-version": 0 }, "vcpkg-tool-mozbuild": { "baseline": "4.0.2", @@ -9133,7 +9353,7 @@ "port-version": 0 }, "veigar": { - "baseline": "1.0", + "baseline": "1.2", "port-version": 0 }, "velodyne-decoder": { @@ -9144,12 +9364,16 @@ "baseline": "1.4.0", "port-version": 0 }, + "via-httplib": { + "baseline": "1.9.0", + "port-version": 0 + }, "vili": { "baseline": "1.0.0+20221123", "port-version": 1 }, "vincentlaucsb-csv-parser": { - "baseline": "2.1.3", + "baseline": "2.2.3", "port-version": 0 }, "visit-struct": { @@ -9169,35 +9393,35 @@ "port-version": 0 }, "vladimirshaleev-ipaddress": { - "baseline": "1.0.1", + "baseline": "1.1.0", "port-version": 0 }, "vlfeat": { "baseline": "2020-07-10", - "port-version": 3 + "port-version": 4 }, "vlpp": { - "baseline": "1.1.0.0", - "port-version": 1 + "baseline": "1.2.9.0", + "port-version": 0 }, "volk": { - "baseline": "1.3.280.0", + "baseline": "1.3.296", "port-version": 0 }, "vowpal-wabbit": { - "baseline": "9.8.0", - "port-version": 2 + "baseline": "9.10.0", + "port-version": 0 }, "vs-yasm": { "baseline": "0.5.0", "port-version": 2 }, "vsg": { - "baseline": "1.1.2", + "baseline": "1.1.7", "port-version": 0 }, "vsgimgui": { - "baseline": "0.1.0", + "baseline": "0.3.0", "port-version": 0 }, "vsgxchange": { @@ -9205,12 +9429,12 @@ "port-version": 1 }, "vtk": { - "baseline": "9.3.0-pv5.12.0", - "port-version": 3 + "baseline": "9.3.0-pv5.12.1", + "port-version": 5 }, "vtk-dicom": { - "baseline": "0.8.14", - "port-version": 2 + "baseline": "0.8.16", + "port-version": 0 }, "vtk-m": { "baseline": "2.1.0", @@ -9221,7 +9445,7 @@ "port-version": 0 }, "vulkan-headers": { - "baseline": "1.3.280.0", + "baseline": "1.3.296.0", "port-version": 0 }, "vulkan-hpp": { @@ -9229,31 +9453,31 @@ "port-version": 0 }, "vulkan-loader": { - "baseline": "1.3.280.0", + "baseline": "1.3.296.0", "port-version": 0 }, "vulkan-memory-allocator": { - "baseline": "3.0.1", - "port-version": 4 + "baseline": "3.1.0", + "port-version": 0 }, "vulkan-memory-allocator-hpp": { - "baseline": "3.0.1.1", - "port-version": 1 + "baseline": "3.1.0", + "port-version": 0 }, "vulkan-sdk-components": { - "baseline": "1.3.280.0", + "baseline": "1.3.296.0", "port-version": 0 }, "vulkan-tools": { - "baseline": "1.3.280.0", + "baseline": "1.3.296.0", "port-version": 0 }, "vulkan-utility-libraries": { - "baseline": "1.3.280.0", + "baseline": "1.3.296.0", "port-version": 0 }, "vulkan-validationlayers": { - "baseline": "1.3.280.0", + "baseline": "1.3.296.0", "port-version": 0 }, "vvenc": { @@ -9261,28 +9485,32 @@ "port-version": 0 }, "vxl": { - "baseline": "2.0.2", - "port-version": 6 + "baseline": "3.5.0", + "port-version": 0 + }, + "wabt": { + "baseline": "1.0.36", + "port-version": 0 }, "wampcc": { "baseline": "2019-09-04", "port-version": 5 }, "wangle": { - "baseline": "2024.05.06.00", + "baseline": "2024.10.07.00", "port-version": 0 }, "wasmedge": { "baseline": "0.13.5", - "port-version": 1 + "port-version": 2 }, "wavelib": { "baseline": "2021-11-26", "port-version": 0 }, "wavpack": { - "baseline": "5.6.0", - "port-version": 1 + "baseline": "5.7.0", + "port-version": 0 }, "wayland": { "baseline": "1.21.0", @@ -9310,14 +9538,14 @@ }, "wg21-linear-algebra": { "baseline": "0.7.3", - "port-version": 0 + "port-version": 1 }, "wg21-sg14": { "baseline": "2019-08-13", "port-version": 2 }, "wil": { - "baseline": "2024-01-22", + "baseline": "1.0.240803.1", "port-version": 0 }, "wildmidi": { @@ -9341,7 +9569,7 @@ "port-version": 0 }, "winreg": { - "baseline": "6.2.0", + "baseline": "6.3.0", "port-version": 0 }, "winsock2": { @@ -9357,7 +9585,7 @@ "port-version": 0 }, "wmipp": { - "baseline": "1.2.0", + "baseline": "1.3.0", "port-version": 0 }, "woff2": { @@ -9365,16 +9593,16 @@ "port-version": 4 }, "wolfmqtt": { - "baseline": "1.16.0", + "baseline": "1.19.0", "port-version": 0 }, "wolfssl": { - "baseline": "5.7.0", - "port-version": 0 + "baseline": "5.7.2", + "port-version": 3 }, "wolftpm": { - "baseline": "2.7.0", - "port-version": 2 + "baseline": "3.4.0", + "port-version": 0 }, "wordnet": { "baseline": "3.0", @@ -9386,7 +9614,7 @@ }, "wpilib": { "baseline": "2023-08-24", - "port-version": 0 + "port-version": 1 }, "wren": { "baseline": "0.4.0", @@ -9409,8 +9637,8 @@ "port-version": 0 }, "wxwidgets": { - "baseline": "3.2.4", - "port-version": 2 + "baseline": "3.2.6", + "port-version": 0 }, "wyhash": { "baseline": "2023-12-03", @@ -9421,15 +9649,15 @@ "port-version": 0 }, "x264": { - "baseline": "0.164.3107", + "baseline": "0.164.3108", "port-version": 0 }, "x265": { - "baseline": "3.5", + "baseline": "3.6", "port-version": 0 }, "x86-simd-sort": { - "baseline": "4.0", + "baseline": "5.0", "port-version": 0 }, "xapian": { @@ -9438,19 +9666,19 @@ }, "xaudio2redist": { "baseline": "1.2.11", - "port-version": 2 + "port-version": 4 }, "xbitmaps": { "baseline": "1.1.2", "port-version": 0 }, "xbyak": { - "baseline": "6.73", + "baseline": "7.7", "port-version": 0 }, "xcb": { "baseline": "1.14", - "port-version": 1 + "port-version": 2 }, "xcb-image": { "baseline": "0.4.1", @@ -9505,7 +9733,7 @@ "port-version": 0 }, "xmlsec": { - "baseline": "1.3.3", + "baseline": "1.3.5", "port-version": 0 }, "xnnpack": { @@ -9514,7 +9742,7 @@ }, "xorg-macros": { "baseline": "1.19.3", - "port-version": 0 + "port-version": 1 }, "xorstr": { "baseline": "2021-11-20", @@ -9534,11 +9762,11 @@ }, "xqilla": { "baseline": "2.3.4", - "port-version": 2 + "port-version": 3 }, "xsimd": { - "baseline": "12.1.1", - "port-version": 0 + "baseline": "13.0.0", + "port-version": 1 }, "xtensor": { "baseline": "0.25.0", @@ -9562,7 +9790,7 @@ }, "xtrans": { "baseline": "1.4.0", - "port-version": 1 + "port-version": 2 }, "xxhash": { "baseline": "0.8.2", @@ -9577,8 +9805,8 @@ "port-version": 1 }, "yara": { - "baseline": "4.5.0", - "port-version": 0 + "baseline": "4.5.2", + "port-version": 1 }, "yas": { "baseline": "7.1.0", @@ -9601,23 +9829,23 @@ "port-version": 0 }, "yoga": { - "baseline": "3.0.2", + "baseline": "3.1.0", "port-version": 0 }, "yomm2": { - "baseline": "1.5.1", + "baseline": "1.5.2", "port-version": 0 }, "yyjson": { - "baseline": "0.8.0", + "baseline": "0.10.0", "port-version": 0 }, "z3": { - "baseline": "4.13.0", + "baseline": "4.13.3", "port-version": 0 }, "z4kn4fein-semver": { - "baseline": "0.2.1", + "baseline": "0.4.0", "port-version": 0 }, "z85": { @@ -9630,14 +9858,14 @@ }, "zeromq": { "baseline": "4.3.5", - "port-version": 1 + "port-version": 2 }, "zfp": { "baseline": "1.0.0", - "port-version": 1 + "port-version": 2 }, "zimpl": { - "baseline": "3.5.3", + "baseline": "3.6.1", "port-version": 0 }, "zint": { @@ -9654,14 +9882,14 @@ }, "zlib-ng": { "baseline": "2.1.5", - "port-version": 0 + "port-version": 1 }, "zlmediakit": { - "baseline": "2024-03-30", - "port-version": 1 + "baseline": "2024-09-29", + "port-version": 0 }, "zoe": { - "baseline": "3.0", + "baseline": "3.2", "port-version": 0 }, "zookeeper": { @@ -9670,7 +9898,7 @@ }, "zopfli": { "baseline": "1.0.3", - "port-version": 3 + "port-version": 4 }, "zpp-bits": { "baseline": "4.4.17", @@ -9690,7 +9918,7 @@ }, "ztd-cuneicode": { "baseline": "2023-11-03", - "port-version": 0 + "port-version": 1 }, "ztd-encoding-tables": { "baseline": "2023-06-10", @@ -9710,26 +9938,26 @@ }, "ztd-text": { "baseline": "2023-11-03", - "port-version": 0 + "port-version": 1 }, "zug": { - "baseline": "2021-04-23", - "port-version": 1 + "baseline": "2024-04-26", + "port-version": 0 }, "zycore": { - "baseline": "1.3.0", - "port-version": 1 + "baseline": "1.5.0", + "port-version": 0 }, "zydis": { - "baseline": "4.0.0", - "port-version": 2 + "baseline": "4.1.0", + "port-version": 0 }, "zyre": { - "baseline": "2019-07-07", - "port-version": 5 + "baseline": "2024-04-10", + "port-version": 0 }, "zziplib": { - "baseline": "0.13.73", + "baseline": "0.13.78", "port-version": 0 } } diff --git a/vcpkg/versions/d-/dpp.json b/vcpkg/versions/d-/dpp.json index d5b64f737f..11230705c7 100644 --- a/vcpkg/versions/d-/dpp.json +++ b/vcpkg/versions/d-/dpp.json @@ -1,5 +1,15 @@ { "versions": [ + { + "git-tree": "58fffa3b78cf2a0c2e28b5abdd40931a4b0a5c40", + "version": "10.0.33", + "port-version": 0 + }, + { + "git-tree": "190a206eddf272472a4668d756e0293096341f97", + "version": "10.0.31", + "port-version": 0 + }, { "git-tree": "2224384b8c94dc8993bee072c9f506ef17e6eef4", "version": "10.0.30", diff --git a/win32/32/bin/libsodium.dll b/win32/32/bin/libsodium.dll deleted file mode 100755 index 09d2e51792..0000000000 Binary files a/win32/32/bin/libsodium.dll and /dev/null differ diff --git a/win32/bin/libsodium.dll b/win32/bin/libsodium.dll deleted file mode 100644 index 0fc90b1231..0000000000 Binary files a/win32/bin/libsodium.dll and /dev/null differ diff --git a/win32/include/sodium.h b/win32/include/sodium.h deleted file mode 100644 index 295f911cff..0000000000 --- a/win32/include/sodium.h +++ /dev/null @@ -1,69 +0,0 @@ - -#ifndef sodium_H -#define sodium_H - -#include "sodium/version.h" - -#include "sodium/core.h" -#include "sodium/crypto_aead_aes256gcm.h" -#include "sodium/crypto_aead_chacha20poly1305.h" -#include "sodium/crypto_aead_xchacha20poly1305.h" -#include "sodium/crypto_auth.h" -#include "sodium/crypto_auth_hmacsha256.h" -#include "sodium/crypto_auth_hmacsha512.h" -#include "sodium/crypto_auth_hmacsha512256.h" -#include "sodium/crypto_box.h" -#include "sodium/crypto_box_curve25519xsalsa20poly1305.h" -#include "sodium/crypto_core_hsalsa20.h" -#include "sodium/crypto_core_hchacha20.h" -#include "sodium/crypto_core_salsa20.h" -#include "sodium/crypto_core_salsa2012.h" -#include "sodium/crypto_core_salsa208.h" -#include "sodium/crypto_generichash.h" -#include "sodium/crypto_generichash_blake2b.h" -#include "sodium/crypto_hash.h" -#include "sodium/crypto_hash_sha256.h" -#include "sodium/crypto_hash_sha512.h" -#include "sodium/crypto_kdf.h" -#include "sodium/crypto_kdf_blake2b.h" -#include "sodium/crypto_kx.h" -#include "sodium/crypto_onetimeauth.h" -#include "sodium/crypto_onetimeauth_poly1305.h" -#include "sodium/crypto_pwhash.h" -#include "sodium/crypto_pwhash_argon2i.h" -#include "sodium/crypto_scalarmult.h" -#include "sodium/crypto_scalarmult_curve25519.h" -#include "sodium/crypto_secretbox.h" -#include "sodium/crypto_secretbox_xsalsa20poly1305.h" -#include "sodium/crypto_secretstream_xchacha20poly1305.h" -#include "sodium/crypto_shorthash.h" -#include "sodium/crypto_shorthash_siphash24.h" -#include "sodium/crypto_sign.h" -#include "sodium/crypto_sign_ed25519.h" -#include "sodium/crypto_stream.h" -#include "sodium/crypto_stream_chacha20.h" -#include "sodium/crypto_stream_salsa20.h" -#include "sodium/crypto_stream_xsalsa20.h" -#include "sodium/crypto_verify_16.h" -#include "sodium/crypto_verify_32.h" -#include "sodium/crypto_verify_64.h" -#include "sodium/randombytes.h" -#include "sodium/randombytes_internal_random.h" -#include "sodium/randombytes_sysrandom.h" -#include "sodium/runtime.h" -#include "sodium/utils.h" - -#ifndef SODIUM_LIBRARY_MINIMAL -# include "sodium/crypto_box_curve25519xchacha20poly1305.h" -# include "sodium/crypto_core_ed25519.h" -# include "sodium/crypto_core_ristretto255.h" -# include "sodium/crypto_scalarmult_ed25519.h" -# include "sodium/crypto_scalarmult_ristretto255.h" -# include "sodium/crypto_secretbox_xchacha20poly1305.h" -# include "sodium/crypto_pwhash_scryptsalsa208sha256.h" -# include "sodium/crypto_stream_salsa2012.h" -# include "sodium/crypto_stream_salsa208.h" -# include "sodium/crypto_stream_xchacha20.h" -#endif - -#endif diff --git a/win32/include/sodium/core.h b/win32/include/sodium/core.h deleted file mode 100644 index dd088d2cae..0000000000 --- a/win32/include/sodium/core.h +++ /dev/null @@ -1,28 +0,0 @@ - -#ifndef sodium_core_H -#define sodium_core_H - -#include "export.h" - -#ifdef __cplusplus -extern "C" { -#endif - -SODIUM_EXPORT -int sodium_init(void) - __attribute__ ((warn_unused_result)); - -/* ---- */ - -SODIUM_EXPORT -int sodium_set_misuse_handler(void (*handler)(void)); - -SODIUM_EXPORT -void sodium_misuse(void) - __attribute__ ((noreturn)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_aead_aes256gcm.h b/win32/include/sodium/crypto_aead_aes256gcm.h deleted file mode 100644 index 9baeb3f19f..0000000000 --- a/win32/include/sodium/crypto_aead_aes256gcm.h +++ /dev/null @@ -1,179 +0,0 @@ -#ifndef crypto_aead_aes256gcm_H -#define crypto_aead_aes256gcm_H - -/* - * WARNING: Despite being the most popular AEAD construction due to its - * use in TLS, safely using AES-GCM in a different context is tricky. - * - * No more than ~ 350 GB of input data should be encrypted with a given key. - * This is for ~ 16 KB messages -- Actual figures vary according to - * message sizes. - * - * In addition, nonces are short and repeated nonces would totally destroy - * the security of this scheme. - * - * Nonces should thus come from atomic counters, which can be difficult to - * set up in a distributed environment. - * - * Unless you absolutely need AES-GCM, use crypto_aead_xchacha20poly1305_ietf_*() - * instead. It doesn't have any of these limitations. - * Or, if you don't need to authenticate additional data, just stick to - * crypto_secretbox(). - */ - -#include -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -SODIUM_EXPORT -int crypto_aead_aes256gcm_is_available(void); - -#define crypto_aead_aes256gcm_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_aead_aes256gcm_keybytes(void); - -#define crypto_aead_aes256gcm_NSECBYTES 0U -SODIUM_EXPORT -size_t crypto_aead_aes256gcm_nsecbytes(void); - -#define crypto_aead_aes256gcm_NPUBBYTES 12U -SODIUM_EXPORT -size_t crypto_aead_aes256gcm_npubbytes(void); - -#define crypto_aead_aes256gcm_ABYTES 16U -SODIUM_EXPORT -size_t crypto_aead_aes256gcm_abytes(void); - -#define crypto_aead_aes256gcm_MESSAGEBYTES_MAX \ - SODIUM_MIN(SODIUM_SIZE_MAX - crypto_aead_aes256gcm_ABYTES, \ - (16ULL * ((1ULL << 32) - 2ULL))) -SODIUM_EXPORT -size_t crypto_aead_aes256gcm_messagebytes_max(void); - -typedef struct CRYPTO_ALIGN(16) crypto_aead_aes256gcm_state_ { - unsigned char opaque[512]; -} crypto_aead_aes256gcm_state; - -SODIUM_EXPORT -size_t crypto_aead_aes256gcm_statebytes(void); - -SODIUM_EXPORT -int crypto_aead_aes256gcm_encrypt(unsigned char *c, - unsigned long long *clen_p, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *ad, - unsigned long long adlen, - const unsigned char *nsec, - const unsigned char *npub, - const unsigned char *k) - __attribute__ ((nonnull(1, 8, 9))); - -SODIUM_EXPORT -int crypto_aead_aes256gcm_decrypt(unsigned char *m, - unsigned long long *mlen_p, - unsigned char *nsec, - const unsigned char *c, - unsigned long long clen, - const unsigned char *ad, - unsigned long long adlen, - const unsigned char *npub, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(4, 8, 9))); - -SODIUM_EXPORT -int crypto_aead_aes256gcm_encrypt_detached(unsigned char *c, - unsigned char *mac, - unsigned long long *maclen_p, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *ad, - unsigned long long adlen, - const unsigned char *nsec, - const unsigned char *npub, - const unsigned char *k) - __attribute__ ((nonnull(1, 2, 9, 10))); - -SODIUM_EXPORT -int crypto_aead_aes256gcm_decrypt_detached(unsigned char *m, - unsigned char *nsec, - const unsigned char *c, - unsigned long long clen, - const unsigned char *mac, - const unsigned char *ad, - unsigned long long adlen, - const unsigned char *npub, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(3, 5, 8, 9))); - -/* -- Precomputation interface -- */ - -SODIUM_EXPORT -int crypto_aead_aes256gcm_beforenm(crypto_aead_aes256gcm_state *ctx_, - const unsigned char *k) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_aead_aes256gcm_encrypt_afternm(unsigned char *c, - unsigned long long *clen_p, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *ad, - unsigned long long adlen, - const unsigned char *nsec, - const unsigned char *npub, - const crypto_aead_aes256gcm_state *ctx_) - __attribute__ ((nonnull(1, 8, 9))); - -SODIUM_EXPORT -int crypto_aead_aes256gcm_decrypt_afternm(unsigned char *m, - unsigned long long *mlen_p, - unsigned char *nsec, - const unsigned char *c, - unsigned long long clen, - const unsigned char *ad, - unsigned long long adlen, - const unsigned char *npub, - const crypto_aead_aes256gcm_state *ctx_) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(4, 8, 9))); - -SODIUM_EXPORT -int crypto_aead_aes256gcm_encrypt_detached_afternm(unsigned char *c, - unsigned char *mac, - unsigned long long *maclen_p, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *ad, - unsigned long long adlen, - const unsigned char *nsec, - const unsigned char *npub, - const crypto_aead_aes256gcm_state *ctx_) - __attribute__ ((nonnull(1, 2, 9, 10))); - -SODIUM_EXPORT -int crypto_aead_aes256gcm_decrypt_detached_afternm(unsigned char *m, - unsigned char *nsec, - const unsigned char *c, - unsigned long long clen, - const unsigned char *mac, - const unsigned char *ad, - unsigned long long adlen, - const unsigned char *npub, - const crypto_aead_aes256gcm_state *ctx_) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(3, 5, 8, 9))); - -SODIUM_EXPORT -void crypto_aead_aes256gcm_keygen(unsigned char k[crypto_aead_aes256gcm_KEYBYTES]) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_aead_chacha20poly1305.h b/win32/include/sodium/crypto_aead_chacha20poly1305.h deleted file mode 100644 index 5d671df142..0000000000 --- a/win32/include/sodium/crypto_aead_chacha20poly1305.h +++ /dev/null @@ -1,180 +0,0 @@ -#ifndef crypto_aead_chacha20poly1305_H -#define crypto_aead_chacha20poly1305_H - -#include -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -/* -- IETF ChaCha20-Poly1305 construction with a 96-bit nonce and a 32-bit internal counter -- */ - -#define crypto_aead_chacha20poly1305_ietf_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_aead_chacha20poly1305_ietf_keybytes(void); - -#define crypto_aead_chacha20poly1305_ietf_NSECBYTES 0U -SODIUM_EXPORT -size_t crypto_aead_chacha20poly1305_ietf_nsecbytes(void); - -#define crypto_aead_chacha20poly1305_ietf_NPUBBYTES 12U - -SODIUM_EXPORT -size_t crypto_aead_chacha20poly1305_ietf_npubbytes(void); - -#define crypto_aead_chacha20poly1305_ietf_ABYTES 16U -SODIUM_EXPORT -size_t crypto_aead_chacha20poly1305_ietf_abytes(void); - -#define crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX \ - SODIUM_MIN(SODIUM_SIZE_MAX - crypto_aead_chacha20poly1305_ietf_ABYTES, \ - (64ULL * ((1ULL << 32) - 1ULL))) -SODIUM_EXPORT -size_t crypto_aead_chacha20poly1305_ietf_messagebytes_max(void); - -SODIUM_EXPORT -int crypto_aead_chacha20poly1305_ietf_encrypt(unsigned char *c, - unsigned long long *clen_p, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *ad, - unsigned long long adlen, - const unsigned char *nsec, - const unsigned char *npub, - const unsigned char *k) - __attribute__ ((nonnull(1, 8, 9))); - -SODIUM_EXPORT -int crypto_aead_chacha20poly1305_ietf_decrypt(unsigned char *m, - unsigned long long *mlen_p, - unsigned char *nsec, - const unsigned char *c, - unsigned long long clen, - const unsigned char *ad, - unsigned long long adlen, - const unsigned char *npub, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(4, 8, 9))); - -SODIUM_EXPORT -int crypto_aead_chacha20poly1305_ietf_encrypt_detached(unsigned char *c, - unsigned char *mac, - unsigned long long *maclen_p, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *ad, - unsigned long long adlen, - const unsigned char *nsec, - const unsigned char *npub, - const unsigned char *k) - __attribute__ ((nonnull(1, 2, 9, 10))); - -SODIUM_EXPORT -int crypto_aead_chacha20poly1305_ietf_decrypt_detached(unsigned char *m, - unsigned char *nsec, - const unsigned char *c, - unsigned long long clen, - const unsigned char *mac, - const unsigned char *ad, - unsigned long long adlen, - const unsigned char *npub, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(3, 5, 8, 9))); - -SODIUM_EXPORT -void crypto_aead_chacha20poly1305_ietf_keygen(unsigned char k[crypto_aead_chacha20poly1305_ietf_KEYBYTES]) - __attribute__ ((nonnull)); - -/* -- Original ChaCha20-Poly1305 construction with a 64-bit nonce and a 64-bit internal counter -- */ - -#define crypto_aead_chacha20poly1305_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_aead_chacha20poly1305_keybytes(void); - -#define crypto_aead_chacha20poly1305_NSECBYTES 0U -SODIUM_EXPORT -size_t crypto_aead_chacha20poly1305_nsecbytes(void); - -#define crypto_aead_chacha20poly1305_NPUBBYTES 8U -SODIUM_EXPORT -size_t crypto_aead_chacha20poly1305_npubbytes(void); - -#define crypto_aead_chacha20poly1305_ABYTES 16U -SODIUM_EXPORT -size_t crypto_aead_chacha20poly1305_abytes(void); - -#define crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX \ - (SODIUM_SIZE_MAX - crypto_aead_chacha20poly1305_ABYTES) -SODIUM_EXPORT -size_t crypto_aead_chacha20poly1305_messagebytes_max(void); - -SODIUM_EXPORT -int crypto_aead_chacha20poly1305_encrypt(unsigned char *c, - unsigned long long *clen_p, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *ad, - unsigned long long adlen, - const unsigned char *nsec, - const unsigned char *npub, - const unsigned char *k) - __attribute__ ((nonnull(1, 8, 9))); - -SODIUM_EXPORT -int crypto_aead_chacha20poly1305_decrypt(unsigned char *m, - unsigned long long *mlen_p, - unsigned char *nsec, - const unsigned char *c, - unsigned long long clen, - const unsigned char *ad, - unsigned long long adlen, - const unsigned char *npub, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(4, 8, 9))); - -SODIUM_EXPORT -int crypto_aead_chacha20poly1305_encrypt_detached(unsigned char *c, - unsigned char *mac, - unsigned long long *maclen_p, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *ad, - unsigned long long adlen, - const unsigned char *nsec, - const unsigned char *npub, - const unsigned char *k) - __attribute__ ((nonnull(1, 2, 9, 10))); - -SODIUM_EXPORT -int crypto_aead_chacha20poly1305_decrypt_detached(unsigned char *m, - unsigned char *nsec, - const unsigned char *c, - unsigned long long clen, - const unsigned char *mac, - const unsigned char *ad, - unsigned long long adlen, - const unsigned char *npub, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(3, 5, 8, 9))); - -SODIUM_EXPORT -void crypto_aead_chacha20poly1305_keygen(unsigned char k[crypto_aead_chacha20poly1305_KEYBYTES]) - __attribute__ ((nonnull)); - -/* Aliases */ - -#define crypto_aead_chacha20poly1305_IETF_KEYBYTES crypto_aead_chacha20poly1305_ietf_KEYBYTES -#define crypto_aead_chacha20poly1305_IETF_NSECBYTES crypto_aead_chacha20poly1305_ietf_NSECBYTES -#define crypto_aead_chacha20poly1305_IETF_NPUBBYTES crypto_aead_chacha20poly1305_ietf_NPUBBYTES -#define crypto_aead_chacha20poly1305_IETF_ABYTES crypto_aead_chacha20poly1305_ietf_ABYTES -#define crypto_aead_chacha20poly1305_IETF_MESSAGEBYTES_MAX crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_aead_xchacha20poly1305.h b/win32/include/sodium/crypto_aead_xchacha20poly1305.h deleted file mode 100644 index 6643b0cbf5..0000000000 --- a/win32/include/sodium/crypto_aead_xchacha20poly1305.h +++ /dev/null @@ -1,100 +0,0 @@ -#ifndef crypto_aead_xchacha20poly1305_H -#define crypto_aead_xchacha20poly1305_H - -#include -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_aead_xchacha20poly1305_ietf_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_aead_xchacha20poly1305_ietf_keybytes(void); - -#define crypto_aead_xchacha20poly1305_ietf_NSECBYTES 0U -SODIUM_EXPORT -size_t crypto_aead_xchacha20poly1305_ietf_nsecbytes(void); - -#define crypto_aead_xchacha20poly1305_ietf_NPUBBYTES 24U -SODIUM_EXPORT -size_t crypto_aead_xchacha20poly1305_ietf_npubbytes(void); - -#define crypto_aead_xchacha20poly1305_ietf_ABYTES 16U -SODIUM_EXPORT -size_t crypto_aead_xchacha20poly1305_ietf_abytes(void); - -#define crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX \ - (SODIUM_SIZE_MAX - crypto_aead_xchacha20poly1305_ietf_ABYTES) -SODIUM_EXPORT -size_t crypto_aead_xchacha20poly1305_ietf_messagebytes_max(void); - -SODIUM_EXPORT -int crypto_aead_xchacha20poly1305_ietf_encrypt(unsigned char *c, - unsigned long long *clen_p, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *ad, - unsigned long long adlen, - const unsigned char *nsec, - const unsigned char *npub, - const unsigned char *k) - __attribute__ ((nonnull(1, 8, 9))); - -SODIUM_EXPORT -int crypto_aead_xchacha20poly1305_ietf_decrypt(unsigned char *m, - unsigned long long *mlen_p, - unsigned char *nsec, - const unsigned char *c, - unsigned long long clen, - const unsigned char *ad, - unsigned long long adlen, - const unsigned char *npub, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(4, 8, 9))); - -SODIUM_EXPORT -int crypto_aead_xchacha20poly1305_ietf_encrypt_detached(unsigned char *c, - unsigned char *mac, - unsigned long long *maclen_p, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *ad, - unsigned long long adlen, - const unsigned char *nsec, - const unsigned char *npub, - const unsigned char *k) - __attribute__ ((nonnull(1, 2, 9, 10))); - -SODIUM_EXPORT -int crypto_aead_xchacha20poly1305_ietf_decrypt_detached(unsigned char *m, - unsigned char *nsec, - const unsigned char *c, - unsigned long long clen, - const unsigned char *mac, - const unsigned char *ad, - unsigned long long adlen, - const unsigned char *npub, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(3, 5, 8, 9))); - -SODIUM_EXPORT -void crypto_aead_xchacha20poly1305_ietf_keygen(unsigned char k[crypto_aead_xchacha20poly1305_ietf_KEYBYTES]) - __attribute__ ((nonnull)); - -/* Aliases */ - -#define crypto_aead_xchacha20poly1305_IETF_KEYBYTES crypto_aead_xchacha20poly1305_ietf_KEYBYTES -#define crypto_aead_xchacha20poly1305_IETF_NSECBYTES crypto_aead_xchacha20poly1305_ietf_NSECBYTES -#define crypto_aead_xchacha20poly1305_IETF_NPUBBYTES crypto_aead_xchacha20poly1305_ietf_NPUBBYTES -#define crypto_aead_xchacha20poly1305_IETF_ABYTES crypto_aead_xchacha20poly1305_ietf_ABYTES -#define crypto_aead_xchacha20poly1305_IETF_MESSAGEBYTES_MAX crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_auth.h b/win32/include/sodium/crypto_auth.h deleted file mode 100644 index 540aee0e8d..0000000000 --- a/win32/include/sodium/crypto_auth.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef crypto_auth_H -#define crypto_auth_H - -#include - -#include "crypto_auth_hmacsha512256.h" -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_auth_BYTES crypto_auth_hmacsha512256_BYTES -SODIUM_EXPORT -size_t crypto_auth_bytes(void); - -#define crypto_auth_KEYBYTES crypto_auth_hmacsha512256_KEYBYTES -SODIUM_EXPORT -size_t crypto_auth_keybytes(void); - -#define crypto_auth_PRIMITIVE "hmacsha512256" -SODIUM_EXPORT -const char *crypto_auth_primitive(void); - -SODIUM_EXPORT -int crypto_auth(unsigned char *out, const unsigned char *in, - unsigned long long inlen, const unsigned char *k) - __attribute__ ((nonnull(1, 4))); - -SODIUM_EXPORT -int crypto_auth_verify(const unsigned char *h, const unsigned char *in, - unsigned long long inlen, const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(1, 4))); - -SODIUM_EXPORT -void crypto_auth_keygen(unsigned char k[crypto_auth_KEYBYTES]) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_auth_hmacsha256.h b/win32/include/sodium/crypto_auth_hmacsha256.h deleted file mode 100644 index 3da864c7d2..0000000000 --- a/win32/include/sodium/crypto_auth_hmacsha256.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef crypto_auth_hmacsha256_H -#define crypto_auth_hmacsha256_H - -#include -#include "crypto_hash_sha256.h" -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_auth_hmacsha256_BYTES 32U -SODIUM_EXPORT -size_t crypto_auth_hmacsha256_bytes(void); - -#define crypto_auth_hmacsha256_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_auth_hmacsha256_keybytes(void); - -SODIUM_EXPORT -int crypto_auth_hmacsha256(unsigned char *out, - const unsigned char *in, - unsigned long long inlen, - const unsigned char *k) __attribute__ ((nonnull(1, 4))); - -SODIUM_EXPORT -int crypto_auth_hmacsha256_verify(const unsigned char *h, - const unsigned char *in, - unsigned long long inlen, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(1, 4))); - -/* ------------------------------------------------------------------------- */ - -typedef struct crypto_auth_hmacsha256_state { - crypto_hash_sha256_state ictx; - crypto_hash_sha256_state octx; -} crypto_auth_hmacsha256_state; - -SODIUM_EXPORT -size_t crypto_auth_hmacsha256_statebytes(void); - -SODIUM_EXPORT -int crypto_auth_hmacsha256_init(crypto_auth_hmacsha256_state *state, - const unsigned char *key, - size_t keylen) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_auth_hmacsha256_update(crypto_auth_hmacsha256_state *state, - const unsigned char *in, - unsigned long long inlen) - __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int crypto_auth_hmacsha256_final(crypto_auth_hmacsha256_state *state, - unsigned char *out) __attribute__ ((nonnull)); - - -SODIUM_EXPORT -void crypto_auth_hmacsha256_keygen(unsigned char k[crypto_auth_hmacsha256_KEYBYTES]) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_auth_hmacsha512.h b/win32/include/sodium/crypto_auth_hmacsha512.h deleted file mode 100644 index d992cb8163..0000000000 --- a/win32/include/sodium/crypto_auth_hmacsha512.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef crypto_auth_hmacsha512_H -#define crypto_auth_hmacsha512_H - -#include -#include "crypto_hash_sha512.h" -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_auth_hmacsha512_BYTES 64U -SODIUM_EXPORT -size_t crypto_auth_hmacsha512_bytes(void); - -#define crypto_auth_hmacsha512_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_auth_hmacsha512_keybytes(void); - -SODIUM_EXPORT -int crypto_auth_hmacsha512(unsigned char *out, - const unsigned char *in, - unsigned long long inlen, - const unsigned char *k) __attribute__ ((nonnull(1, 4))); - -SODIUM_EXPORT -int crypto_auth_hmacsha512_verify(const unsigned char *h, - const unsigned char *in, - unsigned long long inlen, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(1, 4))); - -/* ------------------------------------------------------------------------- */ - -typedef struct crypto_auth_hmacsha512_state { - crypto_hash_sha512_state ictx; - crypto_hash_sha512_state octx; -} crypto_auth_hmacsha512_state; - -SODIUM_EXPORT -size_t crypto_auth_hmacsha512_statebytes(void); - -SODIUM_EXPORT -int crypto_auth_hmacsha512_init(crypto_auth_hmacsha512_state *state, - const unsigned char *key, - size_t keylen) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_auth_hmacsha512_update(crypto_auth_hmacsha512_state *state, - const unsigned char *in, - unsigned long long inlen) __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int crypto_auth_hmacsha512_final(crypto_auth_hmacsha512_state *state, - unsigned char *out) __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_auth_hmacsha512_keygen(unsigned char k[crypto_auth_hmacsha512_KEYBYTES]) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_auth_hmacsha512256.h b/win32/include/sodium/crypto_auth_hmacsha512256.h deleted file mode 100644 index 3fb5263892..0000000000 --- a/win32/include/sodium/crypto_auth_hmacsha512256.h +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef crypto_auth_hmacsha512256_H -#define crypto_auth_hmacsha512256_H - -#include -#include "crypto_auth_hmacsha512.h" -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_auth_hmacsha512256_BYTES 32U -SODIUM_EXPORT -size_t crypto_auth_hmacsha512256_bytes(void); - -#define crypto_auth_hmacsha512256_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_auth_hmacsha512256_keybytes(void); - -SODIUM_EXPORT -int crypto_auth_hmacsha512256(unsigned char *out, - const unsigned char *in, - unsigned long long inlen, - const unsigned char *k) __attribute__ ((nonnull(1, 4))); - -SODIUM_EXPORT -int crypto_auth_hmacsha512256_verify(const unsigned char *h, - const unsigned char *in, - unsigned long long inlen, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(1, 4))); - -/* ------------------------------------------------------------------------- */ - -typedef crypto_auth_hmacsha512_state crypto_auth_hmacsha512256_state; - -SODIUM_EXPORT -size_t crypto_auth_hmacsha512256_statebytes(void); - -SODIUM_EXPORT -int crypto_auth_hmacsha512256_init(crypto_auth_hmacsha512256_state *state, - const unsigned char *key, - size_t keylen) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_auth_hmacsha512256_update(crypto_auth_hmacsha512256_state *state, - const unsigned char *in, - unsigned long long inlen) __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int crypto_auth_hmacsha512256_final(crypto_auth_hmacsha512256_state *state, - unsigned char *out) __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_auth_hmacsha512256_keygen(unsigned char k[crypto_auth_hmacsha512256_KEYBYTES]) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_box.h b/win32/include/sodium/crypto_box.h deleted file mode 100644 index e060dd29fc..0000000000 --- a/win32/include/sodium/crypto_box.h +++ /dev/null @@ -1,177 +0,0 @@ -#ifndef crypto_box_H -#define crypto_box_H - -/* - * THREAD SAFETY: crypto_box_keypair() is thread-safe, - * provided that sodium_init() was called before. - * - * Other functions are always thread-safe. - */ - -#include - -#include "crypto_box_curve25519xsalsa20poly1305.h" -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_box_SEEDBYTES crypto_box_curve25519xsalsa20poly1305_SEEDBYTES -SODIUM_EXPORT -size_t crypto_box_seedbytes(void); - -#define crypto_box_PUBLICKEYBYTES crypto_box_curve25519xsalsa20poly1305_PUBLICKEYBYTES -SODIUM_EXPORT -size_t crypto_box_publickeybytes(void); - -#define crypto_box_SECRETKEYBYTES crypto_box_curve25519xsalsa20poly1305_SECRETKEYBYTES -SODIUM_EXPORT -size_t crypto_box_secretkeybytes(void); - -#define crypto_box_NONCEBYTES crypto_box_curve25519xsalsa20poly1305_NONCEBYTES -SODIUM_EXPORT -size_t crypto_box_noncebytes(void); - -#define crypto_box_MACBYTES crypto_box_curve25519xsalsa20poly1305_MACBYTES -SODIUM_EXPORT -size_t crypto_box_macbytes(void); - -#define crypto_box_MESSAGEBYTES_MAX crypto_box_curve25519xsalsa20poly1305_MESSAGEBYTES_MAX -SODIUM_EXPORT -size_t crypto_box_messagebytes_max(void); - -#define crypto_box_PRIMITIVE "curve25519xsalsa20poly1305" -SODIUM_EXPORT -const char *crypto_box_primitive(void); - -SODIUM_EXPORT -int crypto_box_seed_keypair(unsigned char *pk, unsigned char *sk, - const unsigned char *seed) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_box_keypair(unsigned char *pk, unsigned char *sk) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_box_easy(unsigned char *c, const unsigned char *m, - unsigned long long mlen, const unsigned char *n, - const unsigned char *pk, const unsigned char *sk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(1, 4, 5, 6))); - -SODIUM_EXPORT -int crypto_box_open_easy(unsigned char *m, const unsigned char *c, - unsigned long long clen, const unsigned char *n, - const unsigned char *pk, const unsigned char *sk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(2, 4, 5, 6))); - -SODIUM_EXPORT -int crypto_box_detached(unsigned char *c, unsigned char *mac, - const unsigned char *m, unsigned long long mlen, - const unsigned char *n, const unsigned char *pk, - const unsigned char *sk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(1, 2, 5, 6, 7))); - -SODIUM_EXPORT -int crypto_box_open_detached(unsigned char *m, const unsigned char *c, - const unsigned char *mac, - unsigned long long clen, - const unsigned char *n, - const unsigned char *pk, - const unsigned char *sk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(2, 3, 5, 6, 7))); - -/* -- Precomputation interface -- */ - -#define crypto_box_BEFORENMBYTES crypto_box_curve25519xsalsa20poly1305_BEFORENMBYTES -SODIUM_EXPORT -size_t crypto_box_beforenmbytes(void); - -SODIUM_EXPORT -int crypto_box_beforenm(unsigned char *k, const unsigned char *pk, - const unsigned char *sk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_box_easy_afternm(unsigned char *c, const unsigned char *m, - unsigned long long mlen, const unsigned char *n, - const unsigned char *k) __attribute__ ((nonnull(1, 4, 5))); - -SODIUM_EXPORT -int crypto_box_open_easy_afternm(unsigned char *m, const unsigned char *c, - unsigned long long clen, const unsigned char *n, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(2, 4, 5))); - -SODIUM_EXPORT -int crypto_box_detached_afternm(unsigned char *c, unsigned char *mac, - const unsigned char *m, unsigned long long mlen, - const unsigned char *n, const unsigned char *k) - __attribute__ ((nonnull(1, 2, 5, 6))); - -SODIUM_EXPORT -int crypto_box_open_detached_afternm(unsigned char *m, const unsigned char *c, - const unsigned char *mac, - unsigned long long clen, const unsigned char *n, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(2, 3, 5, 6))); - -/* -- Ephemeral SK interface -- */ - -#define crypto_box_SEALBYTES (crypto_box_PUBLICKEYBYTES + crypto_box_MACBYTES) -SODIUM_EXPORT -size_t crypto_box_sealbytes(void); - -SODIUM_EXPORT -int crypto_box_seal(unsigned char *c, const unsigned char *m, - unsigned long long mlen, const unsigned char *pk) - __attribute__ ((nonnull(1, 4))); - -SODIUM_EXPORT -int crypto_box_seal_open(unsigned char *m, const unsigned char *c, - unsigned long long clen, - const unsigned char *pk, const unsigned char *sk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(2, 4, 5))); - -/* -- NaCl compatibility interface ; Requires padding -- */ - -#define crypto_box_ZEROBYTES crypto_box_curve25519xsalsa20poly1305_ZEROBYTES -SODIUM_EXPORT -size_t crypto_box_zerobytes(void); - -#define crypto_box_BOXZEROBYTES crypto_box_curve25519xsalsa20poly1305_BOXZEROBYTES -SODIUM_EXPORT -size_t crypto_box_boxzerobytes(void); - -SODIUM_EXPORT -int crypto_box(unsigned char *c, const unsigned char *m, - unsigned long long mlen, const unsigned char *n, - const unsigned char *pk, const unsigned char *sk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(1, 4, 5, 6))); - -SODIUM_EXPORT -int crypto_box_open(unsigned char *m, const unsigned char *c, - unsigned long long clen, const unsigned char *n, - const unsigned char *pk, const unsigned char *sk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(2, 4, 5, 6))); - -SODIUM_EXPORT -int crypto_box_afternm(unsigned char *c, const unsigned char *m, - unsigned long long mlen, const unsigned char *n, - const unsigned char *k) __attribute__ ((nonnull(1, 4, 5))); - -SODIUM_EXPORT -int crypto_box_open_afternm(unsigned char *m, const unsigned char *c, - unsigned long long clen, const unsigned char *n, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(2, 4, 5))); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_box_curve25519xchacha20poly1305.h b/win32/include/sodium/crypto_box_curve25519xchacha20poly1305.h deleted file mode 100644 index 26a3d31efa..0000000000 --- a/win32/include/sodium/crypto_box_curve25519xchacha20poly1305.h +++ /dev/null @@ -1,164 +0,0 @@ - -#ifndef crypto_box_curve25519xchacha20poly1305_H -#define crypto_box_curve25519xchacha20poly1305_H - -#include -#include "crypto_stream_xchacha20.h" -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_box_curve25519xchacha20poly1305_SEEDBYTES 32U -SODIUM_EXPORT -size_t crypto_box_curve25519xchacha20poly1305_seedbytes(void); - -#define crypto_box_curve25519xchacha20poly1305_PUBLICKEYBYTES 32U -SODIUM_EXPORT -size_t crypto_box_curve25519xchacha20poly1305_publickeybytes(void); - -#define crypto_box_curve25519xchacha20poly1305_SECRETKEYBYTES 32U -SODIUM_EXPORT -size_t crypto_box_curve25519xchacha20poly1305_secretkeybytes(void); - -#define crypto_box_curve25519xchacha20poly1305_BEFORENMBYTES 32U -SODIUM_EXPORT -size_t crypto_box_curve25519xchacha20poly1305_beforenmbytes(void); - -#define crypto_box_curve25519xchacha20poly1305_NONCEBYTES 24U -SODIUM_EXPORT -size_t crypto_box_curve25519xchacha20poly1305_noncebytes(void); - -#define crypto_box_curve25519xchacha20poly1305_MACBYTES 16U -SODIUM_EXPORT -size_t crypto_box_curve25519xchacha20poly1305_macbytes(void); - -#define crypto_box_curve25519xchacha20poly1305_MESSAGEBYTES_MAX \ - (crypto_stream_xchacha20_MESSAGEBYTES_MAX - crypto_box_curve25519xchacha20poly1305_MACBYTES) -SODIUM_EXPORT -size_t crypto_box_curve25519xchacha20poly1305_messagebytes_max(void); - -SODIUM_EXPORT -int crypto_box_curve25519xchacha20poly1305_seed_keypair(unsigned char *pk, - unsigned char *sk, - const unsigned char *seed) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_box_curve25519xchacha20poly1305_keypair(unsigned char *pk, - unsigned char *sk) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_box_curve25519xchacha20poly1305_easy(unsigned char *c, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *n, - const unsigned char *pk, - const unsigned char *sk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(1, 4, 5, 6))); - -SODIUM_EXPORT -int crypto_box_curve25519xchacha20poly1305_open_easy(unsigned char *m, - const unsigned char *c, - unsigned long long clen, - const unsigned char *n, - const unsigned char *pk, - const unsigned char *sk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(2, 4, 5, 6))); - -SODIUM_EXPORT -int crypto_box_curve25519xchacha20poly1305_detached(unsigned char *c, - unsigned char *mac, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *n, - const unsigned char *pk, - const unsigned char *sk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(1, 2, 5, 6, 7))); - -SODIUM_EXPORT -int crypto_box_curve25519xchacha20poly1305_open_detached(unsigned char *m, - const unsigned char *c, - const unsigned char *mac, - unsigned long long clen, - const unsigned char *n, - const unsigned char *pk, - const unsigned char *sk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(2, 3, 5, 6, 7))); - -/* -- Precomputation interface -- */ - -SODIUM_EXPORT -int crypto_box_curve25519xchacha20poly1305_beforenm(unsigned char *k, - const unsigned char *pk, - const unsigned char *sk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_box_curve25519xchacha20poly1305_easy_afternm(unsigned char *c, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *n, - const unsigned char *k) - __attribute__ ((nonnull(1, 4, 5))); - -SODIUM_EXPORT -int crypto_box_curve25519xchacha20poly1305_open_easy_afternm(unsigned char *m, - const unsigned char *c, - unsigned long long clen, - const unsigned char *n, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(2, 4, 5))); - -SODIUM_EXPORT -int crypto_box_curve25519xchacha20poly1305_detached_afternm(unsigned char *c, - unsigned char *mac, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *n, - const unsigned char *k) - __attribute__ ((nonnull(1, 2, 5, 6))); - -SODIUM_EXPORT -int crypto_box_curve25519xchacha20poly1305_open_detached_afternm(unsigned char *m, - const unsigned char *c, - const unsigned char *mac, - unsigned long long clen, - const unsigned char *n, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(2, 3, 5, 6))); - -/* -- Ephemeral SK interface -- */ - -#define crypto_box_curve25519xchacha20poly1305_SEALBYTES \ - (crypto_box_curve25519xchacha20poly1305_PUBLICKEYBYTES + \ - crypto_box_curve25519xchacha20poly1305_MACBYTES) - -SODIUM_EXPORT -size_t crypto_box_curve25519xchacha20poly1305_sealbytes(void); - -SODIUM_EXPORT -int crypto_box_curve25519xchacha20poly1305_seal(unsigned char *c, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *pk) - __attribute__ ((nonnull(1, 4))); - -SODIUM_EXPORT -int crypto_box_curve25519xchacha20poly1305_seal_open(unsigned char *m, - const unsigned char *c, - unsigned long long clen, - const unsigned char *pk, - const unsigned char *sk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(2, 4, 5))); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_box_curve25519xsalsa20poly1305.h b/win32/include/sodium/crypto_box_curve25519xsalsa20poly1305.h deleted file mode 100644 index e733f49995..0000000000 --- a/win32/include/sodium/crypto_box_curve25519xsalsa20poly1305.h +++ /dev/null @@ -1,112 +0,0 @@ -#ifndef crypto_box_curve25519xsalsa20poly1305_H -#define crypto_box_curve25519xsalsa20poly1305_H - -#include -#include "crypto_stream_xsalsa20.h" -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_box_curve25519xsalsa20poly1305_SEEDBYTES 32U -SODIUM_EXPORT -size_t crypto_box_curve25519xsalsa20poly1305_seedbytes(void); - -#define crypto_box_curve25519xsalsa20poly1305_PUBLICKEYBYTES 32U -SODIUM_EXPORT -size_t crypto_box_curve25519xsalsa20poly1305_publickeybytes(void); - -#define crypto_box_curve25519xsalsa20poly1305_SECRETKEYBYTES 32U -SODIUM_EXPORT -size_t crypto_box_curve25519xsalsa20poly1305_secretkeybytes(void); - -#define crypto_box_curve25519xsalsa20poly1305_BEFORENMBYTES 32U -SODIUM_EXPORT -size_t crypto_box_curve25519xsalsa20poly1305_beforenmbytes(void); - -#define crypto_box_curve25519xsalsa20poly1305_NONCEBYTES 24U -SODIUM_EXPORT -size_t crypto_box_curve25519xsalsa20poly1305_noncebytes(void); - -#define crypto_box_curve25519xsalsa20poly1305_MACBYTES 16U -SODIUM_EXPORT -size_t crypto_box_curve25519xsalsa20poly1305_macbytes(void); - -/* Only for the libsodium API - The NaCl compatibility API would require BOXZEROBYTES extra bytes */ -#define crypto_box_curve25519xsalsa20poly1305_MESSAGEBYTES_MAX \ - (crypto_stream_xsalsa20_MESSAGEBYTES_MAX - crypto_box_curve25519xsalsa20poly1305_MACBYTES) -SODIUM_EXPORT -size_t crypto_box_curve25519xsalsa20poly1305_messagebytes_max(void); - -SODIUM_EXPORT -int crypto_box_curve25519xsalsa20poly1305_seed_keypair(unsigned char *pk, - unsigned char *sk, - const unsigned char *seed) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_box_curve25519xsalsa20poly1305_keypair(unsigned char *pk, - unsigned char *sk) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_box_curve25519xsalsa20poly1305_beforenm(unsigned char *k, - const unsigned char *pk, - const unsigned char *sk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -/* -- NaCl compatibility interface ; Requires padding -- */ - -#define crypto_box_curve25519xsalsa20poly1305_BOXZEROBYTES 16U -SODIUM_EXPORT -size_t crypto_box_curve25519xsalsa20poly1305_boxzerobytes(void); - -#define crypto_box_curve25519xsalsa20poly1305_ZEROBYTES \ - (crypto_box_curve25519xsalsa20poly1305_BOXZEROBYTES + \ - crypto_box_curve25519xsalsa20poly1305_MACBYTES) -SODIUM_EXPORT -size_t crypto_box_curve25519xsalsa20poly1305_zerobytes(void); - -SODIUM_EXPORT -int crypto_box_curve25519xsalsa20poly1305(unsigned char *c, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *n, - const unsigned char *pk, - const unsigned char *sk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(1, 4, 5, 6))); - -SODIUM_EXPORT -int crypto_box_curve25519xsalsa20poly1305_open(unsigned char *m, - const unsigned char *c, - unsigned long long clen, - const unsigned char *n, - const unsigned char *pk, - const unsigned char *sk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(2, 4, 5, 6))); - -SODIUM_EXPORT -int crypto_box_curve25519xsalsa20poly1305_afternm(unsigned char *c, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *n, - const unsigned char *k) - __attribute__ ((nonnull(1, 4, 5))); - -SODIUM_EXPORT -int crypto_box_curve25519xsalsa20poly1305_open_afternm(unsigned char *m, - const unsigned char *c, - unsigned long long clen, - const unsigned char *n, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(2, 4, 5))); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_core_ed25519.h b/win32/include/sodium/crypto_core_ed25519.h deleted file mode 100644 index 3eae00c456..0000000000 --- a/win32/include/sodium/crypto_core_ed25519.h +++ /dev/null @@ -1,100 +0,0 @@ -#ifndef crypto_core_ed25519_H -#define crypto_core_ed25519_H - -#include -#include "export.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define crypto_core_ed25519_BYTES 32 -SODIUM_EXPORT -size_t crypto_core_ed25519_bytes(void); - -#define crypto_core_ed25519_UNIFORMBYTES 32 -SODIUM_EXPORT -size_t crypto_core_ed25519_uniformbytes(void); - -#define crypto_core_ed25519_HASHBYTES 64 -SODIUM_EXPORT -size_t crypto_core_ed25519_hashbytes(void); - -#define crypto_core_ed25519_SCALARBYTES 32 -SODIUM_EXPORT -size_t crypto_core_ed25519_scalarbytes(void); - -#define crypto_core_ed25519_NONREDUCEDSCALARBYTES 64 -SODIUM_EXPORT -size_t crypto_core_ed25519_nonreducedscalarbytes(void); - -SODIUM_EXPORT -int crypto_core_ed25519_is_valid_point(const unsigned char *p) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_core_ed25519_add(unsigned char *r, - const unsigned char *p, const unsigned char *q) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_core_ed25519_sub(unsigned char *r, - const unsigned char *p, const unsigned char *q) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_core_ed25519_from_uniform(unsigned char *p, const unsigned char *r) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_core_ed25519_from_hash(unsigned char *p, const unsigned char *h) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_core_ed25519_random(unsigned char *p) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_core_ed25519_scalar_random(unsigned char *r) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_core_ed25519_scalar_invert(unsigned char *recip, const unsigned char *s) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_core_ed25519_scalar_negate(unsigned char *neg, const unsigned char *s) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_core_ed25519_scalar_complement(unsigned char *comp, const unsigned char *s) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_core_ed25519_scalar_add(unsigned char *z, const unsigned char *x, - const unsigned char *y) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_core_ed25519_scalar_sub(unsigned char *z, const unsigned char *x, - const unsigned char *y) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_core_ed25519_scalar_mul(unsigned char *z, const unsigned char *x, - const unsigned char *y) - __attribute__ ((nonnull)); - -/* - * The interval `s` is sampled from should be at least 317 bits to ensure almost - * uniformity of `r` over `L`. - */ -SODIUM_EXPORT -void crypto_core_ed25519_scalar_reduce(unsigned char *r, const unsigned char *s) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_core_hchacha20.h b/win32/include/sodium/crypto_core_hchacha20.h deleted file mode 100644 index ece141b09b..0000000000 --- a/win32/include/sodium/crypto_core_hchacha20.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef crypto_core_hchacha20_H -#define crypto_core_hchacha20_H - -#include -#include "export.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define crypto_core_hchacha20_OUTPUTBYTES 32U -SODIUM_EXPORT -size_t crypto_core_hchacha20_outputbytes(void); - -#define crypto_core_hchacha20_INPUTBYTES 16U -SODIUM_EXPORT -size_t crypto_core_hchacha20_inputbytes(void); - -#define crypto_core_hchacha20_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_core_hchacha20_keybytes(void); - -#define crypto_core_hchacha20_CONSTBYTES 16U -SODIUM_EXPORT -size_t crypto_core_hchacha20_constbytes(void); - -SODIUM_EXPORT -int crypto_core_hchacha20(unsigned char *out, const unsigned char *in, - const unsigned char *k, const unsigned char *c) - __attribute__ ((nonnull(1, 2, 3))); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_core_hsalsa20.h b/win32/include/sodium/crypto_core_hsalsa20.h deleted file mode 100644 index 4bf7a48786..0000000000 --- a/win32/include/sodium/crypto_core_hsalsa20.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef crypto_core_hsalsa20_H -#define crypto_core_hsalsa20_H - -#include -#include "export.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define crypto_core_hsalsa20_OUTPUTBYTES 32U -SODIUM_EXPORT -size_t crypto_core_hsalsa20_outputbytes(void); - -#define crypto_core_hsalsa20_INPUTBYTES 16U -SODIUM_EXPORT -size_t crypto_core_hsalsa20_inputbytes(void); - -#define crypto_core_hsalsa20_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_core_hsalsa20_keybytes(void); - -#define crypto_core_hsalsa20_CONSTBYTES 16U -SODIUM_EXPORT -size_t crypto_core_hsalsa20_constbytes(void); - -SODIUM_EXPORT -int crypto_core_hsalsa20(unsigned char *out, const unsigned char *in, - const unsigned char *k, const unsigned char *c) - __attribute__ ((nonnull(1, 2, 3))); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_core_ristretto255.h b/win32/include/sodium/crypto_core_ristretto255.h deleted file mode 100644 index f2820e5576..0000000000 --- a/win32/include/sodium/crypto_core_ristretto255.h +++ /dev/null @@ -1,100 +0,0 @@ -#ifndef crypto_core_ristretto255_H -#define crypto_core_ristretto255_H - -#include -#include "export.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define crypto_core_ristretto255_BYTES 32 -SODIUM_EXPORT -size_t crypto_core_ristretto255_bytes(void); - -#define crypto_core_ristretto255_HASHBYTES 64 -SODIUM_EXPORT -size_t crypto_core_ristretto255_hashbytes(void); - -#define crypto_core_ristretto255_SCALARBYTES 32 -SODIUM_EXPORT -size_t crypto_core_ristretto255_scalarbytes(void); - -#define crypto_core_ristretto255_NONREDUCEDSCALARBYTES 64 -SODIUM_EXPORT -size_t crypto_core_ristretto255_nonreducedscalarbytes(void); - -SODIUM_EXPORT -int crypto_core_ristretto255_is_valid_point(const unsigned char *p) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_core_ristretto255_add(unsigned char *r, - const unsigned char *p, const unsigned char *q) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_core_ristretto255_sub(unsigned char *r, - const unsigned char *p, const unsigned char *q) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_core_ristretto255_from_hash(unsigned char *p, - const unsigned char *r) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_core_ristretto255_random(unsigned char *p) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_core_ristretto255_scalar_random(unsigned char *r) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_core_ristretto255_scalar_invert(unsigned char *recip, - const unsigned char *s) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_core_ristretto255_scalar_negate(unsigned char *neg, - const unsigned char *s) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_core_ristretto255_scalar_complement(unsigned char *comp, - const unsigned char *s) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_core_ristretto255_scalar_add(unsigned char *z, - const unsigned char *x, - const unsigned char *y) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_core_ristretto255_scalar_sub(unsigned char *z, - const unsigned char *x, - const unsigned char *y) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_core_ristretto255_scalar_mul(unsigned char *z, - const unsigned char *x, - const unsigned char *y) - __attribute__ ((nonnull)); - -/* - * The interval `s` is sampled from should be at least 317 bits to ensure almost - * uniformity of `r` over `L`. - */ -SODIUM_EXPORT -void crypto_core_ristretto255_scalar_reduce(unsigned char *r, - const unsigned char *s) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_core_salsa20.h b/win32/include/sodium/crypto_core_salsa20.h deleted file mode 100644 index bd79fd9f54..0000000000 --- a/win32/include/sodium/crypto_core_salsa20.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef crypto_core_salsa20_H -#define crypto_core_salsa20_H - -#include -#include "export.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define crypto_core_salsa20_OUTPUTBYTES 64U -SODIUM_EXPORT -size_t crypto_core_salsa20_outputbytes(void); - -#define crypto_core_salsa20_INPUTBYTES 16U -SODIUM_EXPORT -size_t crypto_core_salsa20_inputbytes(void); - -#define crypto_core_salsa20_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_core_salsa20_keybytes(void); - -#define crypto_core_salsa20_CONSTBYTES 16U -SODIUM_EXPORT -size_t crypto_core_salsa20_constbytes(void); - -SODIUM_EXPORT -int crypto_core_salsa20(unsigned char *out, const unsigned char *in, - const unsigned char *k, const unsigned char *c) - __attribute__ ((nonnull(1, 2, 3))); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_core_salsa2012.h b/win32/include/sodium/crypto_core_salsa2012.h deleted file mode 100644 index 05957591ca..0000000000 --- a/win32/include/sodium/crypto_core_salsa2012.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef crypto_core_salsa2012_H -#define crypto_core_salsa2012_H - -#include -#include "export.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define crypto_core_salsa2012_OUTPUTBYTES 64U -SODIUM_EXPORT -size_t crypto_core_salsa2012_outputbytes(void); - -#define crypto_core_salsa2012_INPUTBYTES 16U -SODIUM_EXPORT -size_t crypto_core_salsa2012_inputbytes(void); - -#define crypto_core_salsa2012_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_core_salsa2012_keybytes(void); - -#define crypto_core_salsa2012_CONSTBYTES 16U -SODIUM_EXPORT -size_t crypto_core_salsa2012_constbytes(void); - -SODIUM_EXPORT -int crypto_core_salsa2012(unsigned char *out, const unsigned char *in, - const unsigned char *k, const unsigned char *c) - __attribute__ ((nonnull(1, 2, 3))); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_core_salsa208.h b/win32/include/sodium/crypto_core_salsa208.h deleted file mode 100644 index d2f216af26..0000000000 --- a/win32/include/sodium/crypto_core_salsa208.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef crypto_core_salsa208_H -#define crypto_core_salsa208_H - -#include -#include "export.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define crypto_core_salsa208_OUTPUTBYTES 64U -SODIUM_EXPORT -size_t crypto_core_salsa208_outputbytes(void) - __attribute__ ((deprecated)); - -#define crypto_core_salsa208_INPUTBYTES 16U -SODIUM_EXPORT -size_t crypto_core_salsa208_inputbytes(void) - __attribute__ ((deprecated)); - -#define crypto_core_salsa208_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_core_salsa208_keybytes(void) - __attribute__ ((deprecated)); - -#define crypto_core_salsa208_CONSTBYTES 16U -SODIUM_EXPORT -size_t crypto_core_salsa208_constbytes(void) - __attribute__ ((deprecated)); - -SODIUM_EXPORT -int crypto_core_salsa208(unsigned char *out, const unsigned char *in, - const unsigned char *k, const unsigned char *c) - __attribute__ ((nonnull(1, 2, 3))); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_generichash.h b/win32/include/sodium/crypto_generichash.h deleted file mode 100644 index d897e5d26c..0000000000 --- a/win32/include/sodium/crypto_generichash.h +++ /dev/null @@ -1,84 +0,0 @@ -#ifndef crypto_generichash_H -#define crypto_generichash_H - -#include - -#include "crypto_generichash_blake2b.h" -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_generichash_BYTES_MIN crypto_generichash_blake2b_BYTES_MIN -SODIUM_EXPORT -size_t crypto_generichash_bytes_min(void); - -#define crypto_generichash_BYTES_MAX crypto_generichash_blake2b_BYTES_MAX -SODIUM_EXPORT -size_t crypto_generichash_bytes_max(void); - -#define crypto_generichash_BYTES crypto_generichash_blake2b_BYTES -SODIUM_EXPORT -size_t crypto_generichash_bytes(void); - -#define crypto_generichash_KEYBYTES_MIN crypto_generichash_blake2b_KEYBYTES_MIN -SODIUM_EXPORT -size_t crypto_generichash_keybytes_min(void); - -#define crypto_generichash_KEYBYTES_MAX crypto_generichash_blake2b_KEYBYTES_MAX -SODIUM_EXPORT -size_t crypto_generichash_keybytes_max(void); - -#define crypto_generichash_KEYBYTES crypto_generichash_blake2b_KEYBYTES -SODIUM_EXPORT -size_t crypto_generichash_keybytes(void); - -#define crypto_generichash_PRIMITIVE "blake2b" -SODIUM_EXPORT -const char *crypto_generichash_primitive(void); - -/* - * Important when writing bindings for other programming languages: - * the state address should be 64-bytes aligned. - */ -typedef crypto_generichash_blake2b_state crypto_generichash_state; - -SODIUM_EXPORT -size_t crypto_generichash_statebytes(void); - -SODIUM_EXPORT -int crypto_generichash(unsigned char *out, size_t outlen, - const unsigned char *in, unsigned long long inlen, - const unsigned char *key, size_t keylen) - __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int crypto_generichash_init(crypto_generichash_state *state, - const unsigned char *key, - const size_t keylen, const size_t outlen) - __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int crypto_generichash_update(crypto_generichash_state *state, - const unsigned char *in, - unsigned long long inlen) - __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int crypto_generichash_final(crypto_generichash_state *state, - unsigned char *out, const size_t outlen) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_generichash_keygen(unsigned char k[crypto_generichash_KEYBYTES]) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_generichash_blake2b.h b/win32/include/sodium/crypto_generichash_blake2b.h deleted file mode 100644 index fee9d8ad19..0000000000 --- a/win32/include/sodium/crypto_generichash_blake2b.h +++ /dev/null @@ -1,118 +0,0 @@ -#ifndef crypto_generichash_blake2b_H -#define crypto_generichash_blake2b_H - -#include -#include -#include - -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#if defined(__IBMC__) || defined(__SUNPRO_C) || defined(__SUNPRO_CC) -# pragma pack(1) -#else -# pragma pack(push, 1) -#endif - -typedef struct CRYPTO_ALIGN(64) crypto_generichash_blake2b_state { - unsigned char opaque[384]; -} crypto_generichash_blake2b_state; - -#if defined(__IBMC__) || defined(__SUNPRO_C) || defined(__SUNPRO_CC) -# pragma pack() -#else -# pragma pack(pop) -#endif - -#define crypto_generichash_blake2b_BYTES_MIN 16U -SODIUM_EXPORT -size_t crypto_generichash_blake2b_bytes_min(void); - -#define crypto_generichash_blake2b_BYTES_MAX 64U -SODIUM_EXPORT -size_t crypto_generichash_blake2b_bytes_max(void); - -#define crypto_generichash_blake2b_BYTES 32U -SODIUM_EXPORT -size_t crypto_generichash_blake2b_bytes(void); - -#define crypto_generichash_blake2b_KEYBYTES_MIN 16U -SODIUM_EXPORT -size_t crypto_generichash_blake2b_keybytes_min(void); - -#define crypto_generichash_blake2b_KEYBYTES_MAX 64U -SODIUM_EXPORT -size_t crypto_generichash_blake2b_keybytes_max(void); - -#define crypto_generichash_blake2b_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_generichash_blake2b_keybytes(void); - -#define crypto_generichash_blake2b_SALTBYTES 16U -SODIUM_EXPORT -size_t crypto_generichash_blake2b_saltbytes(void); - -#define crypto_generichash_blake2b_PERSONALBYTES 16U -SODIUM_EXPORT -size_t crypto_generichash_blake2b_personalbytes(void); - -SODIUM_EXPORT -size_t crypto_generichash_blake2b_statebytes(void); - -SODIUM_EXPORT -int crypto_generichash_blake2b(unsigned char *out, size_t outlen, - const unsigned char *in, - unsigned long long inlen, - const unsigned char *key, size_t keylen) - __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int crypto_generichash_blake2b_salt_personal(unsigned char *out, size_t outlen, - const unsigned char *in, - unsigned long long inlen, - const unsigned char *key, - size_t keylen, - const unsigned char *salt, - const unsigned char *personal) - __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int crypto_generichash_blake2b_init(crypto_generichash_blake2b_state *state, - const unsigned char *key, - const size_t keylen, const size_t outlen) - __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int crypto_generichash_blake2b_init_salt_personal(crypto_generichash_blake2b_state *state, - const unsigned char *key, - const size_t keylen, const size_t outlen, - const unsigned char *salt, - const unsigned char *personal) - __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int crypto_generichash_blake2b_update(crypto_generichash_blake2b_state *state, - const unsigned char *in, - unsigned long long inlen) - __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int crypto_generichash_blake2b_final(crypto_generichash_blake2b_state *state, - unsigned char *out, - const size_t outlen) __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_generichash_blake2b_keygen(unsigned char k[crypto_generichash_blake2b_KEYBYTES]) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_hash.h b/win32/include/sodium/crypto_hash.h deleted file mode 100644 index 8752f9cafe..0000000000 --- a/win32/include/sodium/crypto_hash.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef crypto_hash_H -#define crypto_hash_H - -/* - * WARNING: Unless you absolutely need to use SHA512 for interoperatibility, - * purposes, you might want to consider crypto_generichash() instead. - * Unlike SHA512, crypto_generichash() is not vulnerable to length - * extension attacks. - */ - -#include - -#include "crypto_hash_sha512.h" -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_hash_BYTES crypto_hash_sha512_BYTES -SODIUM_EXPORT -size_t crypto_hash_bytes(void); - -SODIUM_EXPORT -int crypto_hash(unsigned char *out, const unsigned char *in, - unsigned long long inlen) __attribute__ ((nonnull(1))); - -#define crypto_hash_PRIMITIVE "sha512" -SODIUM_EXPORT -const char *crypto_hash_primitive(void) - __attribute__ ((warn_unused_result)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_hash_sha256.h b/win32/include/sodium/crypto_hash_sha256.h deleted file mode 100644 index b18217e18d..0000000000 --- a/win32/include/sodium/crypto_hash_sha256.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef crypto_hash_sha256_H -#define crypto_hash_sha256_H - -/* - * WARNING: Unless you absolutely need to use SHA256 for interoperatibility, - * purposes, you might want to consider crypto_generichash() instead. - * Unlike SHA256, crypto_generichash() is not vulnerable to length - * extension attacks. - */ - -#include -#include -#include - -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -typedef struct crypto_hash_sha256_state { - uint32_t state[8]; - uint64_t count; - uint8_t buf[64]; -} crypto_hash_sha256_state; - -SODIUM_EXPORT -size_t crypto_hash_sha256_statebytes(void); - -#define crypto_hash_sha256_BYTES 32U -SODIUM_EXPORT -size_t crypto_hash_sha256_bytes(void); - -SODIUM_EXPORT -int crypto_hash_sha256(unsigned char *out, const unsigned char *in, - unsigned long long inlen) __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int crypto_hash_sha256_init(crypto_hash_sha256_state *state) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_hash_sha256_update(crypto_hash_sha256_state *state, - const unsigned char *in, - unsigned long long inlen) - __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int crypto_hash_sha256_final(crypto_hash_sha256_state *state, - unsigned char *out) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_hash_sha512.h b/win32/include/sodium/crypto_hash_sha512.h deleted file mode 100644 index 8efa7193ad..0000000000 --- a/win32/include/sodium/crypto_hash_sha512.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef crypto_hash_sha512_H -#define crypto_hash_sha512_H - -/* - * WARNING: Unless you absolutely need to use SHA512 for interoperatibility, - * purposes, you might want to consider crypto_generichash() instead. - * Unlike SHA512, crypto_generichash() is not vulnerable to length - * extension attacks. - */ - -#include -#include -#include - -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -typedef struct crypto_hash_sha512_state { - uint64_t state[8]; - uint64_t count[2]; - uint8_t buf[128]; -} crypto_hash_sha512_state; - -SODIUM_EXPORT -size_t crypto_hash_sha512_statebytes(void); - -#define crypto_hash_sha512_BYTES 64U -SODIUM_EXPORT -size_t crypto_hash_sha512_bytes(void); - -SODIUM_EXPORT -int crypto_hash_sha512(unsigned char *out, const unsigned char *in, - unsigned long long inlen) __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int crypto_hash_sha512_init(crypto_hash_sha512_state *state) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_hash_sha512_update(crypto_hash_sha512_state *state, - const unsigned char *in, - unsigned long long inlen) - __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int crypto_hash_sha512_final(crypto_hash_sha512_state *state, - unsigned char *out) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_kdf.h b/win32/include/sodium/crypto_kdf.h deleted file mode 100644 index ac2fc6183c..0000000000 --- a/win32/include/sodium/crypto_kdf.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef crypto_kdf_H -#define crypto_kdf_H - -#include -#include - -#include "crypto_kdf_blake2b.h" -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_kdf_BYTES_MIN crypto_kdf_blake2b_BYTES_MIN -SODIUM_EXPORT -size_t crypto_kdf_bytes_min(void); - -#define crypto_kdf_BYTES_MAX crypto_kdf_blake2b_BYTES_MAX -SODIUM_EXPORT -size_t crypto_kdf_bytes_max(void); - -#define crypto_kdf_CONTEXTBYTES crypto_kdf_blake2b_CONTEXTBYTES -SODIUM_EXPORT -size_t crypto_kdf_contextbytes(void); - -#define crypto_kdf_KEYBYTES crypto_kdf_blake2b_KEYBYTES -SODIUM_EXPORT -size_t crypto_kdf_keybytes(void); - -#define crypto_kdf_PRIMITIVE "blake2b" -SODIUM_EXPORT -const char *crypto_kdf_primitive(void) - __attribute__ ((warn_unused_result)); - -SODIUM_EXPORT -int crypto_kdf_derive_from_key(unsigned char *subkey, size_t subkey_len, - uint64_t subkey_id, - const char ctx[crypto_kdf_CONTEXTBYTES], - const unsigned char key[crypto_kdf_KEYBYTES]) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_kdf_keygen(unsigned char k[crypto_kdf_KEYBYTES]) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_kdf_blake2b.h b/win32/include/sodium/crypto_kdf_blake2b.h deleted file mode 100644 index 3ae47dd32c..0000000000 --- a/win32/include/sodium/crypto_kdf_blake2b.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef crypto_kdf_blake2b_H -#define crypto_kdf_blake2b_H - -#include -#include - -#include "crypto_kdf_blake2b.h" -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_kdf_blake2b_BYTES_MIN 16 -SODIUM_EXPORT -size_t crypto_kdf_blake2b_bytes_min(void); - -#define crypto_kdf_blake2b_BYTES_MAX 64 -SODIUM_EXPORT -size_t crypto_kdf_blake2b_bytes_max(void); - -#define crypto_kdf_blake2b_CONTEXTBYTES 8 -SODIUM_EXPORT -size_t crypto_kdf_blake2b_contextbytes(void); - -#define crypto_kdf_blake2b_KEYBYTES 32 -SODIUM_EXPORT -size_t crypto_kdf_blake2b_keybytes(void); - -SODIUM_EXPORT -int crypto_kdf_blake2b_derive_from_key(unsigned char *subkey, size_t subkey_len, - uint64_t subkey_id, - const char ctx[crypto_kdf_blake2b_CONTEXTBYTES], - const unsigned char key[crypto_kdf_blake2b_KEYBYTES]) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_kx.h b/win32/include/sodium/crypto_kx.h deleted file mode 100644 index 347132c320..0000000000 --- a/win32/include/sodium/crypto_kx.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef crypto_kx_H -#define crypto_kx_H - -#include - -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_kx_PUBLICKEYBYTES 32 -SODIUM_EXPORT -size_t crypto_kx_publickeybytes(void); - -#define crypto_kx_SECRETKEYBYTES 32 -SODIUM_EXPORT -size_t crypto_kx_secretkeybytes(void); - -#define crypto_kx_SEEDBYTES 32 -SODIUM_EXPORT -size_t crypto_kx_seedbytes(void); - -#define crypto_kx_SESSIONKEYBYTES 32 -SODIUM_EXPORT -size_t crypto_kx_sessionkeybytes(void); - -#define crypto_kx_PRIMITIVE "x25519blake2b" -SODIUM_EXPORT -const char *crypto_kx_primitive(void); - -SODIUM_EXPORT -int crypto_kx_seed_keypair(unsigned char pk[crypto_kx_PUBLICKEYBYTES], - unsigned char sk[crypto_kx_SECRETKEYBYTES], - const unsigned char seed[crypto_kx_SEEDBYTES]) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_kx_keypair(unsigned char pk[crypto_kx_PUBLICKEYBYTES], - unsigned char sk[crypto_kx_SECRETKEYBYTES]) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_kx_client_session_keys(unsigned char rx[crypto_kx_SESSIONKEYBYTES], - unsigned char tx[crypto_kx_SESSIONKEYBYTES], - const unsigned char client_pk[crypto_kx_PUBLICKEYBYTES], - const unsigned char client_sk[crypto_kx_SECRETKEYBYTES], - const unsigned char server_pk[crypto_kx_PUBLICKEYBYTES]) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(3, 4, 5))); - -SODIUM_EXPORT -int crypto_kx_server_session_keys(unsigned char rx[crypto_kx_SESSIONKEYBYTES], - unsigned char tx[crypto_kx_SESSIONKEYBYTES], - const unsigned char server_pk[crypto_kx_PUBLICKEYBYTES], - const unsigned char server_sk[crypto_kx_SECRETKEYBYTES], - const unsigned char client_pk[crypto_kx_PUBLICKEYBYTES]) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(3, 4, 5))); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_onetimeauth.h b/win32/include/sodium/crypto_onetimeauth.h deleted file mode 100644 index 7cd7b07060..0000000000 --- a/win32/include/sodium/crypto_onetimeauth.h +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef crypto_onetimeauth_H -#define crypto_onetimeauth_H - -#include - -#include "crypto_onetimeauth_poly1305.h" -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -typedef crypto_onetimeauth_poly1305_state crypto_onetimeauth_state; - -SODIUM_EXPORT -size_t crypto_onetimeauth_statebytes(void); - -#define crypto_onetimeauth_BYTES crypto_onetimeauth_poly1305_BYTES -SODIUM_EXPORT -size_t crypto_onetimeauth_bytes(void); - -#define crypto_onetimeauth_KEYBYTES crypto_onetimeauth_poly1305_KEYBYTES -SODIUM_EXPORT -size_t crypto_onetimeauth_keybytes(void); - -#define crypto_onetimeauth_PRIMITIVE "poly1305" -SODIUM_EXPORT -const char *crypto_onetimeauth_primitive(void); - -SODIUM_EXPORT -int crypto_onetimeauth(unsigned char *out, const unsigned char *in, - unsigned long long inlen, const unsigned char *k) - __attribute__ ((nonnull(1, 4))); - -SODIUM_EXPORT -int crypto_onetimeauth_verify(const unsigned char *h, const unsigned char *in, - unsigned long long inlen, const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(1, 4))); - -SODIUM_EXPORT -int crypto_onetimeauth_init(crypto_onetimeauth_state *state, - const unsigned char *key) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_onetimeauth_update(crypto_onetimeauth_state *state, - const unsigned char *in, - unsigned long long inlen) - __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int crypto_onetimeauth_final(crypto_onetimeauth_state *state, - unsigned char *out) __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_onetimeauth_keygen(unsigned char k[crypto_onetimeauth_KEYBYTES]) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_onetimeauth_poly1305.h b/win32/include/sodium/crypto_onetimeauth_poly1305.h deleted file mode 100644 index f3e34d86df..0000000000 --- a/win32/include/sodium/crypto_onetimeauth_poly1305.h +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef crypto_onetimeauth_poly1305_H -#define crypto_onetimeauth_poly1305_H - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#include -#include -#include - -#include - -#include "export.h" - -typedef struct CRYPTO_ALIGN(16) crypto_onetimeauth_poly1305_state { - unsigned char opaque[256]; -} crypto_onetimeauth_poly1305_state; - -SODIUM_EXPORT -size_t crypto_onetimeauth_poly1305_statebytes(void); - -#define crypto_onetimeauth_poly1305_BYTES 16U -SODIUM_EXPORT -size_t crypto_onetimeauth_poly1305_bytes(void); - -#define crypto_onetimeauth_poly1305_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_onetimeauth_poly1305_keybytes(void); - -SODIUM_EXPORT -int crypto_onetimeauth_poly1305(unsigned char *out, - const unsigned char *in, - unsigned long long inlen, - const unsigned char *k) - __attribute__ ((nonnull(1, 4))); - -SODIUM_EXPORT -int crypto_onetimeauth_poly1305_verify(const unsigned char *h, - const unsigned char *in, - unsigned long long inlen, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(1, 4))); - -SODIUM_EXPORT -int crypto_onetimeauth_poly1305_init(crypto_onetimeauth_poly1305_state *state, - const unsigned char *key) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_onetimeauth_poly1305_update(crypto_onetimeauth_poly1305_state *state, - const unsigned char *in, - unsigned long long inlen) - __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int crypto_onetimeauth_poly1305_final(crypto_onetimeauth_poly1305_state *state, - unsigned char *out) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_onetimeauth_poly1305_keygen(unsigned char k[crypto_onetimeauth_poly1305_KEYBYTES]) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_pwhash.h b/win32/include/sodium/crypto_pwhash.h deleted file mode 100644 index 585a993efd..0000000000 --- a/win32/include/sodium/crypto_pwhash.h +++ /dev/null @@ -1,147 +0,0 @@ -#ifndef crypto_pwhash_H -#define crypto_pwhash_H - -#include - -#include "crypto_pwhash_argon2i.h" -#include "crypto_pwhash_argon2id.h" -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_pwhash_ALG_ARGON2I13 crypto_pwhash_argon2i_ALG_ARGON2I13 -SODIUM_EXPORT -int crypto_pwhash_alg_argon2i13(void); - -#define crypto_pwhash_ALG_ARGON2ID13 crypto_pwhash_argon2id_ALG_ARGON2ID13 -SODIUM_EXPORT -int crypto_pwhash_alg_argon2id13(void); - -#define crypto_pwhash_ALG_DEFAULT crypto_pwhash_ALG_ARGON2ID13 -SODIUM_EXPORT -int crypto_pwhash_alg_default(void); - -#define crypto_pwhash_BYTES_MIN crypto_pwhash_argon2id_BYTES_MIN -SODIUM_EXPORT -size_t crypto_pwhash_bytes_min(void); - -#define crypto_pwhash_BYTES_MAX crypto_pwhash_argon2id_BYTES_MAX -SODIUM_EXPORT -size_t crypto_pwhash_bytes_max(void); - -#define crypto_pwhash_PASSWD_MIN crypto_pwhash_argon2id_PASSWD_MIN -SODIUM_EXPORT -size_t crypto_pwhash_passwd_min(void); - -#define crypto_pwhash_PASSWD_MAX crypto_pwhash_argon2id_PASSWD_MAX -SODIUM_EXPORT -size_t crypto_pwhash_passwd_max(void); - -#define crypto_pwhash_SALTBYTES crypto_pwhash_argon2id_SALTBYTES -SODIUM_EXPORT -size_t crypto_pwhash_saltbytes(void); - -#define crypto_pwhash_STRBYTES crypto_pwhash_argon2id_STRBYTES -SODIUM_EXPORT -size_t crypto_pwhash_strbytes(void); - -#define crypto_pwhash_STRPREFIX crypto_pwhash_argon2id_STRPREFIX -SODIUM_EXPORT -const char *crypto_pwhash_strprefix(void); - -#define crypto_pwhash_OPSLIMIT_MIN crypto_pwhash_argon2id_OPSLIMIT_MIN -SODIUM_EXPORT -size_t crypto_pwhash_opslimit_min(void); - -#define crypto_pwhash_OPSLIMIT_MAX crypto_pwhash_argon2id_OPSLIMIT_MAX -SODIUM_EXPORT -size_t crypto_pwhash_opslimit_max(void); - -#define crypto_pwhash_MEMLIMIT_MIN crypto_pwhash_argon2id_MEMLIMIT_MIN -SODIUM_EXPORT -size_t crypto_pwhash_memlimit_min(void); - -#define crypto_pwhash_MEMLIMIT_MAX crypto_pwhash_argon2id_MEMLIMIT_MAX -SODIUM_EXPORT -size_t crypto_pwhash_memlimit_max(void); - -#define crypto_pwhash_OPSLIMIT_INTERACTIVE crypto_pwhash_argon2id_OPSLIMIT_INTERACTIVE -SODIUM_EXPORT -size_t crypto_pwhash_opslimit_interactive(void); - -#define crypto_pwhash_MEMLIMIT_INTERACTIVE crypto_pwhash_argon2id_MEMLIMIT_INTERACTIVE -SODIUM_EXPORT -size_t crypto_pwhash_memlimit_interactive(void); - -#define crypto_pwhash_OPSLIMIT_MODERATE crypto_pwhash_argon2id_OPSLIMIT_MODERATE -SODIUM_EXPORT -size_t crypto_pwhash_opslimit_moderate(void); - -#define crypto_pwhash_MEMLIMIT_MODERATE crypto_pwhash_argon2id_MEMLIMIT_MODERATE -SODIUM_EXPORT -size_t crypto_pwhash_memlimit_moderate(void); - -#define crypto_pwhash_OPSLIMIT_SENSITIVE crypto_pwhash_argon2id_OPSLIMIT_SENSITIVE -SODIUM_EXPORT -size_t crypto_pwhash_opslimit_sensitive(void); - -#define crypto_pwhash_MEMLIMIT_SENSITIVE crypto_pwhash_argon2id_MEMLIMIT_SENSITIVE -SODIUM_EXPORT -size_t crypto_pwhash_memlimit_sensitive(void); - -/* - * With this function, do not forget to store all parameters, including the - * algorithm identifier in order to produce deterministic output. - * The crypto_pwhash_* definitions, including crypto_pwhash_ALG_DEFAULT, - * may change. - */ -SODIUM_EXPORT -int crypto_pwhash(unsigned char * const out, unsigned long long outlen, - const char * const passwd, unsigned long long passwdlen, - const unsigned char * const salt, - unsigned long long opslimit, size_t memlimit, int alg) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -/* - * The output string already includes all the required parameters, including - * the algorithm identifier. The string is all that has to be stored in - * order to verify a password. - */ -SODIUM_EXPORT -int crypto_pwhash_str(char out[crypto_pwhash_STRBYTES], - const char * const passwd, unsigned long long passwdlen, - unsigned long long opslimit, size_t memlimit) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_pwhash_str_alg(char out[crypto_pwhash_STRBYTES], - const char * const passwd, unsigned long long passwdlen, - unsigned long long opslimit, size_t memlimit, int alg) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_pwhash_str_verify(const char str[crypto_pwhash_STRBYTES], - const char * const passwd, - unsigned long long passwdlen) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_pwhash_str_needs_rehash(const char str[crypto_pwhash_STRBYTES], - unsigned long long opslimit, size_t memlimit) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -#define crypto_pwhash_PRIMITIVE "argon2i" -SODIUM_EXPORT -const char *crypto_pwhash_primitive(void) - __attribute__ ((warn_unused_result)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_pwhash_argon2i.h b/win32/include/sodium/crypto_pwhash_argon2i.h deleted file mode 100644 index 88ff6221d6..0000000000 --- a/win32/include/sodium/crypto_pwhash_argon2i.h +++ /dev/null @@ -1,122 +0,0 @@ -#ifndef crypto_pwhash_argon2i_H -#define crypto_pwhash_argon2i_H - -#include -#include -#include - -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_pwhash_argon2i_ALG_ARGON2I13 1 -SODIUM_EXPORT -int crypto_pwhash_argon2i_alg_argon2i13(void); - -#define crypto_pwhash_argon2i_BYTES_MIN 16U -SODIUM_EXPORT -size_t crypto_pwhash_argon2i_bytes_min(void); - -#define crypto_pwhash_argon2i_BYTES_MAX SODIUM_MIN(SODIUM_SIZE_MAX, 4294967295U) -SODIUM_EXPORT -size_t crypto_pwhash_argon2i_bytes_max(void); - -#define crypto_pwhash_argon2i_PASSWD_MIN 0U -SODIUM_EXPORT -size_t crypto_pwhash_argon2i_passwd_min(void); - -#define crypto_pwhash_argon2i_PASSWD_MAX 4294967295U -SODIUM_EXPORT -size_t crypto_pwhash_argon2i_passwd_max(void); - -#define crypto_pwhash_argon2i_SALTBYTES 16U -SODIUM_EXPORT -size_t crypto_pwhash_argon2i_saltbytes(void); - -#define crypto_pwhash_argon2i_STRBYTES 128U -SODIUM_EXPORT -size_t crypto_pwhash_argon2i_strbytes(void); - -#define crypto_pwhash_argon2i_STRPREFIX "$argon2i$" -SODIUM_EXPORT -const char *crypto_pwhash_argon2i_strprefix(void); - -#define crypto_pwhash_argon2i_OPSLIMIT_MIN 3U -SODIUM_EXPORT -size_t crypto_pwhash_argon2i_opslimit_min(void); - -#define crypto_pwhash_argon2i_OPSLIMIT_MAX 4294967295U -SODIUM_EXPORT -size_t crypto_pwhash_argon2i_opslimit_max(void); - -#define crypto_pwhash_argon2i_MEMLIMIT_MIN 8192U -SODIUM_EXPORT -size_t crypto_pwhash_argon2i_memlimit_min(void); - -#define crypto_pwhash_argon2i_MEMLIMIT_MAX \ - ((SIZE_MAX >= 4398046510080U) ? 4398046510080U : (SIZE_MAX >= 2147483648U) ? 2147483648U : 32768U) -SODIUM_EXPORT -size_t crypto_pwhash_argon2i_memlimit_max(void); - -#define crypto_pwhash_argon2i_OPSLIMIT_INTERACTIVE 4U -SODIUM_EXPORT -size_t crypto_pwhash_argon2i_opslimit_interactive(void); - -#define crypto_pwhash_argon2i_MEMLIMIT_INTERACTIVE 33554432U -SODIUM_EXPORT -size_t crypto_pwhash_argon2i_memlimit_interactive(void); - -#define crypto_pwhash_argon2i_OPSLIMIT_MODERATE 6U -SODIUM_EXPORT -size_t crypto_pwhash_argon2i_opslimit_moderate(void); - -#define crypto_pwhash_argon2i_MEMLIMIT_MODERATE 134217728U -SODIUM_EXPORT -size_t crypto_pwhash_argon2i_memlimit_moderate(void); - -#define crypto_pwhash_argon2i_OPSLIMIT_SENSITIVE 8U -SODIUM_EXPORT -size_t crypto_pwhash_argon2i_opslimit_sensitive(void); - -#define crypto_pwhash_argon2i_MEMLIMIT_SENSITIVE 536870912U -SODIUM_EXPORT -size_t crypto_pwhash_argon2i_memlimit_sensitive(void); - -SODIUM_EXPORT -int crypto_pwhash_argon2i(unsigned char * const out, - unsigned long long outlen, - const char * const passwd, - unsigned long long passwdlen, - const unsigned char * const salt, - unsigned long long opslimit, size_t memlimit, - int alg) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_pwhash_argon2i_str(char out[crypto_pwhash_argon2i_STRBYTES], - const char * const passwd, - unsigned long long passwdlen, - unsigned long long opslimit, size_t memlimit) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_pwhash_argon2i_str_verify(const char str[crypto_pwhash_argon2i_STRBYTES], - const char * const passwd, - unsigned long long passwdlen) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_pwhash_argon2i_str_needs_rehash(const char str[crypto_pwhash_argon2i_STRBYTES], - unsigned long long opslimit, size_t memlimit) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_pwhash_argon2id.h b/win32/include/sodium/crypto_pwhash_argon2id.h deleted file mode 100644 index 7183abd186..0000000000 --- a/win32/include/sodium/crypto_pwhash_argon2id.h +++ /dev/null @@ -1,122 +0,0 @@ -#ifndef crypto_pwhash_argon2id_H -#define crypto_pwhash_argon2id_H - -#include -#include -#include - -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_pwhash_argon2id_ALG_ARGON2ID13 2 -SODIUM_EXPORT -int crypto_pwhash_argon2id_alg_argon2id13(void); - -#define crypto_pwhash_argon2id_BYTES_MIN 16U -SODIUM_EXPORT -size_t crypto_pwhash_argon2id_bytes_min(void); - -#define crypto_pwhash_argon2id_BYTES_MAX SODIUM_MIN(SODIUM_SIZE_MAX, 4294967295U) -SODIUM_EXPORT -size_t crypto_pwhash_argon2id_bytes_max(void); - -#define crypto_pwhash_argon2id_PASSWD_MIN 0U -SODIUM_EXPORT -size_t crypto_pwhash_argon2id_passwd_min(void); - -#define crypto_pwhash_argon2id_PASSWD_MAX 4294967295U -SODIUM_EXPORT -size_t crypto_pwhash_argon2id_passwd_max(void); - -#define crypto_pwhash_argon2id_SALTBYTES 16U -SODIUM_EXPORT -size_t crypto_pwhash_argon2id_saltbytes(void); - -#define crypto_pwhash_argon2id_STRBYTES 128U -SODIUM_EXPORT -size_t crypto_pwhash_argon2id_strbytes(void); - -#define crypto_pwhash_argon2id_STRPREFIX "$argon2id$" -SODIUM_EXPORT -const char *crypto_pwhash_argon2id_strprefix(void); - -#define crypto_pwhash_argon2id_OPSLIMIT_MIN 1U -SODIUM_EXPORT -size_t crypto_pwhash_argon2id_opslimit_min(void); - -#define crypto_pwhash_argon2id_OPSLIMIT_MAX 4294967295U -SODIUM_EXPORT -size_t crypto_pwhash_argon2id_opslimit_max(void); - -#define crypto_pwhash_argon2id_MEMLIMIT_MIN 8192U -SODIUM_EXPORT -size_t crypto_pwhash_argon2id_memlimit_min(void); - -#define crypto_pwhash_argon2id_MEMLIMIT_MAX \ - ((SIZE_MAX >= 4398046510080U) ? 4398046510080U : (SIZE_MAX >= 2147483648U) ? 2147483648U : 32768U) -SODIUM_EXPORT -size_t crypto_pwhash_argon2id_memlimit_max(void); - -#define crypto_pwhash_argon2id_OPSLIMIT_INTERACTIVE 2U -SODIUM_EXPORT -size_t crypto_pwhash_argon2id_opslimit_interactive(void); - -#define crypto_pwhash_argon2id_MEMLIMIT_INTERACTIVE 67108864U -SODIUM_EXPORT -size_t crypto_pwhash_argon2id_memlimit_interactive(void); - -#define crypto_pwhash_argon2id_OPSLIMIT_MODERATE 3U -SODIUM_EXPORT -size_t crypto_pwhash_argon2id_opslimit_moderate(void); - -#define crypto_pwhash_argon2id_MEMLIMIT_MODERATE 268435456U -SODIUM_EXPORT -size_t crypto_pwhash_argon2id_memlimit_moderate(void); - -#define crypto_pwhash_argon2id_OPSLIMIT_SENSITIVE 4U -SODIUM_EXPORT -size_t crypto_pwhash_argon2id_opslimit_sensitive(void); - -#define crypto_pwhash_argon2id_MEMLIMIT_SENSITIVE 1073741824U -SODIUM_EXPORT -size_t crypto_pwhash_argon2id_memlimit_sensitive(void); - -SODIUM_EXPORT -int crypto_pwhash_argon2id(unsigned char * const out, - unsigned long long outlen, - const char * const passwd, - unsigned long long passwdlen, - const unsigned char * const salt, - unsigned long long opslimit, size_t memlimit, - int alg) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_pwhash_argon2id_str(char out[crypto_pwhash_argon2id_STRBYTES], - const char * const passwd, - unsigned long long passwdlen, - unsigned long long opslimit, size_t memlimit) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_pwhash_argon2id_str_verify(const char str[crypto_pwhash_argon2id_STRBYTES], - const char * const passwd, - unsigned long long passwdlen) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_pwhash_argon2id_str_needs_rehash(const char str[crypto_pwhash_argon2id_STRBYTES], - unsigned long long opslimit, size_t memlimit) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_pwhash_scryptsalsa208sha256.h b/win32/include/sodium/crypto_pwhash_scryptsalsa208sha256.h deleted file mode 100644 index 5c0bf7d390..0000000000 --- a/win32/include/sodium/crypto_pwhash_scryptsalsa208sha256.h +++ /dev/null @@ -1,120 +0,0 @@ -#ifndef crypto_pwhash_scryptsalsa208sha256_H -#define crypto_pwhash_scryptsalsa208sha256_H - -#include -#include -#include - -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_pwhash_scryptsalsa208sha256_BYTES_MIN 16U -SODIUM_EXPORT -size_t crypto_pwhash_scryptsalsa208sha256_bytes_min(void); - -#define crypto_pwhash_scryptsalsa208sha256_BYTES_MAX \ - SODIUM_MIN(SODIUM_SIZE_MAX, 0x1fffffffe0ULL) -SODIUM_EXPORT -size_t crypto_pwhash_scryptsalsa208sha256_bytes_max(void); - -#define crypto_pwhash_scryptsalsa208sha256_PASSWD_MIN 0U -SODIUM_EXPORT -size_t crypto_pwhash_scryptsalsa208sha256_passwd_min(void); - -#define crypto_pwhash_scryptsalsa208sha256_PASSWD_MAX SODIUM_SIZE_MAX -SODIUM_EXPORT -size_t crypto_pwhash_scryptsalsa208sha256_passwd_max(void); - -#define crypto_pwhash_scryptsalsa208sha256_SALTBYTES 32U -SODIUM_EXPORT -size_t crypto_pwhash_scryptsalsa208sha256_saltbytes(void); - -#define crypto_pwhash_scryptsalsa208sha256_STRBYTES 102U -SODIUM_EXPORT -size_t crypto_pwhash_scryptsalsa208sha256_strbytes(void); - -#define crypto_pwhash_scryptsalsa208sha256_STRPREFIX "$7$" -SODIUM_EXPORT -const char *crypto_pwhash_scryptsalsa208sha256_strprefix(void); - -#define crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN 32768U -SODIUM_EXPORT -size_t crypto_pwhash_scryptsalsa208sha256_opslimit_min(void); - -#define crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX 4294967295U -SODIUM_EXPORT -size_t crypto_pwhash_scryptsalsa208sha256_opslimit_max(void); - -#define crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN 16777216U -SODIUM_EXPORT -size_t crypto_pwhash_scryptsalsa208sha256_memlimit_min(void); - -#define crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX \ - SODIUM_MIN(SIZE_MAX, 68719476736ULL) -SODIUM_EXPORT -size_t crypto_pwhash_scryptsalsa208sha256_memlimit_max(void); - -#define crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE 524288U -SODIUM_EXPORT -size_t crypto_pwhash_scryptsalsa208sha256_opslimit_interactive(void); - -#define crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE 16777216U -SODIUM_EXPORT -size_t crypto_pwhash_scryptsalsa208sha256_memlimit_interactive(void); - -#define crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE 33554432U -SODIUM_EXPORT -size_t crypto_pwhash_scryptsalsa208sha256_opslimit_sensitive(void); - -#define crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE 1073741824U -SODIUM_EXPORT -size_t crypto_pwhash_scryptsalsa208sha256_memlimit_sensitive(void); - -SODIUM_EXPORT -int crypto_pwhash_scryptsalsa208sha256(unsigned char * const out, - unsigned long long outlen, - const char * const passwd, - unsigned long long passwdlen, - const unsigned char * const salt, - unsigned long long opslimit, - size_t memlimit) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_pwhash_scryptsalsa208sha256_str(char out[crypto_pwhash_scryptsalsa208sha256_STRBYTES], - const char * const passwd, - unsigned long long passwdlen, - unsigned long long opslimit, - size_t memlimit) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_pwhash_scryptsalsa208sha256_str_verify(const char str[crypto_pwhash_scryptsalsa208sha256_STRBYTES], - const char * const passwd, - unsigned long long passwdlen) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_pwhash_scryptsalsa208sha256_ll(const uint8_t * passwd, size_t passwdlen, - const uint8_t * salt, size_t saltlen, - uint64_t N, uint32_t r, uint32_t p, - uint8_t * buf, size_t buflen) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_pwhash_scryptsalsa208sha256_str_needs_rehash(const char str[crypto_pwhash_scryptsalsa208sha256_STRBYTES], - unsigned long long opslimit, - size_t memlimit) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_scalarmult.h b/win32/include/sodium/crypto_scalarmult.h deleted file mode 100644 index 1c68585378..0000000000 --- a/win32/include/sodium/crypto_scalarmult.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef crypto_scalarmult_H -#define crypto_scalarmult_H - -#include - -#include "crypto_scalarmult_curve25519.h" -#include "export.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define crypto_scalarmult_BYTES crypto_scalarmult_curve25519_BYTES -SODIUM_EXPORT -size_t crypto_scalarmult_bytes(void); - -#define crypto_scalarmult_SCALARBYTES crypto_scalarmult_curve25519_SCALARBYTES -SODIUM_EXPORT -size_t crypto_scalarmult_scalarbytes(void); - -#define crypto_scalarmult_PRIMITIVE "curve25519" -SODIUM_EXPORT -const char *crypto_scalarmult_primitive(void); - -SODIUM_EXPORT -int crypto_scalarmult_base(unsigned char *q, const unsigned char *n) - __attribute__ ((nonnull)); - -/* - * NOTE: Do not use the result of this function directly for key exchange. - * - * Hash the result with the public keys in order to compute a shared - * secret key: H(q || client_pk || server_pk) - * - * Or unless this is not an option, use the crypto_kx() API instead. - */ -SODIUM_EXPORT -int crypto_scalarmult(unsigned char *q, const unsigned char *n, - const unsigned char *p) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_scalarmult_curve25519.h b/win32/include/sodium/crypto_scalarmult_curve25519.h deleted file mode 100644 index 60e9d0c5a4..0000000000 --- a/win32/include/sodium/crypto_scalarmult_curve25519.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef crypto_scalarmult_curve25519_H -#define crypto_scalarmult_curve25519_H - -#include - -#include "export.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define crypto_scalarmult_curve25519_BYTES 32U -SODIUM_EXPORT -size_t crypto_scalarmult_curve25519_bytes(void); - -#define crypto_scalarmult_curve25519_SCALARBYTES 32U -SODIUM_EXPORT -size_t crypto_scalarmult_curve25519_scalarbytes(void); - -/* - * NOTE: Do not use the result of this function directly for key exchange. - * - * Hash the result with the public keys in order to compute a shared - * secret key: H(q || client_pk || server_pk) - * - * Or unless this is not an option, use the crypto_kx() API instead. - */ -SODIUM_EXPORT -int crypto_scalarmult_curve25519(unsigned char *q, const unsigned char *n, - const unsigned char *p) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_scalarmult_curve25519_base(unsigned char *q, - const unsigned char *n) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_scalarmult_ed25519.h b/win32/include/sodium/crypto_scalarmult_ed25519.h deleted file mode 100644 index 2dfa4d7073..0000000000 --- a/win32/include/sodium/crypto_scalarmult_ed25519.h +++ /dev/null @@ -1,51 +0,0 @@ - -#ifndef crypto_scalarmult_ed25519_H -#define crypto_scalarmult_ed25519_H - -#include - -#include "export.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define crypto_scalarmult_ed25519_BYTES 32U -SODIUM_EXPORT -size_t crypto_scalarmult_ed25519_bytes(void); - -#define crypto_scalarmult_ed25519_SCALARBYTES 32U -SODIUM_EXPORT -size_t crypto_scalarmult_ed25519_scalarbytes(void); - -/* - * NOTE: Do not use the result of this function directly for key exchange. - * - * Hash the result with the public keys in order to compute a shared - * secret key: H(q || client_pk || server_pk) - * - * Or unless this is not an option, use the crypto_kx() API instead. - */ -SODIUM_EXPORT -int crypto_scalarmult_ed25519(unsigned char *q, const unsigned char *n, - const unsigned char *p) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_scalarmult_ed25519_noclamp(unsigned char *q, const unsigned char *n, - const unsigned char *p) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_scalarmult_ed25519_base(unsigned char *q, const unsigned char *n) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_scalarmult_ed25519_base_noclamp(unsigned char *q, const unsigned char *n) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_scalarmult_ristretto255.h b/win32/include/sodium/crypto_scalarmult_ristretto255.h deleted file mode 100644 index 40a45ccef0..0000000000 --- a/win32/include/sodium/crypto_scalarmult_ristretto255.h +++ /dev/null @@ -1,43 +0,0 @@ - -#ifndef crypto_scalarmult_ristretto255_H -#define crypto_scalarmult_ristretto255_H - -#include - -#include "export.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define crypto_scalarmult_ristretto255_BYTES 32U -SODIUM_EXPORT -size_t crypto_scalarmult_ristretto255_bytes(void); - -#define crypto_scalarmult_ristretto255_SCALARBYTES 32U -SODIUM_EXPORT -size_t crypto_scalarmult_ristretto255_scalarbytes(void); - -/* - * NOTE: Do not use the result of this function directly for key exchange. - * - * Hash the result with the public keys in order to compute a shared - * secret key: H(q || client_pk || server_pk) - * - * Or unless this is not an option, use the crypto_kx() API instead. - */ -SODIUM_EXPORT -int crypto_scalarmult_ristretto255(unsigned char *q, const unsigned char *n, - const unsigned char *p) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_scalarmult_ristretto255_base(unsigned char *q, - const unsigned char *n) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_secretbox.h b/win32/include/sodium/crypto_secretbox.h deleted file mode 100644 index 1d3709db12..0000000000 --- a/win32/include/sodium/crypto_secretbox.h +++ /dev/null @@ -1,93 +0,0 @@ -#ifndef crypto_secretbox_H -#define crypto_secretbox_H - -#include - -#include "crypto_secretbox_xsalsa20poly1305.h" -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_secretbox_KEYBYTES crypto_secretbox_xsalsa20poly1305_KEYBYTES -SODIUM_EXPORT -size_t crypto_secretbox_keybytes(void); - -#define crypto_secretbox_NONCEBYTES crypto_secretbox_xsalsa20poly1305_NONCEBYTES -SODIUM_EXPORT -size_t crypto_secretbox_noncebytes(void); - -#define crypto_secretbox_MACBYTES crypto_secretbox_xsalsa20poly1305_MACBYTES -SODIUM_EXPORT -size_t crypto_secretbox_macbytes(void); - -#define crypto_secretbox_PRIMITIVE "xsalsa20poly1305" -SODIUM_EXPORT -const char *crypto_secretbox_primitive(void); - -#define crypto_secretbox_MESSAGEBYTES_MAX crypto_secretbox_xsalsa20poly1305_MESSAGEBYTES_MAX -SODIUM_EXPORT -size_t crypto_secretbox_messagebytes_max(void); - -SODIUM_EXPORT -int crypto_secretbox_easy(unsigned char *c, const unsigned char *m, - unsigned long long mlen, const unsigned char *n, - const unsigned char *k) __attribute__ ((nonnull(1, 4, 5))); - -SODIUM_EXPORT -int crypto_secretbox_open_easy(unsigned char *m, const unsigned char *c, - unsigned long long clen, const unsigned char *n, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(2, 4, 5))); - -SODIUM_EXPORT -int crypto_secretbox_detached(unsigned char *c, unsigned char *mac, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *n, - const unsigned char *k) - __attribute__ ((nonnull(1, 2, 5, 6))); - -SODIUM_EXPORT -int crypto_secretbox_open_detached(unsigned char *m, - const unsigned char *c, - const unsigned char *mac, - unsigned long long clen, - const unsigned char *n, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(2, 3, 5, 6))); - -SODIUM_EXPORT -void crypto_secretbox_keygen(unsigned char k[crypto_secretbox_KEYBYTES]) - __attribute__ ((nonnull)); - -/* -- NaCl compatibility interface ; Requires padding -- */ - -#define crypto_secretbox_ZEROBYTES crypto_secretbox_xsalsa20poly1305_ZEROBYTES -SODIUM_EXPORT -size_t crypto_secretbox_zerobytes(void); - -#define crypto_secretbox_BOXZEROBYTES crypto_secretbox_xsalsa20poly1305_BOXZEROBYTES -SODIUM_EXPORT -size_t crypto_secretbox_boxzerobytes(void); - -SODIUM_EXPORT -int crypto_secretbox(unsigned char *c, const unsigned char *m, - unsigned long long mlen, const unsigned char *n, - const unsigned char *k) __attribute__ ((nonnull(1, 4, 5))); - -SODIUM_EXPORT -int crypto_secretbox_open(unsigned char *m, const unsigned char *c, - unsigned long long clen, const unsigned char *n, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(2, 4, 5))); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_secretbox_xchacha20poly1305.h b/win32/include/sodium/crypto_secretbox_xchacha20poly1305.h deleted file mode 100644 index 6ec674e310..0000000000 --- a/win32/include/sodium/crypto_secretbox_xchacha20poly1305.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef crypto_secretbox_xchacha20poly1305_H -#define crypto_secretbox_xchacha20poly1305_H - -#include -#include "crypto_stream_xchacha20.h" -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_secretbox_xchacha20poly1305_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_secretbox_xchacha20poly1305_keybytes(void); - -#define crypto_secretbox_xchacha20poly1305_NONCEBYTES 24U -SODIUM_EXPORT -size_t crypto_secretbox_xchacha20poly1305_noncebytes(void); - -#define crypto_secretbox_xchacha20poly1305_MACBYTES 16U -SODIUM_EXPORT -size_t crypto_secretbox_xchacha20poly1305_macbytes(void); - -#define crypto_secretbox_xchacha20poly1305_MESSAGEBYTES_MAX \ - (crypto_stream_xchacha20_MESSAGEBYTES_MAX - crypto_secretbox_xchacha20poly1305_MACBYTES) -SODIUM_EXPORT -size_t crypto_secretbox_xchacha20poly1305_messagebytes_max(void); - -SODIUM_EXPORT -int crypto_secretbox_xchacha20poly1305_easy(unsigned char *c, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *n, - const unsigned char *k) - __attribute__ ((nonnull(1, 4, 5))); - -SODIUM_EXPORT -int crypto_secretbox_xchacha20poly1305_open_easy(unsigned char *m, - const unsigned char *c, - unsigned long long clen, - const unsigned char *n, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(2, 4, 5))); - -SODIUM_EXPORT -int crypto_secretbox_xchacha20poly1305_detached(unsigned char *c, - unsigned char *mac, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *n, - const unsigned char *k) - __attribute__ ((nonnull(1, 2, 5, 6))); - -SODIUM_EXPORT -int crypto_secretbox_xchacha20poly1305_open_detached(unsigned char *m, - const unsigned char *c, - const unsigned char *mac, - unsigned long long clen, - const unsigned char *n, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(2, 3, 5, 6))); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_secretbox_xsalsa20poly1305.h b/win32/include/sodium/crypto_secretbox_xsalsa20poly1305.h deleted file mode 100644 index be0874cbaf..0000000000 --- a/win32/include/sodium/crypto_secretbox_xsalsa20poly1305.h +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef crypto_secretbox_xsalsa20poly1305_H -#define crypto_secretbox_xsalsa20poly1305_H - -#include -#include "crypto_stream_xsalsa20.h" -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_secretbox_xsalsa20poly1305_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_secretbox_xsalsa20poly1305_keybytes(void); - -#define crypto_secretbox_xsalsa20poly1305_NONCEBYTES 24U -SODIUM_EXPORT -size_t crypto_secretbox_xsalsa20poly1305_noncebytes(void); - -#define crypto_secretbox_xsalsa20poly1305_MACBYTES 16U -SODIUM_EXPORT -size_t crypto_secretbox_xsalsa20poly1305_macbytes(void); - -/* Only for the libsodium API - The NaCl compatibility API would require BOXZEROBYTES extra bytes */ -#define crypto_secretbox_xsalsa20poly1305_MESSAGEBYTES_MAX \ - (crypto_stream_xsalsa20_MESSAGEBYTES_MAX - crypto_secretbox_xsalsa20poly1305_MACBYTES) -SODIUM_EXPORT -size_t crypto_secretbox_xsalsa20poly1305_messagebytes_max(void); - -SODIUM_EXPORT -int crypto_secretbox_xsalsa20poly1305(unsigned char *c, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *n, - const unsigned char *k) - __attribute__ ((nonnull(1, 4, 5))); - -SODIUM_EXPORT -int crypto_secretbox_xsalsa20poly1305_open(unsigned char *m, - const unsigned char *c, - unsigned long long clen, - const unsigned char *n, - const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(2, 4, 5))); - -SODIUM_EXPORT -void crypto_secretbox_xsalsa20poly1305_keygen(unsigned char k[crypto_secretbox_xsalsa20poly1305_KEYBYTES]) - __attribute__ ((nonnull)); - -/* -- NaCl compatibility interface ; Requires padding -- */ - -#define crypto_secretbox_xsalsa20poly1305_BOXZEROBYTES 16U -SODIUM_EXPORT -size_t crypto_secretbox_xsalsa20poly1305_boxzerobytes(void); - -#define crypto_secretbox_xsalsa20poly1305_ZEROBYTES \ - (crypto_secretbox_xsalsa20poly1305_BOXZEROBYTES + \ - crypto_secretbox_xsalsa20poly1305_MACBYTES) -SODIUM_EXPORT -size_t crypto_secretbox_xsalsa20poly1305_zerobytes(void); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_secretstream_xchacha20poly1305.h b/win32/include/sodium/crypto_secretstream_xchacha20poly1305.h deleted file mode 100644 index b22e4e9313..0000000000 --- a/win32/include/sodium/crypto_secretstream_xchacha20poly1305.h +++ /dev/null @@ -1,108 +0,0 @@ -#ifndef crypto_secretstream_xchacha20poly1305_H -#define crypto_secretstream_xchacha20poly1305_H - -#include - -#include "crypto_aead_xchacha20poly1305.h" -#include "crypto_stream_chacha20.h" -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_secretstream_xchacha20poly1305_ABYTES \ - (1U + crypto_aead_xchacha20poly1305_ietf_ABYTES) -SODIUM_EXPORT -size_t crypto_secretstream_xchacha20poly1305_abytes(void); - -#define crypto_secretstream_xchacha20poly1305_HEADERBYTES \ - crypto_aead_xchacha20poly1305_ietf_NPUBBYTES -SODIUM_EXPORT -size_t crypto_secretstream_xchacha20poly1305_headerbytes(void); - -#define crypto_secretstream_xchacha20poly1305_KEYBYTES \ - crypto_aead_xchacha20poly1305_ietf_KEYBYTES -SODIUM_EXPORT -size_t crypto_secretstream_xchacha20poly1305_keybytes(void); - -#define crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX \ - SODIUM_MIN(SODIUM_SIZE_MAX - crypto_secretstream_xchacha20poly1305_ABYTES, \ - (64ULL * ((1ULL << 32) - 2ULL))) -SODIUM_EXPORT -size_t crypto_secretstream_xchacha20poly1305_messagebytes_max(void); - -#define crypto_secretstream_xchacha20poly1305_TAG_MESSAGE 0x00 -SODIUM_EXPORT -unsigned char crypto_secretstream_xchacha20poly1305_tag_message(void); - -#define crypto_secretstream_xchacha20poly1305_TAG_PUSH 0x01 -SODIUM_EXPORT -unsigned char crypto_secretstream_xchacha20poly1305_tag_push(void); - -#define crypto_secretstream_xchacha20poly1305_TAG_REKEY 0x02 -SODIUM_EXPORT -unsigned char crypto_secretstream_xchacha20poly1305_tag_rekey(void); - -#define crypto_secretstream_xchacha20poly1305_TAG_FINAL \ - (crypto_secretstream_xchacha20poly1305_TAG_PUSH | \ - crypto_secretstream_xchacha20poly1305_TAG_REKEY) -SODIUM_EXPORT -unsigned char crypto_secretstream_xchacha20poly1305_tag_final(void); - -typedef struct crypto_secretstream_xchacha20poly1305_state { - unsigned char k[crypto_stream_chacha20_ietf_KEYBYTES]; - unsigned char nonce[crypto_stream_chacha20_ietf_NONCEBYTES]; - unsigned char _pad[8]; -} crypto_secretstream_xchacha20poly1305_state; - -SODIUM_EXPORT -size_t crypto_secretstream_xchacha20poly1305_statebytes(void); - -SODIUM_EXPORT -void crypto_secretstream_xchacha20poly1305_keygen - (unsigned char k[crypto_secretstream_xchacha20poly1305_KEYBYTES]) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_secretstream_xchacha20poly1305_init_push - (crypto_secretstream_xchacha20poly1305_state *state, - unsigned char header[crypto_secretstream_xchacha20poly1305_HEADERBYTES], - const unsigned char k[crypto_secretstream_xchacha20poly1305_KEYBYTES]) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_secretstream_xchacha20poly1305_push - (crypto_secretstream_xchacha20poly1305_state *state, - unsigned char *c, unsigned long long *clen_p, - const unsigned char *m, unsigned long long mlen, - const unsigned char *ad, unsigned long long adlen, unsigned char tag) - __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int crypto_secretstream_xchacha20poly1305_init_pull - (crypto_secretstream_xchacha20poly1305_state *state, - const unsigned char header[crypto_secretstream_xchacha20poly1305_HEADERBYTES], - const unsigned char k[crypto_secretstream_xchacha20poly1305_KEYBYTES]) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_secretstream_xchacha20poly1305_pull - (crypto_secretstream_xchacha20poly1305_state *state, - unsigned char *m, unsigned long long *mlen_p, unsigned char *tag_p, - const unsigned char *c, unsigned long long clen, - const unsigned char *ad, unsigned long long adlen) - __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -void crypto_secretstream_xchacha20poly1305_rekey - (crypto_secretstream_xchacha20poly1305_state *state); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_shorthash.h b/win32/include/sodium/crypto_shorthash.h deleted file mode 100644 index fecaa88bd8..0000000000 --- a/win32/include/sodium/crypto_shorthash.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef crypto_shorthash_H -#define crypto_shorthash_H - -#include - -#include "crypto_shorthash_siphash24.h" -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_shorthash_BYTES crypto_shorthash_siphash24_BYTES -SODIUM_EXPORT -size_t crypto_shorthash_bytes(void); - -#define crypto_shorthash_KEYBYTES crypto_shorthash_siphash24_KEYBYTES -SODIUM_EXPORT -size_t crypto_shorthash_keybytes(void); - -#define crypto_shorthash_PRIMITIVE "siphash24" -SODIUM_EXPORT -const char *crypto_shorthash_primitive(void); - -SODIUM_EXPORT -int crypto_shorthash(unsigned char *out, const unsigned char *in, - unsigned long long inlen, const unsigned char *k) - __attribute__ ((nonnull(1, 4))); - -SODIUM_EXPORT -void crypto_shorthash_keygen(unsigned char k[crypto_shorthash_KEYBYTES]) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_shorthash_siphash24.h b/win32/include/sodium/crypto_shorthash_siphash24.h deleted file mode 100644 index 1e6f72a620..0000000000 --- a/win32/include/sodium/crypto_shorthash_siphash24.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef crypto_shorthash_siphash24_H -#define crypto_shorthash_siphash24_H - -#include -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -/* -- 64-bit output -- */ - -#define crypto_shorthash_siphash24_BYTES 8U -SODIUM_EXPORT -size_t crypto_shorthash_siphash24_bytes(void); - -#define crypto_shorthash_siphash24_KEYBYTES 16U -SODIUM_EXPORT -size_t crypto_shorthash_siphash24_keybytes(void); - -SODIUM_EXPORT -int crypto_shorthash_siphash24(unsigned char *out, const unsigned char *in, - unsigned long long inlen, const unsigned char *k) - __attribute__ ((nonnull(1, 4))); - -#ifndef SODIUM_LIBRARY_MINIMAL -/* -- 128-bit output -- */ - -#define crypto_shorthash_siphashx24_BYTES 16U -SODIUM_EXPORT -size_t crypto_shorthash_siphashx24_bytes(void); - -#define crypto_shorthash_siphashx24_KEYBYTES 16U -SODIUM_EXPORT -size_t crypto_shorthash_siphashx24_keybytes(void); - -SODIUM_EXPORT -int crypto_shorthash_siphashx24(unsigned char *out, const unsigned char *in, - unsigned long long inlen, const unsigned char *k) - __attribute__ ((nonnull(1, 4))); -#endif - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_sign.h b/win32/include/sodium/crypto_sign.h deleted file mode 100644 index f5fafb123e..0000000000 --- a/win32/include/sodium/crypto_sign.h +++ /dev/null @@ -1,107 +0,0 @@ -#ifndef crypto_sign_H -#define crypto_sign_H - -/* - * THREAD SAFETY: crypto_sign_keypair() is thread-safe, - * provided that sodium_init() was called before. - * - * Other functions, including crypto_sign_seed_keypair() are always thread-safe. - */ - -#include - -#include "crypto_sign_ed25519.h" -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -typedef crypto_sign_ed25519ph_state crypto_sign_state; - -SODIUM_EXPORT -size_t crypto_sign_statebytes(void); - -#define crypto_sign_BYTES crypto_sign_ed25519_BYTES -SODIUM_EXPORT -size_t crypto_sign_bytes(void); - -#define crypto_sign_SEEDBYTES crypto_sign_ed25519_SEEDBYTES -SODIUM_EXPORT -size_t crypto_sign_seedbytes(void); - -#define crypto_sign_PUBLICKEYBYTES crypto_sign_ed25519_PUBLICKEYBYTES -SODIUM_EXPORT -size_t crypto_sign_publickeybytes(void); - -#define crypto_sign_SECRETKEYBYTES crypto_sign_ed25519_SECRETKEYBYTES -SODIUM_EXPORT -size_t crypto_sign_secretkeybytes(void); - -#define crypto_sign_MESSAGEBYTES_MAX crypto_sign_ed25519_MESSAGEBYTES_MAX -SODIUM_EXPORT -size_t crypto_sign_messagebytes_max(void); - -#define crypto_sign_PRIMITIVE "ed25519" -SODIUM_EXPORT -const char *crypto_sign_primitive(void); - -SODIUM_EXPORT -int crypto_sign_seed_keypair(unsigned char *pk, unsigned char *sk, - const unsigned char *seed) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_sign_keypair(unsigned char *pk, unsigned char *sk) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_sign(unsigned char *sm, unsigned long long *smlen_p, - const unsigned char *m, unsigned long long mlen, - const unsigned char *sk) __attribute__ ((nonnull(1, 5))); - -SODIUM_EXPORT -int crypto_sign_open(unsigned char *m, unsigned long long *mlen_p, - const unsigned char *sm, unsigned long long smlen, - const unsigned char *pk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(3, 5))); - -SODIUM_EXPORT -int crypto_sign_detached(unsigned char *sig, unsigned long long *siglen_p, - const unsigned char *m, unsigned long long mlen, - const unsigned char *sk) __attribute__ ((nonnull(1, 5))); - -SODIUM_EXPORT -int crypto_sign_verify_detached(const unsigned char *sig, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *pk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(1, 4))); - -SODIUM_EXPORT -int crypto_sign_init(crypto_sign_state *state); - -SODIUM_EXPORT -int crypto_sign_update(crypto_sign_state *state, - const unsigned char *m, unsigned long long mlen) - __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int crypto_sign_final_create(crypto_sign_state *state, unsigned char *sig, - unsigned long long *siglen_p, - const unsigned char *sk) - __attribute__ ((nonnull(1, 2, 4))); - -SODIUM_EXPORT -int crypto_sign_final_verify(crypto_sign_state *state, const unsigned char *sig, - const unsigned char *pk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_sign_ed25519.h b/win32/include/sodium/crypto_sign_ed25519.h deleted file mode 100644 index 0fdac42d35..0000000000 --- a/win32/include/sodium/crypto_sign_ed25519.h +++ /dev/null @@ -1,124 +0,0 @@ -#ifndef crypto_sign_ed25519_H -#define crypto_sign_ed25519_H - -#include -#include "crypto_hash_sha512.h" -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -typedef struct crypto_sign_ed25519ph_state { - crypto_hash_sha512_state hs; -} crypto_sign_ed25519ph_state; - -SODIUM_EXPORT -size_t crypto_sign_ed25519ph_statebytes(void); - -#define crypto_sign_ed25519_BYTES 64U -SODIUM_EXPORT -size_t crypto_sign_ed25519_bytes(void); - -#define crypto_sign_ed25519_SEEDBYTES 32U -SODIUM_EXPORT -size_t crypto_sign_ed25519_seedbytes(void); - -#define crypto_sign_ed25519_PUBLICKEYBYTES 32U -SODIUM_EXPORT -size_t crypto_sign_ed25519_publickeybytes(void); - -#define crypto_sign_ed25519_SECRETKEYBYTES (32U + 32U) -SODIUM_EXPORT -size_t crypto_sign_ed25519_secretkeybytes(void); - -#define crypto_sign_ed25519_MESSAGEBYTES_MAX (SODIUM_SIZE_MAX - crypto_sign_ed25519_BYTES) -SODIUM_EXPORT -size_t crypto_sign_ed25519_messagebytes_max(void); - -SODIUM_EXPORT -int crypto_sign_ed25519(unsigned char *sm, unsigned long long *smlen_p, - const unsigned char *m, unsigned long long mlen, - const unsigned char *sk) - __attribute__ ((nonnull(1, 5))); - -SODIUM_EXPORT -int crypto_sign_ed25519_open(unsigned char *m, unsigned long long *mlen_p, - const unsigned char *sm, unsigned long long smlen, - const unsigned char *pk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(3, 5))); - -SODIUM_EXPORT -int crypto_sign_ed25519_detached(unsigned char *sig, - unsigned long long *siglen_p, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *sk) - __attribute__ ((nonnull(1, 5))); - -SODIUM_EXPORT -int crypto_sign_ed25519_verify_detached(const unsigned char *sig, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *pk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(1, 4))); - -SODIUM_EXPORT -int crypto_sign_ed25519_keypair(unsigned char *pk, unsigned char *sk) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_sign_ed25519_seed_keypair(unsigned char *pk, unsigned char *sk, - const unsigned char *seed) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_sign_ed25519_pk_to_curve25519(unsigned char *curve25519_pk, - const unsigned char *ed25519_pk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_sign_ed25519_sk_to_curve25519(unsigned char *curve25519_sk, - const unsigned char *ed25519_sk) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_sign_ed25519_sk_to_seed(unsigned char *seed, - const unsigned char *sk) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_sign_ed25519_sk_to_pk(unsigned char *pk, const unsigned char *sk) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_sign_ed25519ph_init(crypto_sign_ed25519ph_state *state) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_sign_ed25519ph_update(crypto_sign_ed25519ph_state *state, - const unsigned char *m, - unsigned long long mlen) - __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int crypto_sign_ed25519ph_final_create(crypto_sign_ed25519ph_state *state, - unsigned char *sig, - unsigned long long *siglen_p, - const unsigned char *sk) - __attribute__ ((nonnull(1, 2, 4))); - -SODIUM_EXPORT -int crypto_sign_ed25519ph_final_verify(crypto_sign_ed25519ph_state *state, - const unsigned char *sig, - const unsigned char *pk) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_sign_edwards25519sha512batch.h b/win32/include/sodium/crypto_sign_edwards25519sha512batch.h deleted file mode 100644 index eed158aa84..0000000000 --- a/win32/include/sodium/crypto_sign_edwards25519sha512batch.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef crypto_sign_edwards25519sha512batch_H -#define crypto_sign_edwards25519sha512batch_H - -/* - * WARNING: This construction was a prototype, which should not be used - * any more in new projects. - * - * crypto_sign_edwards25519sha512batch is provided for applications - * initially built with NaCl, but as recommended by the author of this - * construction, new applications should use ed25519 instead. - * - * In Sodium, you should use the high-level crypto_sign_*() functions instead. - */ - -#include -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_sign_edwards25519sha512batch_BYTES 64U -#define crypto_sign_edwards25519sha512batch_PUBLICKEYBYTES 32U -#define crypto_sign_edwards25519sha512batch_SECRETKEYBYTES (32U + 32U) -#define crypto_sign_edwards25519sha512batch_MESSAGEBYTES_MAX (SODIUM_SIZE_MAX - crypto_sign_edwards25519sha512batch_BYTES) - -SODIUM_EXPORT -int crypto_sign_edwards25519sha512batch(unsigned char *sm, - unsigned long long *smlen_p, - const unsigned char *m, - unsigned long long mlen, - const unsigned char *sk) - __attribute__ ((deprecated)) __attribute__ ((nonnull(1, 5))); - -SODIUM_EXPORT -int crypto_sign_edwards25519sha512batch_open(unsigned char *m, - unsigned long long *mlen_p, - const unsigned char *sm, - unsigned long long smlen, - const unsigned char *pk) - __attribute__ ((deprecated)) __attribute__ ((nonnull(3, 5))); - -SODIUM_EXPORT -int crypto_sign_edwards25519sha512batch_keypair(unsigned char *pk, - unsigned char *sk) - __attribute__ ((deprecated)) __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_stream.h b/win32/include/sodium/crypto_stream.h deleted file mode 100644 index 88dab5f611..0000000000 --- a/win32/include/sodium/crypto_stream.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef crypto_stream_H -#define crypto_stream_H - -/* - * WARNING: This is just a stream cipher. It is NOT authenticated encryption. - * While it provides some protection against eavesdropping, it does NOT - * provide any security against active attacks. - * Unless you know what you're doing, what you are looking for is probably - * the crypto_box functions. - */ - -#include - -#include "crypto_stream_xsalsa20.h" -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_stream_KEYBYTES crypto_stream_xsalsa20_KEYBYTES -SODIUM_EXPORT -size_t crypto_stream_keybytes(void); - -#define crypto_stream_NONCEBYTES crypto_stream_xsalsa20_NONCEBYTES -SODIUM_EXPORT -size_t crypto_stream_noncebytes(void); - -#define crypto_stream_MESSAGEBYTES_MAX crypto_stream_xsalsa20_MESSAGEBYTES_MAX -SODIUM_EXPORT -size_t crypto_stream_messagebytes_max(void); - -#define crypto_stream_PRIMITIVE "xsalsa20" -SODIUM_EXPORT -const char *crypto_stream_primitive(void); - -SODIUM_EXPORT -int crypto_stream(unsigned char *c, unsigned long long clen, - const unsigned char *n, const unsigned char *k) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_stream_xor(unsigned char *c, const unsigned char *m, - unsigned long long mlen, const unsigned char *n, - const unsigned char *k) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_stream_keygen(unsigned char k[crypto_stream_KEYBYTES]) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_stream_chacha20.h b/win32/include/sodium/crypto_stream_chacha20.h deleted file mode 100644 index 408897558b..0000000000 --- a/win32/include/sodium/crypto_stream_chacha20.h +++ /dev/null @@ -1,106 +0,0 @@ -#ifndef crypto_stream_chacha20_H -#define crypto_stream_chacha20_H - -/* - * WARNING: This is just a stream cipher. It is NOT authenticated encryption. - * While it provides some protection against eavesdropping, it does NOT - * provide any security against active attacks. - * Unless you know what you're doing, what you are looking for is probably - * the crypto_box functions. - */ - -#include -#include -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_stream_chacha20_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_stream_chacha20_keybytes(void); - -#define crypto_stream_chacha20_NONCEBYTES 8U -SODIUM_EXPORT -size_t crypto_stream_chacha20_noncebytes(void); - -#define crypto_stream_chacha20_MESSAGEBYTES_MAX SODIUM_SIZE_MAX -SODIUM_EXPORT -size_t crypto_stream_chacha20_messagebytes_max(void); - -/* ChaCha20 with a 64-bit nonce and a 64-bit counter, as originally designed */ - -SODIUM_EXPORT -int crypto_stream_chacha20(unsigned char *c, unsigned long long clen, - const unsigned char *n, const unsigned char *k) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_stream_chacha20_xor(unsigned char *c, const unsigned char *m, - unsigned long long mlen, const unsigned char *n, - const unsigned char *k) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_stream_chacha20_xor_ic(unsigned char *c, const unsigned char *m, - unsigned long long mlen, - const unsigned char *n, uint64_t ic, - const unsigned char *k) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_stream_chacha20_keygen(unsigned char k[crypto_stream_chacha20_KEYBYTES]) - __attribute__ ((nonnull)); - -/* ChaCha20 with a 96-bit nonce and a 32-bit counter (IETF) */ - -#define crypto_stream_chacha20_ietf_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_stream_chacha20_ietf_keybytes(void); - -#define crypto_stream_chacha20_ietf_NONCEBYTES 12U -SODIUM_EXPORT -size_t crypto_stream_chacha20_ietf_noncebytes(void); - -#define crypto_stream_chacha20_ietf_MESSAGEBYTES_MAX \ - SODIUM_MIN(SODIUM_SIZE_MAX, 64ULL * (1ULL << 32)) -SODIUM_EXPORT -size_t crypto_stream_chacha20_ietf_messagebytes_max(void); - -SODIUM_EXPORT -int crypto_stream_chacha20_ietf(unsigned char *c, unsigned long long clen, - const unsigned char *n, const unsigned char *k) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_stream_chacha20_ietf_xor(unsigned char *c, const unsigned char *m, - unsigned long long mlen, const unsigned char *n, - const unsigned char *k) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_stream_chacha20_ietf_xor_ic(unsigned char *c, const unsigned char *m, - unsigned long long mlen, - const unsigned char *n, uint32_t ic, - const unsigned char *k) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_stream_chacha20_ietf_keygen(unsigned char k[crypto_stream_chacha20_ietf_KEYBYTES]) - __attribute__ ((nonnull)); - -/* Aliases */ - -#define crypto_stream_chacha20_IETF_KEYBYTES crypto_stream_chacha20_ietf_KEYBYTES -#define crypto_stream_chacha20_IETF_NONCEBYTES crypto_stream_chacha20_ietf_NONCEBYTES -#define crypto_stream_chacha20_IETF_MESSAGEBYTES_MAX crypto_stream_chacha20_ietf_MESSAGEBYTES_MAX - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_stream_salsa20.h b/win32/include/sodium/crypto_stream_salsa20.h deleted file mode 100644 index 45b3b3e34a..0000000000 --- a/win32/include/sodium/crypto_stream_salsa20.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef crypto_stream_salsa20_H -#define crypto_stream_salsa20_H - -/* - * WARNING: This is just a stream cipher. It is NOT authenticated encryption. - * While it provides some protection against eavesdropping, it does NOT - * provide any security against active attacks. - * Unless you know what you're doing, what you are looking for is probably - * the crypto_box functions. - */ - -#include -#include -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_stream_salsa20_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_stream_salsa20_keybytes(void); - -#define crypto_stream_salsa20_NONCEBYTES 8U -SODIUM_EXPORT -size_t crypto_stream_salsa20_noncebytes(void); - -#define crypto_stream_salsa20_MESSAGEBYTES_MAX SODIUM_SIZE_MAX -SODIUM_EXPORT -size_t crypto_stream_salsa20_messagebytes_max(void); - -SODIUM_EXPORT -int crypto_stream_salsa20(unsigned char *c, unsigned long long clen, - const unsigned char *n, const unsigned char *k) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_stream_salsa20_xor(unsigned char *c, const unsigned char *m, - unsigned long long mlen, const unsigned char *n, - const unsigned char *k) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_stream_salsa20_xor_ic(unsigned char *c, const unsigned char *m, - unsigned long long mlen, - const unsigned char *n, uint64_t ic, - const unsigned char *k) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_stream_salsa20_keygen(unsigned char k[crypto_stream_salsa20_KEYBYTES]) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_stream_salsa2012.h b/win32/include/sodium/crypto_stream_salsa2012.h deleted file mode 100644 index 6c5d303cac..0000000000 --- a/win32/include/sodium/crypto_stream_salsa2012.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef crypto_stream_salsa2012_H -#define crypto_stream_salsa2012_H - -/* - * WARNING: This is just a stream cipher. It is NOT authenticated encryption. - * While it provides some protection against eavesdropping, it does NOT - * provide any security against active attacks. - * Unless you know what you're doing, what you are looking for is probably - * the crypto_box functions. - */ - -#include -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_stream_salsa2012_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_stream_salsa2012_keybytes(void); - -#define crypto_stream_salsa2012_NONCEBYTES 8U -SODIUM_EXPORT -size_t crypto_stream_salsa2012_noncebytes(void); - -#define crypto_stream_salsa2012_MESSAGEBYTES_MAX SODIUM_SIZE_MAX -SODIUM_EXPORT -size_t crypto_stream_salsa2012_messagebytes_max(void); - -SODIUM_EXPORT -int crypto_stream_salsa2012(unsigned char *c, unsigned long long clen, - const unsigned char *n, const unsigned char *k) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_stream_salsa2012_xor(unsigned char *c, const unsigned char *m, - unsigned long long mlen, const unsigned char *n, - const unsigned char *k) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_stream_salsa2012_keygen(unsigned char k[crypto_stream_salsa2012_KEYBYTES]) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_stream_salsa208.h b/win32/include/sodium/crypto_stream_salsa208.h deleted file mode 100644 index d574f30478..0000000000 --- a/win32/include/sodium/crypto_stream_salsa208.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef crypto_stream_salsa208_H -#define crypto_stream_salsa208_H - -/* - * WARNING: This is just a stream cipher. It is NOT authenticated encryption. - * While it provides some protection against eavesdropping, it does NOT - * provide any security against active attacks. - * Unless you know what you're doing, what you are looking for is probably - * the crypto_box functions. - */ - -#include -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_stream_salsa208_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_stream_salsa208_keybytes(void) - __attribute__ ((deprecated)); - -#define crypto_stream_salsa208_NONCEBYTES 8U -SODIUM_EXPORT -size_t crypto_stream_salsa208_noncebytes(void) - __attribute__ ((deprecated)); - -#define crypto_stream_salsa208_MESSAGEBYTES_MAX SODIUM_SIZE_MAX - SODIUM_EXPORT -size_t crypto_stream_salsa208_messagebytes_max(void) - __attribute__ ((deprecated)); - -SODIUM_EXPORT -int crypto_stream_salsa208(unsigned char *c, unsigned long long clen, - const unsigned char *n, const unsigned char *k) - __attribute__ ((deprecated)) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_stream_salsa208_xor(unsigned char *c, const unsigned char *m, - unsigned long long mlen, const unsigned char *n, - const unsigned char *k) - __attribute__ ((deprecated)) __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_stream_salsa208_keygen(unsigned char k[crypto_stream_salsa208_KEYBYTES]) - __attribute__ ((deprecated)) __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_stream_xchacha20.h b/win32/include/sodium/crypto_stream_xchacha20.h deleted file mode 100644 index c4002db00a..0000000000 --- a/win32/include/sodium/crypto_stream_xchacha20.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef crypto_stream_xchacha20_H -#define crypto_stream_xchacha20_H - -/* - * WARNING: This is just a stream cipher. It is NOT authenticated encryption. - * While it provides some protection against eavesdropping, it does NOT - * provide any security against active attacks. - * Unless you know what you're doing, what you are looking for is probably - * the crypto_box functions. - */ - -#include -#include -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_stream_xchacha20_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_stream_xchacha20_keybytes(void); - -#define crypto_stream_xchacha20_NONCEBYTES 24U -SODIUM_EXPORT -size_t crypto_stream_xchacha20_noncebytes(void); - -#define crypto_stream_xchacha20_MESSAGEBYTES_MAX SODIUM_SIZE_MAX -SODIUM_EXPORT -size_t crypto_stream_xchacha20_messagebytes_max(void); - -SODIUM_EXPORT -int crypto_stream_xchacha20(unsigned char *c, unsigned long long clen, - const unsigned char *n, const unsigned char *k) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_stream_xchacha20_xor(unsigned char *c, const unsigned char *m, - unsigned long long mlen, const unsigned char *n, - const unsigned char *k) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_stream_xchacha20_xor_ic(unsigned char *c, const unsigned char *m, - unsigned long long mlen, - const unsigned char *n, uint64_t ic, - const unsigned char *k) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_stream_xchacha20_keygen(unsigned char k[crypto_stream_xchacha20_KEYBYTES]) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_stream_xsalsa20.h b/win32/include/sodium/crypto_stream_xsalsa20.h deleted file mode 100644 index 20034e3462..0000000000 --- a/win32/include/sodium/crypto_stream_xsalsa20.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef crypto_stream_xsalsa20_H -#define crypto_stream_xsalsa20_H - -/* - * WARNING: This is just a stream cipher. It is NOT authenticated encryption. - * While it provides some protection against eavesdropping, it does NOT - * provide any security against active attacks. - * Unless you know what you're doing, what you are looking for is probably - * the crypto_box functions. - */ - -#include -#include -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -#define crypto_stream_xsalsa20_KEYBYTES 32U -SODIUM_EXPORT -size_t crypto_stream_xsalsa20_keybytes(void); - -#define crypto_stream_xsalsa20_NONCEBYTES 24U -SODIUM_EXPORT -size_t crypto_stream_xsalsa20_noncebytes(void); - -#define crypto_stream_xsalsa20_MESSAGEBYTES_MAX SODIUM_SIZE_MAX -SODIUM_EXPORT -size_t crypto_stream_xsalsa20_messagebytes_max(void); - -SODIUM_EXPORT -int crypto_stream_xsalsa20(unsigned char *c, unsigned long long clen, - const unsigned char *n, const unsigned char *k) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_stream_xsalsa20_xor(unsigned char *c, const unsigned char *m, - unsigned long long mlen, const unsigned char *n, - const unsigned char *k) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int crypto_stream_xsalsa20_xor_ic(unsigned char *c, const unsigned char *m, - unsigned long long mlen, - const unsigned char *n, uint64_t ic, - const unsigned char *k) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void crypto_stream_xsalsa20_keygen(unsigned char k[crypto_stream_xsalsa20_KEYBYTES]) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_verify_16.h b/win32/include/sodium/crypto_verify_16.h deleted file mode 100644 index 7b9c8077ad..0000000000 --- a/win32/include/sodium/crypto_verify_16.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef crypto_verify_16_H -#define crypto_verify_16_H - -#include -#include "export.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define crypto_verify_16_BYTES 16U -SODIUM_EXPORT -size_t crypto_verify_16_bytes(void); - -SODIUM_EXPORT -int crypto_verify_16(const unsigned char *x, const unsigned char *y) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_verify_32.h b/win32/include/sodium/crypto_verify_32.h deleted file mode 100644 index 9b0f4529f6..0000000000 --- a/win32/include/sodium/crypto_verify_32.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef crypto_verify_32_H -#define crypto_verify_32_H - -#include -#include "export.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define crypto_verify_32_BYTES 32U -SODIUM_EXPORT -size_t crypto_verify_32_bytes(void); - -SODIUM_EXPORT -int crypto_verify_32(const unsigned char *x, const unsigned char *y) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/crypto_verify_64.h b/win32/include/sodium/crypto_verify_64.h deleted file mode 100644 index c83b73025a..0000000000 --- a/win32/include/sodium/crypto_verify_64.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef crypto_verify_64_H -#define crypto_verify_64_H - -#include -#include "export.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define crypto_verify_64_BYTES 64U -SODIUM_EXPORT -size_t crypto_verify_64_bytes(void); - -SODIUM_EXPORT -int crypto_verify_64(const unsigned char *x, const unsigned char *y) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/export.h b/win32/include/sodium/export.h deleted file mode 100644 index a0074fc9cb..0000000000 --- a/win32/include/sodium/export.h +++ /dev/null @@ -1,57 +0,0 @@ - -#ifndef sodium_export_H -#define sodium_export_H - -#include -#include -#include - -#if !defined(__clang__) && !defined(__GNUC__) -# ifdef __attribute__ -# undef __attribute__ -# endif -# define __attribute__(a) -#endif - -#ifdef SODIUM_STATIC -# define SODIUM_EXPORT -# define SODIUM_EXPORT_WEAK -#else -# if defined(_MSC_VER) -# ifdef SODIUM_DLL_EXPORT -# define SODIUM_EXPORT __declspec(dllexport) -# else -# define SODIUM_EXPORT __declspec(dllimport) -# endif -# else -# if defined(__SUNPRO_C) -# ifndef __GNU_C__ -# define SODIUM_EXPORT __attribute__ (visibility(__global)) -# else -# define SODIUM_EXPORT __attribute__ __global -# endif -# elif defined(_MSG_VER) -# define SODIUM_EXPORT extern __declspec(dllexport) -# else -# define SODIUM_EXPORT __attribute__ ((visibility ("default"))) -# endif -# endif -# if defined(__ELF__) && !defined(SODIUM_DISABLE_WEAK_FUNCTIONS) -# define SODIUM_EXPORT_WEAK SODIUM_EXPORT __attribute__((weak)) -# else -# define SODIUM_EXPORT_WEAK SODIUM_EXPORT -# endif -#endif - -#ifndef CRYPTO_ALIGN -# if defined(__INTEL_COMPILER) || defined(_MSC_VER) -# define CRYPTO_ALIGN(x) __declspec(align(x)) -# else -# define CRYPTO_ALIGN(x) __attribute__ ((aligned(x))) -# endif -#endif - -#define SODIUM_MIN(A, B) ((A) < (B) ? (A) : (B)) -#define SODIUM_SIZE_MAX SODIUM_MIN(UINT64_MAX, SIZE_MAX) - -#endif diff --git a/win32/include/sodium/randombytes.h b/win32/include/sodium/randombytes.h deleted file mode 100644 index a03cc65720..0000000000 --- a/win32/include/sodium/randombytes.h +++ /dev/null @@ -1,72 +0,0 @@ - -#ifndef randombytes_H -#define randombytes_H - -#include -#include - -#include - -#include "export.h" - -#ifdef __cplusplus -# ifdef __GNUC__ -# pragma GCC diagnostic ignored "-Wlong-long" -# endif -extern "C" { -#endif - -typedef struct randombytes_implementation { - const char *(*implementation_name)(void); /* required */ - uint32_t (*random)(void); /* required */ - void (*stir)(void); /* optional */ - uint32_t (*uniform)(const uint32_t upper_bound); /* optional, a default implementation will be used if NULL */ - void (*buf)(void * const buf, const size_t size); /* required */ - int (*close)(void); /* optional */ -} randombytes_implementation; - -#define randombytes_BYTES_MAX SODIUM_MIN(SODIUM_SIZE_MAX, 0xffffffffUL) - -#define randombytes_SEEDBYTES 32U -SODIUM_EXPORT -size_t randombytes_seedbytes(void); - -SODIUM_EXPORT -void randombytes_buf(void * const buf, const size_t size) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -void randombytes_buf_deterministic(void * const buf, const size_t size, - const unsigned char seed[randombytes_SEEDBYTES]) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -uint32_t randombytes_random(void); - -SODIUM_EXPORT -uint32_t randombytes_uniform(const uint32_t upper_bound); - -SODIUM_EXPORT -void randombytes_stir(void); - -SODIUM_EXPORT -int randombytes_close(void); - -SODIUM_EXPORT -int randombytes_set_implementation(randombytes_implementation *impl) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -const char *randombytes_implementation_name(void); - -/* -- NaCl compatibility interface -- */ - -SODIUM_EXPORT -void randombytes(unsigned char * const buf, const unsigned long long buf_len) - __attribute__ ((nonnull)); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/randombytes_internal_random.h b/win32/include/sodium/randombytes_internal_random.h deleted file mode 100644 index 2b2b7d6edc..0000000000 --- a/win32/include/sodium/randombytes_internal_random.h +++ /dev/null @@ -1,22 +0,0 @@ - -#ifndef randombytes_internal_random_H -#define randombytes_internal_random_H - -#include "export.h" -#include "randombytes.h" - -#ifdef __cplusplus -extern "C" { -#endif - -SODIUM_EXPORT -extern struct randombytes_implementation randombytes_internal_implementation; - -/* Backwards compatibility with libsodium < 1.0.18 */ -#define randombytes_salsa20_implementation randombytes_internal_implementation - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/randombytes_sysrandom.h b/win32/include/sodium/randombytes_sysrandom.h deleted file mode 100644 index 9e27b674c7..0000000000 --- a/win32/include/sodium/randombytes_sysrandom.h +++ /dev/null @@ -1,19 +0,0 @@ - -#ifndef randombytes_sysrandom_H -#define randombytes_sysrandom_H - -#include "export.h" -#include "randombytes.h" - -#ifdef __cplusplus -extern "C" { -#endif - -SODIUM_EXPORT -extern struct randombytes_implementation randombytes_sysrandom_implementation; - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/runtime.h b/win32/include/sodium/runtime.h deleted file mode 100644 index 7f15d58e7c..0000000000 --- a/win32/include/sodium/runtime.h +++ /dev/null @@ -1,52 +0,0 @@ - -#ifndef sodium_runtime_H -#define sodium_runtime_H - -#include "export.h" - -#ifdef __cplusplus -extern "C" { -#endif - -SODIUM_EXPORT_WEAK -int sodium_runtime_has_neon(void); - -SODIUM_EXPORT_WEAK -int sodium_runtime_has_sse2(void); - -SODIUM_EXPORT_WEAK -int sodium_runtime_has_sse3(void); - -SODIUM_EXPORT_WEAK -int sodium_runtime_has_ssse3(void); - -SODIUM_EXPORT_WEAK -int sodium_runtime_has_sse41(void); - -SODIUM_EXPORT_WEAK -int sodium_runtime_has_avx(void); - -SODIUM_EXPORT_WEAK -int sodium_runtime_has_avx2(void); - -SODIUM_EXPORT_WEAK -int sodium_runtime_has_avx512f(void); - -SODIUM_EXPORT_WEAK -int sodium_runtime_has_pclmul(void); - -SODIUM_EXPORT_WEAK -int sodium_runtime_has_aesni(void); - -SODIUM_EXPORT_WEAK -int sodium_runtime_has_rdrand(void); - -/* ------------------------------------------------------------------------- */ - -int _sodium_runtime_get_cpu_features(void); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/utils.h b/win32/include/sodium/utils.h deleted file mode 100644 index ac80151291..0000000000 --- a/win32/include/sodium/utils.h +++ /dev/null @@ -1,179 +0,0 @@ - -#ifndef sodium_utils_H -#define sodium_utils_H - -#include - -#include "export.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef SODIUM_C99 -# if defined(__cplusplus) || !defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L -# define SODIUM_C99(X) -# else -# define SODIUM_C99(X) X -# endif -#endif - -SODIUM_EXPORT -void sodium_memzero(void * const pnt, const size_t len); - -SODIUM_EXPORT -void sodium_stackzero(const size_t len); - -/* - * WARNING: sodium_memcmp() must be used to verify if two secret keys - * are equal, in constant time. - * It returns 0 if the keys are equal, and -1 if they differ. - * This function is not designed for lexicographical comparisons. - */ -SODIUM_EXPORT -int sodium_memcmp(const void * const b1_, const void * const b2_, size_t len) - __attribute__ ((warn_unused_result)); - -/* - * sodium_compare() returns -1 if b1_ < b2_, 1 if b1_ > b2_ and 0 if b1_ == b2_ - * It is suitable for lexicographical comparisons, or to compare nonces - * and counters stored in little-endian format. - * However, it is slower than sodium_memcmp(). - */ -SODIUM_EXPORT -int sodium_compare(const unsigned char *b1_, const unsigned char *b2_, - size_t len) __attribute__ ((warn_unused_result)); - -SODIUM_EXPORT -int sodium_is_zero(const unsigned char *n, const size_t nlen); - -SODIUM_EXPORT -void sodium_increment(unsigned char *n, const size_t nlen); - -SODIUM_EXPORT -void sodium_add(unsigned char *a, const unsigned char *b, const size_t len); - -SODIUM_EXPORT -void sodium_sub(unsigned char *a, const unsigned char *b, const size_t len); - -SODIUM_EXPORT -char *sodium_bin2hex(char * const hex, const size_t hex_maxlen, - const unsigned char * const bin, const size_t bin_len) - __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int sodium_hex2bin(unsigned char * const bin, const size_t bin_maxlen, - const char * const hex, const size_t hex_len, - const char * const ignore, size_t * const bin_len, - const char ** const hex_end) - __attribute__ ((nonnull(1))); - -#define sodium_base64_VARIANT_ORIGINAL 1 -#define sodium_base64_VARIANT_ORIGINAL_NO_PADDING 3 -#define sodium_base64_VARIANT_URLSAFE 5 -#define sodium_base64_VARIANT_URLSAFE_NO_PADDING 7 - -/* - * Computes the required length to encode BIN_LEN bytes as a base64 string - * using the given variant. The computed length includes a trailing \0. - */ -#define sodium_base64_ENCODED_LEN(BIN_LEN, VARIANT) \ - (((BIN_LEN) / 3U) * 4U + \ - ((((BIN_LEN) - ((BIN_LEN) / 3U) * 3U) | (((BIN_LEN) - ((BIN_LEN) / 3U) * 3U) >> 1)) & 1U) * \ - (4U - (~((((VARIANT) & 2U) >> 1) - 1U) & (3U - ((BIN_LEN) - ((BIN_LEN) / 3U) * 3U)))) + 1U) - -SODIUM_EXPORT -size_t sodium_base64_encoded_len(const size_t bin_len, const int variant); - -SODIUM_EXPORT -char *sodium_bin2base64(char * const b64, const size_t b64_maxlen, - const unsigned char * const bin, const size_t bin_len, - const int variant) __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int sodium_base642bin(unsigned char * const bin, const size_t bin_maxlen, - const char * const b64, const size_t b64_len, - const char * const ignore, size_t * const bin_len, - const char ** const b64_end, const int variant) - __attribute__ ((nonnull(1))); - -SODIUM_EXPORT -int sodium_mlock(void * const addr, const size_t len) - __attribute__ ((nonnull)); - -SODIUM_EXPORT -int sodium_munlock(void * const addr, const size_t len) - __attribute__ ((nonnull)); - -/* WARNING: sodium_malloc() and sodium_allocarray() are not general-purpose - * allocation functions. - * - * They return a pointer to a region filled with 0xd0 bytes, immediately - * followed by a guard page. - * As a result, accessing a single byte after the requested allocation size - * will intentionally trigger a segmentation fault. - * - * A canary and an additional guard page placed before the beginning of the - * region may also kill the process if a buffer underflow is detected. - * - * The memory layout is: - * [unprotected region size (read only)][guard page (no access)][unprotected pages (read/write)][guard page (no access)] - * With the layout of the unprotected pages being: - * [optional padding][16-bytes canary][user region] - * - * However: - * - These functions are significantly slower than standard functions - * - Each allocation requires 3 or 4 additional pages - * - The returned address will not be aligned if the allocation size is not - * a multiple of the required alignment. For this reason, these functions - * are designed to store data, such as secret keys and messages. - * - * sodium_malloc() can be used to allocate any libsodium data structure. - * - * The crypto_generichash_state structure is packed and its length is - * either 357 or 361 bytes. For this reason, when using sodium_malloc() to - * allocate a crypto_generichash_state structure, padding must be added in - * order to ensure proper alignment. crypto_generichash_statebytes() - * returns the rounded up structure size, and should be prefered to sizeof(): - * state = sodium_malloc(crypto_generichash_statebytes()); - */ - -SODIUM_EXPORT -void *sodium_malloc(const size_t size) - __attribute__ ((malloc)); - -SODIUM_EXPORT -void *sodium_allocarray(size_t count, size_t size) - __attribute__ ((malloc)); - -SODIUM_EXPORT -void sodium_free(void *ptr); - -SODIUM_EXPORT -int sodium_mprotect_noaccess(void *ptr) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int sodium_mprotect_readonly(void *ptr) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int sodium_mprotect_readwrite(void *ptr) __attribute__ ((nonnull)); - -SODIUM_EXPORT -int sodium_pad(size_t *padded_buflen_p, unsigned char *buf, - size_t unpadded_buflen, size_t blocksize, size_t max_buflen) - __attribute__ ((nonnull(2))); - -SODIUM_EXPORT -int sodium_unpad(size_t *unpadded_buflen_p, const unsigned char *buf, - size_t padded_buflen, size_t blocksize) - __attribute__ ((nonnull(2))); - -/* -------- */ - -int _sodium_alloc_init(void); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/win32/include/sodium/version.h b/win32/include/sodium/version.h deleted file mode 100644 index 201a290e7d..0000000000 --- a/win32/include/sodium/version.h +++ /dev/null @@ -1,33 +0,0 @@ - -#ifndef sodium_version_H -#define sodium_version_H - -#include "export.h" - -#define SODIUM_VERSION_STRING "1.0.18" - -#define SODIUM_LIBRARY_VERSION_MAJOR 10 -#define SODIUM_LIBRARY_VERSION_MINOR 3 - - -#ifdef __cplusplus -extern "C" { -#endif - -SODIUM_EXPORT -const char *sodium_version_string(void); - -SODIUM_EXPORT -int sodium_library_version_major(void); - -SODIUM_EXPORT -int sodium_library_version_minor(void); - -SODIUM_EXPORT -int sodium_library_minimal(void); - -#ifdef __cplusplus -} -#endif - -#endif