Showing preview only (398K chars total). Download the full file or copy to clipboard to get everything.
Repository: discord/libdave
Branch: main
Commit: 52cd56dc550f
Files: 111
Total size: 368.1 KB
Directory structure:
gitextract_k2hmfbp_/
├── .github/
│ ├── actions/
│ │ └── prepare-build/
│ │ └── action.yaml
│ └── workflows/
│ └── main.yaml
├── .gitignore
├── .gitmodules
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── cpp/
│ ├── .clang-format
│ ├── .gitignore
│ ├── CMakeLists.txt
│ ├── Makefile
│ ├── README.md
│ ├── afl-driver/
│ │ └── src/
│ │ └── main.cpp
│ ├── includes/
│ │ └── dave/
│ │ ├── array_view.h
│ │ ├── dave.h
│ │ ├── dave_interfaces.h
│ │ ├── logger.h
│ │ └── version.h
│ ├── src/
│ │ ├── bindings_capi.cpp
│ │ ├── bindings_wasm.cpp
│ │ ├── boringssl_cryptor.cpp
│ │ ├── boringssl_cryptor.h
│ │ ├── codec_utils.cpp
│ │ ├── codec_utils.h
│ │ ├── common.h
│ │ ├── cryptor.cpp
│ │ ├── cryptor.h
│ │ ├── cryptor_manager.cpp
│ │ ├── cryptor_manager.h
│ │ ├── decryptor.cpp
│ │ ├── decryptor.h
│ │ ├── encryptor.cpp
│ │ ├── encryptor.h
│ │ ├── frame_processors.cpp
│ │ ├── frame_processors.h
│ │ ├── key_ratchet.h
│ │ ├── logger.cpp
│ │ ├── mls/
│ │ │ ├── detail/
│ │ │ │ ├── persisted_key_pair.h
│ │ │ │ ├── persisted_key_pair_apple.cpp
│ │ │ │ ├── persisted_key_pair_generic.cpp
│ │ │ │ ├── persisted_key_pair_null.cpp
│ │ │ │ └── persisted_key_pair_win.cpp
│ │ │ ├── parameters.cpp
│ │ │ ├── parameters.h
│ │ │ ├── persisted_key_pair.cpp
│ │ │ ├── persisted_key_pair.h
│ │ │ ├── persisted_key_pair_null.cpp
│ │ │ ├── session.cpp
│ │ │ ├── session.h
│ │ │ ├── user_credential.cpp
│ │ │ ├── user_credential.h
│ │ │ ├── util.cpp
│ │ │ └── util.h
│ │ ├── mls_key_ratchet.cpp
│ │ ├── mls_key_ratchet.h
│ │ ├── openssl_cryptor.cpp
│ │ ├── openssl_cryptor.h
│ │ ├── utils/
│ │ │ ├── clock.h
│ │ │ ├── leb128.cpp
│ │ │ ├── leb128.h
│ │ │ └── scope_exit.h
│ │ └── version.cpp
│ ├── test/
│ │ ├── CMakeLists.txt
│ │ ├── capi/
│ │ │ ├── CMakeLists.txt
│ │ │ ├── basic_tests.c
│ │ │ ├── external_sender_wrapper.cpp
│ │ │ ├── external_sender_wrapper.h
│ │ │ ├── test_helpers.c
│ │ │ └── test_helpers.h
│ │ ├── codec_utils_tests.cpp
│ │ ├── cryptor_manager_tests.cpp
│ │ ├── cryptor_tests.cpp
│ │ ├── dave_test.cpp
│ │ ├── dave_test.h
│ │ ├── external_sender.cpp
│ │ ├── external_sender.h
│ │ ├── static_key_ratchet.cpp
│ │ ├── static_key_ratchet.h
│ │ └── xssl_cryptor_tests.cpp
│ └── vcpkg-alts/
│ ├── boringssl/
│ │ ├── overlay-ports/
│ │ │ └── mlspp/
│ │ │ ├── portfile.cmake
│ │ │ └── vcpkg.json
│ │ └── vcpkg.json
│ ├── openssl_1.1/
│ │ ├── overlay-ports/
│ │ │ └── mlspp/
│ │ │ ├── portfile.cmake
│ │ │ └── vcpkg.json
│ │ └── vcpkg.json
│ ├── openssl_3/
│ │ ├── overlay-ports/
│ │ │ └── mlspp/
│ │ │ ├── portfile.cmake
│ │ │ └── vcpkg.json
│ │ └── vcpkg.json
│ └── wasm/
│ ├── overlay-ports/
│ │ └── mlspp/
│ │ ├── portfile.cmake
│ │ └── vcpkg.json
│ └── vcpkg.json
├── js/
│ ├── .gitignore
│ ├── .npmrc
│ ├── README.md
│ ├── __tests__/
│ │ ├── DisplayableCode-test.ts
│ │ ├── KeyFingerprint-test.ts
│ │ ├── KeySerialization-test.ts
│ │ └── PairwiseFingerprint-test.ts
│ ├── jest-setup.js
│ ├── jest.config.js
│ ├── package.json
│ ├── src/
│ │ ├── DisplayableCode.ts
│ │ ├── KeyFingerprint.ts
│ │ ├── KeySerialization.ts
│ │ ├── PairwiseFingerprint.ts
│ │ ├── index.ts
│ │ └── wasm.ts
│ ├── tsconfig.json
│ └── wasm/
│ └── .gitignore
└── samples/
└── typescript/
├── DaveSessionManager.ts
└── README.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/actions/prepare-build/action.yaml
================================================
name: Install build prerequisites
inputs:
runner:
description: The runner on which the action is being run
required: true
crypto:
description: The crypto library being used
required: true
cache-dir:
description: Where to put vcpkg cache
required: true
make-args:
description: Additional arguments to pass to make to configure the build
required: true
runs:
using: "composite"
steps:
- name: Capture vcpkg revision for use in cache key
shell: bash
run: |
git -C cpp/vcpkg rev-parse HEAD > cpp/vcpkg_commit.txt
- name: Restore cache
id: cache-vcpkg-restore
uses: actions/cache/restore@v4
with:
path: ${{ inputs.cache-dir }}
key: vcpkg-${{ inputs.runner }}-${{ inputs.crypto }}-v03-${{ hashFiles('vcpkg_commit', 'cpp/vcpkg-alts/*') }}
restore-keys: |
vcpkg-${{ inputs.runner }}-${{ inputs.crypto }}
v02-vcpkg-${{ inputs.runner }}-${{ inputs.crypto }}
- name: vcpkg bootstrap (macOS/Linux)
if: ${{ runner.os == 'macOS' || runner.os == 'Linux' }}
shell: bash
run: |
./cpp/vcpkg/bootstrap-vcpkg.sh
- name: vcpkg bootstrap (Windows)
if: ${{ runner.os == 'Windows' }}
shell: cmd
run: cpp\vcpkg\bootstrap-vcpkg.bat
- name: Install dependencies (macOS)
if: ${{ runner.os == 'macOS' }}
shell: bash
run: |
brew install go nasm
- name: Set CC and CXX environment variables (macOS)
if: ${{ runner.os == 'macOS' }}
shell: bash
run: |
echo "CC=$(brew --prefix llvm@18)/bin/clang" >> $GITHUB_ENV
echo "CXX=$(brew --prefix llvm@18)/bin/clang++" >> $GITHUB_ENV
- name: Update dependencies (act)
if: ${{ runner.os == 'Linux' && env.ACT }}
shell: bash
run: |
sudo apt-get update
- name: Install dependencies (Ubuntu)
if: ${{ runner.os == 'Linux' }}
shell: bash
run: |
sudo apt-get install -y nasm
- name: Set BUILD_DIR environment variable
shell: bash
run: |
echo "BUILD_DIR=${{ runner.temp }}/build" >> $GITHUB_ENV
- name: Configure build
shell: bash
working-directory: ./cpp
run: make '${{ env.BUILD_DIR }}' BUILD_DIR='${{ env.BUILD_DIR }}' ${{ inputs.make-args }}
- name: Cache vckpg
if: steps.cache-vcpkg-restore.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
key: ${{ steps.cache-vcpkg-restore.outputs.cache-primary-key }}
path: ${{ inputs.cache-dir }}
================================================
FILE: .github/workflows/main.yaml
================================================
name: cpp
on:
push:
branches: ["main"]
pull_request:
branches: ["**"]
env:
CMAKE_BUILD_PARALLEL_LEVEL: 3
CMAKE_TOOLCHAIN_FILE: ${{ github.workspace }}/cpp/vcpkg/scripts/buildsystems/vcpkg.cmake
VCPKG_BINARY_SOURCES: clear;files,${{ github.workspace }}/vcpkg_cache,readwrite
VCPKG_CACHE_DIR: ${{ github.workspace }}/vcpkg_cache
defaults:
run:
working-directory: ./cpp
shell: bash
jobs:
debug-test:
strategy:
matrix:
runner: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, macos-15-intel, windows-latest]
crypto: [openssl_3, openssl_1.1, boringssl]
fail-fast: false
runs-on: ${{matrix.runner}}
steps:
- uses: actions/checkout@v4
with:
submodules: "recursive"
fetch-depth: 0
- uses: ./.github/actions/prepare-build
with:
runner: ${{ matrix.runner }}
crypto: ${{ matrix.crypto }}
cache-dir: ${{ env.VCPKG_CACHE_DIR }}
make-args: BUILD_TYPE=Debug SSL=${{ matrix.crypto }} BUILD_SHARED_LIBS=ON TESTING=ON SANITIZERS=ON MSVC_RUNTIME_LIBRARY=MultiThreadedDebug
- name: build libdave
run: make dev-sanitizers BUILD_DIR='${{ env.BUILD_DIR }}'
- name: test
run: make dtest BUILD_DIR='${{ env.BUILD_DIR }}' BUILD_TYPE=Debug
- name: test-capi
run: make dtest-capi BUILD_DIR='${{ env.BUILD_DIR }}' BUILD_TYPE=Debug
release-build:
strategy:
matrix:
runner: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, macos-15-intel, windows-latest]
crypto: [boringssl]
fail-fast: false
runs-on: ${{matrix.runner}}
steps:
- uses: actions/checkout@v4
with:
submodules: "recursive"
fetch-depth: 0
- uses: ./.github/actions/prepare-build
with:
runner: ${{ matrix.runner }}
crypto: ${{ matrix.crypto }}
cache-dir: ${{ env.VCPKG_CACHE_DIR }}
make-args: BUILD_TYPE=Release SSL=${{ matrix.crypto }} BUILD_SHARED_LIBS=ON TESTING=ON INSTALL_VCPKG_LICENSES=ON MSVC_RUNTIME_LIBRARY=MultiThreaded
- name: build libdave
run: make all BUILD_DIR='${{ env.BUILD_DIR }}' BUILD_TYPE=Release
- name: run tests
if: ${{ runner.os != 'Windows' }}
run: make dtest BUILD_DIR='${{ env.BUILD_DIR }}' BUILD_TYPE=Release
- name: run C api tests
run: make dtest-capi BUILD_DIR='${{ env.BUILD_DIR }}' BUILD_TYPE=Release
- name: prepare artifacts (install)
run: make install BUILD_DIR='${{ env.BUILD_DIR }}' BUILD_TYPE=Release
- name: check licenses
run: ls -la '${{ env.BUILD_DIR }}/install/licenses' | grep -q "boringssl\|openssl"
- name: upload build artifacts
uses: actions/upload-artifact@v6
if: ${{ github.event_name == 'push' }}
with:
path: ${{ env.BUILD_DIR }}/install
name: libdave-${{ runner.os }}-${{ runner.arch }}-${{ matrix.crypto }}
if-no-files-found: error
================================================
FILE: .gitignore
================================================
.DS_Store
================================================
FILE: .gitmodules
================================================
[submodule "cpp/vcpkg"]
path = cpp/vcpkg
url = https://github.com/microsoft/vcpkg.git
================================================
FILE: CONTRIBUTING.md
================================================
At this time we are not taking pull requests to this repository. We welcome reports and suggestions via Github Issues.
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2024 Discord
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
## libdave
This repository contains the JS and C++ libraries which together implement Discord's Audio & Video End-to-End Encryption (DAVE) protocol. These libraries are leveraged by Discord's native clients to support the DAVE protocol.
The DAVE protocol is described in detail in the [protocol whitepaper](https://github.com/discord/dave-protocol).
See the [cpp README](/cpp/README.md) or the [js README](/js/README.md) for information specific to each library.
================================================
FILE: cpp/.clang-format
================================================
---
AccessModifierOffset: -4
AlignAfterOpenBracket: true
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: false
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: InlineOnly
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: true
BinPackArguments: false
BinPackParameters: false
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Stroustrup
BreakBeforeInheritanceComma: true
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeComma
BreakStringLiterals: true
ColumnLimit: 100
CommentPragmas: ""
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 2
ContinuationIndentWidth: 2
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
FixNamespaceComments: true
ForEachMacros: []
IndentCaseLabels: false
IncludeBlocks: Preserve
IncludeCategories:
- Regex: "^<(W|w)indows.h>"
Priority: 1
- Regex: "^<"
Priority: 2
- Regex: ".*"
Priority: 3
IncludeIsMainRegex: "(_test|_win|_linux|_mac|_ios|_osx|_null)?$"
IndentPPDirectives: None
IndentWidth: 4
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ""
MacroBlockEnd: ""
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
PenaltyBreakAssignment: 0
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 9999999
PointerAlignment: Left
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: true
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
TabWidth: 4
UseTab: Never
================================================
FILE: cpp/.gitignore
================================================
build/
================================================
FILE: cpp/CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.20)
project(
libdave
VERSION 1.0
LANGUAGES CXX C
)
option(REQUIRE_BORINGSSL "Require BoringSSL instead of OpenSSL" OFF)
option(TESTING "Build tests" OFF)
option(PERSISTENT_KEYS "Enable storage of persistent signature keys" OFF)
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
option(ENABLE_SANITIZERS "Enable address and undefined behavior sanitizers" OFF)
option(INSTALL_VCPKG_LICENSES "Installs license files from vcpkg deps which require it" OFF)
include(CheckCXXCompilerFlag)
include(CMakeFindDependencyMacro)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -pedantic -Wextra -Werror -Wimplicit-int-conversion)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
add_compile_options(-Wall -pedantic -Wextra -Werror)
elseif(MSVC)
add_compile_options(/W4 /WX)
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()
# Configure sanitizers
if (ENABLE_SANITIZERS)
if (NOT ${CMAKE_BUILD_TYPE} STREQUAL "Debug")
message(FATAL_ERROR "Sanitizers are only supported for Debug builds")
endif()
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
set(SANITIZER_FLAGS "-fsanitize=address,undefined -fno-omit-frame-pointer -fno-optimize-sibling-calls")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SANITIZER_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SANITIZER_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${SANITIZER_FLAGS}")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${SANITIZER_FLAGS}")
message(STATUS "Sanitizers enabled: address, undefined")
elseif(MSVC)
set(SANITIZER_FLAGS "/fsanitize=address /Zi")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SANITIZER_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SANITIZER_FLAGS}")
# Disable STL container annotations to avoid mismatch with dependencies built without ASAN
add_definitions(-D_DISABLE_STRING_ANNOTATION=1 -D_DISABLE_VECTOR_ANNOTATION=1)
# Find ASAN runtime DLL for copying to test directories
get_filename_component(COMPILER_DIR "${CMAKE_CXX_COMPILER}" DIRECTORY)
set(ASAN_RUNTIME_DLL "${COMPILER_DIR}/clang_rt.asan_dynamic-x86_64.dll" CACHE FILEPATH "Path to ASAN runtime DLL")
if(EXISTS "${ASAN_RUNTIME_DLL}")
message(STATUS "ASAN runtime DLL: ${ASAN_RUNTIME_DLL}")
else()
message(WARNING "ASAN runtime DLL not found at ${ASAN_RUNTIME_DLL}")
endif()
message(STATUS "Sanitizers enabled: address")
endif()
endif()
find_package(OpenSSL REQUIRED)
if (OPENSSL_FOUND)
find_path(BORINGSSL_INCLUDE_DIR openssl/is_boringssl.h HINTS ${OPENSSL_INCLUDE_DIR} NO_DEFAULT_PATH)
if (BORINGSSL_INCLUDE_DIR)
message(STATUS "Found OpenSSL includes are for BoringSSL")
add_compile_definitions(WITH_BORINGSSL)
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
add_compile_options(-Wno-gnu-anonymous-struct -Wno-nested-anon-types)
endif ()
file(STRINGS "${OPENSSL_INCLUDE_DIR}/openssl/crypto.h" boringssl_version_str
REGEX "^#[\t ]*define[\t ]+OPENSSL_VERSION_TEXT[\t ]+\"OpenSSL ([0-9])+\\.([0-9])+\\.([0-9])+ .+")
string(REGEX REPLACE "^.*OPENSSL_VERSION_TEXT[\t ]+\"OpenSSL ([0-9]+\\.[0-9]+\\.[0-9])+ .+$"
"\\1" OPENSSL_VERSION "${boringssl_version_str}")
elseif (REQUIRE_BORINGSSL)
message(FATAL_ERROR "BoringSSL required but not found")
endif ()
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}")
else()
message(FATAL_ERROR "No OpenSSL library found")
endif()
find_package(nlohmann_json REQUIRED)
find_dependency(MLSPP REQUIRED)
set(CMAKE_STATIC_LIBRARY_PREFIX "")
SET(LIB_NAME ${PROJECT_NAME})
file(GLOB_RECURSE LIB_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.h" "${CMAKE_CURRENT_SOURCE_DIR}/includes/*.h")
file(GLOB_RECURSE LIB_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
# remove all of the persistent key files
list(FILTER LIB_SOURCES EXCLUDE REGEX ".*persisted_key.*")
if (PERSISTENT_KEYS)
# persistent keys enabled
list(APPEND LIB_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/mls/persisted_key_pair.cpp")
if (APPLE)
# Apple has its own native and generic implementation, we just add the _apple.cpp file
list(APPEND LIB_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/mls/detail/persisted_key_pair_apple.cpp")
else ()
# Other platforms share the generic implementation
list(APPEND LIB_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/mls/detail/persisted_key_pair_generic.cpp")
if (WIN32)
# Windows has a native implementation
list(APPEND LIB_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/mls/detail/persisted_key_pair_win.cpp")
else ()
# We don't have a native implementation, so we include the nullified native
list(APPEND LIB_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/mls/detail/persisted_key_pair_null.cpp")
endif ()
endif ()
else ()
# not using persistent keys, so we just need to add the null implementation
list (APPEND LIB_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/mls/persisted_key_pair_null.cpp")
endif ()
if (NOT WIN32)
list(FILTER LIB_SOURCES EXCLUDE REGEX ".*_win.cpp")
endif ()
if (NOT APPLE)
list(FILTER LIB_SOURCES EXCLUDE REGEX ".*_apple.cpp")
endif ()
if (NOT DEFINED EMSCRIPTEN)
list(FILTER LIB_SOURCES EXCLUDE REGEX ".*_wasm.cpp")
else()
list(FILTER LIB_SOURCES EXCLUDE REGEX ".*_capi.cpp")
endif()
if (BORINGSSL_INCLUDE_DIR)
list(FILTER LIB_SOURCES EXCLUDE REGEX ".*openssl_cryptor.*")
else ()
list(FILTER LIB_SOURCES EXCLUDE REGEX ".*boringssl_cryptor.*")
endif()
if (DEFINED EMSCRIPTEN)
add_executable(${LIB_NAME} ${LIB_HEADERS} ${LIB_SOURCES})
set(OPTIMIZATION "-O3")
set(CONFIG "-sWASM=1 -sWASM_BIGINT -sENVIRONMENT=web -sMODULARIZE -sALLOW_MEMORY_GROWTH")
set(EXPORTS "-sEXPORT_ES6=1 -sEXPORT_NAME=DaveModuleFactory -sEXPORTED_RUNTIME_METHODS='[\"ccall\"]' -sEXPORTED_FUNCTIONS='[\"_malloc\", \"_free\"]'")
set(COMPILE_FLAGS "${OPTIMIZATION}")
set(LINK_FLAGS "${OPTIMIZATION} ${CONFIG} ${EXPORTS} -lembind --no-entry --whole-archive --emit-tsd libdave.d.ts")
set_target_properties(${LIB_NAME} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS}")
set_target_properties(${LIB_NAME} PROPERTIES LINK_FLAGS "${LINK_FLAGS}")
else()
add_library(${LIB_NAME} ${LIB_HEADERS} ${LIB_SOURCES})
if (BUILD_SHARED_LIBS AND NOT WIN32)
# Whithout this the resulting file is called liblibdave.dylib
set_target_properties(${LIB_NAME} PROPERTIES OUTPUT_NAME dave)
endif()
endif()
if (TESTING)
add_subdirectory(test)
endif()
target_include_directories(
${LIB_NAME}
PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/includes>
$<INSTALL_INTERFACE:include>
PRIVATE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>
)
target_link_libraries(${LIB_NAME} PRIVATE OpenSSL::Crypto)
target_link_libraries(${LIB_NAME} PRIVATE MLSPP::mlspp)
if (APPLE AND PERSISTENT_KEYS)
target_link_libraries(${LIB_NAME} PUBLIC "-framework CoreFoundation" "-framework Security")
endif()
set(CMAKE_SKIP_INSTALL_ALL_DEPENDENCY ON)
install(TARGETS ${LIB_NAME} INCLUDES DESTINATION "include")
install(DIRECTORY ${PROJECT_SOURCE_DIR}/includes/ DESTINATION "include")
if (INSTALL_VCPKG_LICENSES)
set(DEPS_NEEDING_LICENSE mlspp nlohmann-json)
if (BORINGSSL_INCLUDE_DIR)
list(APPEND DEPS_NEEDING_LICENSE boringssl)
else ()
list(APPEND DEPS_NEEDING_LICENSE openssl)
endif ()
foreach(DEP_NAME ${DEPS_NEEDING_LICENSE})
set(DEP_LICENSE_PATH "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/share/${DEP_NAME}")
if (NOT EXISTS ${DEP_LICENSE_PATH})
message(ERROR "Could not find license file for ${DEP_LICENSE_PATH}")
endif()
install(FILES ${DEP_LICENSE_PATH}/copyright DESTINATION "licenses" RENAME ${DEP_NAME})
endforeach()
endif()
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE DESTINATION "licenses" RENAME ${LIB_NAME})
================================================
FILE: cpp/Makefile
================================================
# Options
BUILD_DIR=build
INSTALL_DIR=${BUILD_DIR}/install
SANITIZERS=OFF
BUILD_SHARED_LIBS=OFF
PERSISTENT_KEYS=OFF
TESTING=OFF
SSL=openssl_3
INSTALL_VCPKG_LICENSES=OFF
# Paths
BORINGSSL_MANIFEST=vcpkg-alts/boringssl
OPENSSL_1_1_MANIFEST=vcpkg-alts/openssl_1.1
OPENSSL_3_MANIFEST=vcpkg-alts/openssl_3
WASM_MANIFEST=vcpkg-alts/wasm
TOOLCHAIN_FILE=vcpkg/scripts/buildsystems/vcpkg.cmake
EMSCRIPTEN_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake
CLANG_FORMAT=clang-format -i -style=file:.clang-format
DEFAULT_BUILD_TYPE=Debug
BUILD_TYPE ?= $(DEFAULT_BUILD_TYPE)
all shared install: DEFAULT_BUILD_TYPE=Release
ifeq ($(SSL), boringssl)
SSL_MANIFEST=${BORINGSSL_MANIFEST}
else ifeq ($(SSL), openssl_1.1)
SSL_MANIFEST=${OPENSSL_1_1_MANIFEST}
else ifeq ($(SSL), openssl_3)
SSL_MANIFEST=${OPENSSL_3_MANIFEST}
else
$(error Invalid SSL option: $(SSL))
endif
ifeq ($(OS), Windows_NT)
EXTRA_FLAGS=-DVCPKG_TARGET_TRIPLET=x64-windows-static \
-DVCPKG_TARGET_ARCHITECTURE=x86_64
ifeq ($(BUILD_TYPE), Debug)
EXTRA_FLAGS+=-DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=ON
endif
ifdef MSVC_RUNTIME_LIBRARY
EXTRA_FLAGS+=-DCMAKE_MSVC_RUNTIME_LIBRARY=${MSVC_RUNTIME_LIBRARY}
endif
endif
all: ${BUILD_DIR}
cmake --build ${BUILD_DIR} --target libdave --config ${BUILD_TYPE}
${BUILD_DIR}: CMakeLists.txt test/CMakeLists.txt test/capi/CMakeLists.txt
cmake -B${BUILD_DIR} \
-DCMAKE_BUILD_TYPE=${BUILD_TYPE} \
-DVCPKG_MANIFEST_DIR=${SSL_MANIFEST} \
-DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FILE} \
-DBUILD_SHARED_LIBS=${BUILD_SHARED_LIBS} \
-DENABLE_SANITIZERS=${SANITIZERS} \
-DPERSISTENT_KEYS=${PERSISTENT_KEYS} \
-DTESTING=${TESTING} \
-DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} \
-DINSTALL_VCPKG_LICENSES=${INSTALL_VCPKG_LICENSES} \
${EXTRA_FLAGS}
install: ${BUILD_DIR}
cmake --build ${BUILD_DIR} --target install --config ${BUILD_TYPE}
shared:
$(MAKE) all BUILD_SHARED_LIBS=ON
dev:
$(MAKE) all TESTING=ON BUILD_TYPE=$(BUILD_TYPE)
dev-shared:
$(MAKE) dev BUILD_SHARED_LIBS=ON
dev-sanitizers:
$(MAKE) dev SANITIZERS=ON
devB:
# Like `dev`, but using OpenSSL 1.1
$(MAKE) dev SSL=openssl_1.1
devC:
# Like `dev`, but using BoringSSL
$(MAKE) dev SSL=boringssl
wasm: check-emsdk
emcmake cmake -B${BUILD_DIR} -DCMAKE_BUILD_TYPE=Release \
-DVCPKG_MANIFEST_DIR=${WASM_MANIFEST} \
-DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FILE} \
-DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=${EMSCRIPTEN_TOOLCHAIN_FILE} \
-DVCPKG_TARGET_TRIPLET=wasm32-emscripten
cmake --build ${BUILD_DIR} --target libdave --config ${BUILD_TYPE}
check-emsdk:
@if [ -z "$$EMSDK" ]; then \
echo "Error: EMSDK environment variable is not set"; \
echo "Please set it to your emsdk installation directory"; \
echo "Example: export EMSDK=/path/to/emsdk"; \
exit 1; \
fi
test: dev test/*
cmake --build ${BUILD_DIR} --target libdave_test --config ${BUILD_TYPE}
test-capi: dev test/capi/*
cmake --build ${BUILD_DIR} --target capi_test --config ${BUILD_TYPE}
test-sanitizers: dev-sanitizers test/*
cmake --build ${BUILD_DIR} --target libdave_test --config ${BUILD_TYPE}
test-capi-sanitizers: dev-sanitizers test/capi/*
cmake --build ${BUILD_DIR} --target capi_test --config ${BUILD_TYPE}
dtest: test
ifeq ($(OS), Windows_NT)
${BUILD_DIR}/test/${BUILD_TYPE}/libdave_test.exe
else
${BUILD_DIR}/test/libdave_test
endif
dtest-capi: test-capi
ifeq ($(OS), Windows_NT)
${BUILD_DIR}/test/capi/${BUILD_TYPE}/capi_test.exe
else
${BUILD_DIR}/test/capi/capi_test
endif
dtest-sanitizers: test-sanitizers
ifeq ($(OS), Windows_NT)
${BUILD_DIR}/test/${BUILD_TYPE}/libdave_test.exe
else
${BUILD_DIR}/test/libdave_test
endif
dtest-capi-sanitizers: test-capi-sanitizers
ifeq ($(OS), Windows_NT)
${BUILD_DIR}/test/capi/${BUILD_TYPE}/capi_test.exe
else
${BUILD_DIR}/test/capi/capi_test
endif
dbtest: test
lldb ${BUILD_DIR}/test/libdave_test
dbtest-capi: test-capi
lldb ${BUILD_DIR}/test/capi/capi_test
ctest: test
cmake --build ${BUILD_DIR} --target test --config ${BUILD_TYPE}
clean:
cmake --build ${BUILD_DIR} --target clean
cclean:
ifeq ($(OS), Windows_NT)
if exist ${BUILD_DIR} rmdir /s /q ${BUILD_DIR}
else
rm -rf ${BUILD_DIR}
endif
format:
find src -iname "*.h" -or -iname "*.cpp" -or -iname "*.c" | xargs ${CLANG_FORMAT}
find test -iname "*.h" -or -iname "*.cpp" -or -iname "*.c" | xargs ${CLANG_FORMAT}
================================================
FILE: cpp/README.md
================================================
## libdave C++
Contains the libdave C++ library, which handles the bulk of the DAVE protocol implementation for Discord's native clients.
### Dependencies
- [mlspp](https://github.com/cisco/mlspp)
- Configured with `-DMLS_CXX_NAMESPACE="mlspp"` and `-DDISABLE_GREASE=ON`
- One of the supported SSL backends:
- [OpenSSL 1.1 or 3.0](https://github.com/openssl/openssl)
- [boringssl](https://boringssl.googlesource.com/boringssl)
#### Testing
- [googletest](https://github.com/google/googletest)
- [AFLplusplus](https://github.com/AFLplusplus/AFLplusplus)
## Building
### vcpkg
Make sure the vcpkg submodule is up to date and initialized:
```
git submodule update --recursive
./vcpkg/bootstrap-vcpkg.sh
```
### Compiling
For a static library, run:
```
make cclean
make
```
For a shared library, run:
```
make cclean
make shared
```
### SSL
By default the library builds with OpenSSL 3, however you can modify `VCPKG_MANIFEST_DIR` in the [Makefile](Makefile) to build with OpenSSL 1.1 or BoringSSL instead.
================================================
FILE: cpp/afl-driver/src/main.cpp
================================================
#include <array>
#include <cassert>
#include <iostream>
#include <memory>
#include <unistd.h>
#include <fuzzer/FuzzedDataProvider.h>
#include "common.h"
#include "utils/array_view.h"
#include "decryptor.h"
using namespace discord::dave;
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{
FuzzedDataProvider provider(data, size);
MediaType mediaType = static_cast<MediaType>(provider.ConsumeIntegralInRange(0, 2));
const auto InFrame = provider.ConsumeRemainingBytes<uint8_t>();
Decryptor decryptor;
const auto OutFrameSize = decryptor.GetMaxPlaintextByteSize(mediaType, InFrame.size());
auto outFrame = std::make_unique<uint8_t[]>(OutFrameSize);
[[maybe_unused]] auto res = decryptor.Decrypt(mediaType,
MakeArrayView(InFrame.data(), InFrame.size()),
MakeArrayView(outFrame.get(), OutFrameSize));
return 0;
}
================================================
FILE: cpp/includes/dave/array_view.h
================================================
#pragma once
#include <cassert>
#include <cstddef>
#include <vector>
namespace discord {
namespace dave {
template <typename T>
class ArrayView {
public:
ArrayView() = default;
ArrayView(T* data, size_t size)
: data_(data)
, size_(size)
{
}
size_t size() const { return size_; }
T* data() const { return data_; }
T* begin() const { return data_; }
T* end() const { return data_ + size_; }
private:
T* data_ = nullptr;
size_t size_ = 0;
};
template <typename T>
inline ArrayView<T> MakeArrayView(T* data, size_t size)
{
return ArrayView<T>(data, size);
}
template <typename T>
inline ArrayView<T> MakeArrayView(std::vector<T>& data)
{
return ArrayView<T>(data.data(), data.size());
}
} // namespace dave
} // namespace discord
================================================
FILE: cpp/includes/dave/dave.h
================================================
/**
* @file dave.h
* @brief DAVE (Discord Audio/Video Encryption) C API
*
* This header provides the C API for end-to-end encryption of audio and video
* streams using the DAVE protocol.
*
* All handles are opaque pointers that must be created and destroyed using
* the corresponding API functions. Memory management rules:
* - Handles from *Create functions must be freed with their *Destroy counterpart
* - Output handles should be destroyed by the caller using the corresponding *Destroy function
* - Functions do not take ownership of the input data unless otherwise specified
* - Output byte arrays allocated by the library must be freed using daveFree()
*/
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#if (defined(_WIN32) || defined(_WIN64))
#define DAVE_EXPORT __declspec(dllexport)
#else
#define DAVE_EXPORT __attribute__((visibility("default")))
#endif
#define DECLARE_OPAQUE_HANDLE(x) typedef struct x##_s* x
#ifdef __cplusplus
extern "C" {
#endif
/** @brief DAVE session handle for managing group encryption state */
DECLARE_OPAQUE_HANDLE(DAVESessionHandle);
/** @brief Result handle from processing an MLS commit message */
DECLARE_OPAQUE_HANDLE(DAVECommitResultHandle);
/** @brief Result handle from processing an MLS welcome message */
DECLARE_OPAQUE_HANDLE(DAVEWelcomeResultHandle);
/** @brief Key ratchet handle for deriving encryption keys */
DECLARE_OPAQUE_HANDLE(DAVEKeyRatchetHandle);
/** @brief Media frame encryptor handle */
DECLARE_OPAQUE_HANDLE(DAVEEncryptorHandle);
/** @brief Media frame decryptor handle */
DECLARE_OPAQUE_HANDLE(DAVEDecryptorHandle);
/**
* @brief Supported media codecs for encryption
*/
typedef enum {
DAVE_CODEC_UNKNOWN = 0, /**< Unknown or unspecified codec */
DAVE_CODEC_OPUS = 1, /**< Opus audio codec */
DAVE_CODEC_VP8 = 2, /**< VP8 video codec */
DAVE_CODEC_VP9 = 3, /**< VP9 video codec */
DAVE_CODEC_H264 = 4, /**< H.264/AVC video codec */
DAVE_CODEC_H265 = 5, /**< H.265/HEVC video codec */
DAVE_CODEC_AV1 = 6 /**< AV1 video codec */
} DAVECodec;
/**
* @brief Media stream type classification
*/
typedef enum {
DAVE_MEDIA_TYPE_AUDIO = 0, /**< Audio stream */
DAVE_MEDIA_TYPE_VIDEO = 1 /**< Video stream */
} DAVEMediaType;
/**
* @brief Result codes returned by encryption operations
*/
typedef enum {
DAVE_ENCRYPTOR_RESULT_CODE_SUCCESS = 0, /**< Encryption succeeded */
DAVE_ENCRYPTOR_RESULT_CODE_ENCRYPTION_FAILURE = 1, /**< Encryption failed */
DAVE_ENCRYPTOR_RESULT_CODE_MISSING_KEY_RATCHET = 2,/**< No key ratchet available */
DAVE_ENCRYPTOR_RESULT_CODE_MISSING_CRYPTOR = 3, /**< Missing cryptographic context */
DAVE_ENCRYPTOR_RESULT_CODE_TOO_MANY_ATTEMPTS = 4, /**< Too many attempts to encrypt the frame failed */
} DAVEEncryptorResultCode;
/**
* @brief Result codes returned by decryption operations
*/
typedef enum {
DAVE_DECRYPTOR_RESULT_CODE_SUCCESS = 0, /**< Decryption succeeded */
DAVE_DECRYPTOR_RESULT_CODE_DECRYPTION_FAILURE = 1, /**< Decryption failed */
DAVE_DECRYPTOR_RESULT_CODE_MISSING_KEY_RATCHET = 2,/**< No key ratchet available */
DAVE_DECRYPTOR_RESULT_CODE_INVALID_NONCE = 3, /**< Invalid nonce in encrypted frame */
DAVE_DECRYPTOR_RESULT_CODE_MISSING_CRYPTOR = 4 /**< Missing cryptographic context */
} DAVEDecryptorResultCode;
/**
* @brief Log message severity levels
*/
typedef enum {
DAVE_LOGGING_SEVERITY_VERBOSE = 0, /**< Verbose debug information */
DAVE_LOGGING_SEVERITY_INFO = 1, /**< Informational messages */
DAVE_LOGGING_SEVERITY_WARNING = 2, /**< Warning messages */
DAVE_LOGGING_SEVERITY_ERROR = 3, /**< Error messages */
DAVE_LOGGING_SEVERITY_NONE = 4 /**< Messages to be ignored */
} DAVELoggingSeverity;
/**
* @brief Callback invoked when an MLS protocol failure occurs
* @param source The source/component where the failure occurred
* @param reason Human-readable failure reason
* @param userData User-provided context pointer
*/
typedef void (*DAVEMLSFailureCallback)(const char* source, const char* reason, void* userData);
/**
* @brief Callback invoked with the computed pairwise fingerprint for identity verification
* @param fingerprint Pointer to fingerprint bytes (freed by the library after the callback returns)
* @param length Length of fingerprint in bytes
* @param userData User-provided context pointer
*/
typedef void (*DAVEPairwiseFingerprintCallback)(const uint8_t* fingerprint, size_t length, void* userData);
/**
* @brief Callback invoked when the encryptor's protocol version changes
* @param userData User-provided context pointer
*/
typedef void (*DAVEEncryptorProtocolVersionChangedCallback)(void* userData);
/**
* @brief Custom log sink callback for receiving library log messages
* @param severity Log severity level
* @param file Source file name where log originated (freed by the library after the callback returns)
* @param line Line number in source file
* @param message Log message text (freed by the library after the callback returns)
*/
typedef void (*DAVELogSinkCallback)(DAVELoggingSeverity severity,
const char* file,
int line,
const char* message);
/**
* @brief Statistics for encryption operations
*/
typedef struct DAVEEncryptorStats {
uint64_t passthroughCount; /**< Frames passed through without encryption */
uint64_t encryptSuccessCount; /**< Successful encryption count */
uint64_t encryptFailureCount; /**< Failed encryption count */
uint64_t encryptDuration; /**< Total encryption duration */
uint64_t encryptAttempts; /**< Total encryption attempts */
uint64_t encryptMaxAttempts; /**< Maximum retry attempts for a single frame */
uint64_t encryptMissingKeyCount;/**< Encryptions skipped due to missing key */
} DAVEEncryptorStats;
/**
* @brief Statistics for decryption operations
*/
typedef struct DAVEDecryptorStats {
uint64_t passthroughCount; /**< Frames passed through without decryption */
uint64_t decryptSuccessCount; /**< Successful decryption count */
uint64_t decryptFailureCount; /**< Failed decryption count */
uint64_t decryptDuration; /**< Total decryption duration */
uint64_t decryptAttempts; /**< Total decryption attempts */
uint64_t decryptMissingKeyCount; /**< Decryptions failed due to missing key */
uint64_t decryptInvalidNonceCount;/**< Decryptions failed due to invalid nonce */
} DAVEDecryptorStats;
/*******************************************************************************
* Version
******************************************************************************/
/**
* @brief Returns the maximum protocol version supported by this library
* @return Maximum supported protocol version number
*/
DAVE_EXPORT uint16_t daveMaxSupportedProtocolVersion(void);
/*******************************************************************************
* Memory Management
******************************************************************************/
/**
* @brief Frees memory allocated by the DAVE library
*
* Use this function to free any byte arrays or buffers allocated by DAVE API
* functions.
*
* @param ptr Pointer to memory previously allocated by a DAVE API function.
* If NULL, this function does nothing.
*
* @note This function should be used to free output byte arrays from functions
* like daveSessionGetLastEpochAuthenticator, daveSessionGetMarshalledKeyPackage,
* daveCommitResultGetRosterMemberIds, etc. Do NOT use this to destroy handles;
* use the corresponding *Destroy functions instead.
*/
DAVE_EXPORT void daveFree(void* ptr);
/*******************************************************************************
* Session Management
******************************************************************************/
/**
* @brief Creates a new DAVE session
* @param context Currently unused platform-specific context pointer (can be NULL)
* @param authSessionId String used to manage persistent key lifetimes (can be NULL)
* @param callback Callback invoked on MLS failures
* @param userData User data pointer passed to the callback
* @return New session handle, or NULL on failure. Must be destroyed with daveSessionDestroy()
*/
DAVE_EXPORT DAVESessionHandle daveSessionCreate(void* context,
const char* authSessionId,
DAVEMLSFailureCallback callback,
void* userData);
/**
* @brief Destroys a session and frees associated resources
* @param session Session handle to destroy
*/
DAVE_EXPORT void daveSessionDestroy(DAVESessionHandle session);
/**
* @brief Initializes a session with protocol version and group information
* @param session Session handle
* @param version Protocol version to use
* @param groupId Group identifier common to all users in the group
* @param selfUserId User ID of the local user
*/
DAVE_EXPORT void daveSessionInit(DAVESessionHandle session,
uint16_t version,
uint64_t groupId,
const char* selfUserId);
/**
* @brief Resets the session state
* @param session Session handle
*/
DAVE_EXPORT void daveSessionReset(DAVESessionHandle session);
/**
* @brief Sets the protocol version for the session
* @param session Session handle
* @param version Protocol version to set
*/
DAVE_EXPORT void daveSessionSetProtocolVersion(DAVESessionHandle session, uint16_t version);
/**
* @brief Gets the current protocol version of the session
* @param session Session handle
* @return Current protocol version
*/
DAVE_EXPORT uint16_t daveSessionGetProtocolVersion(DAVESessionHandle session);
/**
* @brief Retrieves the authenticator from the last MLS epoch
* @param session Session handle
* @param[out] authenticator Output pointer to authenticator bytes (caller must free with daveFree)
* @param[out] length Output pointer to authenticator length
*/
DAVE_EXPORT void daveSessionGetLastEpochAuthenticator(DAVESessionHandle session,
uint8_t** authenticator,
size_t* length);
/**
* @brief Sets the external sender credentials for the session
* @param session Session handle
* @param externalSender External sender credential bytes
* @param length Length of external sender data
*/
DAVE_EXPORT void daveSessionSetExternalSender(DAVESessionHandle session,
const uint8_t* externalSender,
size_t length);
/**
* @brief Processes MLS proposals and generates commit/welcome messages
* @param session Session handle
* @param proposals Serialized proposal bytes
* @param length Length of proposals
* @param recognizedUserIds Array of recognized user ID strings
* @param recognizedUserIdsLength Number of recognized user IDs
* @param[out] commitWelcomeBytes Output buffer to commit/welcome message bytes (caller must free with daveFree)
* @param[out] commitWelcomeBytesLength Output length of the commit/welcome message
*/
DAVE_EXPORT void daveSessionProcessProposals(DAVESessionHandle session,
const uint8_t* proposals,
size_t length,
const char** recognizedUserIds,
size_t recognizedUserIdsLength,
uint8_t** commitWelcomeBytes,
size_t* commitWelcomeBytesLength);
/**
* @brief Processes an incoming MLS commit message
* @param session Session handle
* @param commit Serialized commit message bytes
* @param length Length of commit message
* @return Commit result handle. Must be destroyed with daveCommitResultDestroy()
*/
DAVE_EXPORT DAVECommitResultHandle daveSessionProcessCommit(DAVESessionHandle session,
const uint8_t* commit,
size_t length);
/**
* @brief Processes an incoming MLS welcome message to join a group
* @param session Session handle
* @param welcome Serialized welcome message bytes
* @param length Length of welcome message
* @param recognizedUserIds Array of recognized user ID strings
* @param recognizedUserIdsLength Number of recognized user IDs
* @return Welcome result handle. Must be destroyed with daveWelcomeResultDestroy()
*/
DAVE_EXPORT DAVEWelcomeResultHandle daveSessionProcessWelcome(DAVESessionHandle session,
const uint8_t* welcome,
size_t length,
const char** recognizedUserIds,
size_t recognizedUserIdsLength);
/**
* @brief Gets the marshalled MLS key package for this session
* @param session Session handle
* @param[out] keyPackage Output buffer to key package bytes (caller must free with daveFree)
* @param[out] length Output length of the key package
*/
DAVE_EXPORT void daveSessionGetMarshalledKeyPackage(DAVESessionHandle session,
uint8_t** keyPackage,
size_t* length);
/**
* @brief Gets a key ratchet for a specific user in the session
* @param session Session handle
* @param userId User ID to get key ratchet for
* @return Key ratchet handle. Must be destroyed with daveKeyRatchetDestroy()
*/
DAVE_EXPORT DAVEKeyRatchetHandle daveSessionGetKeyRatchet(DAVESessionHandle session,
const char* userId);
/**
* @brief Computes a pairwise fingerprint for identity verification with another user
* @param session Session handle
* @param version Protocol version currently in use
* @param userId User ID of the remote user to compute the fingerprint for
* @param callback Callback to receive the fingerprint
* @param userData User data passed to callback
*/
DAVE_EXPORT void daveSessionGetPairwiseFingerprint(DAVESessionHandle session,
uint16_t version,
const char* userId,
DAVEPairwiseFingerprintCallback callback,
void* userData);
/*******************************************************************************
* Key Ratchet
******************************************************************************/
/**
* @brief Destroys a key ratchet and frees associated resources
* @param keyRatchet Key ratchet handle to destroy
*/
DAVE_EXPORT void daveKeyRatchetDestroy(DAVEKeyRatchetHandle keyRatchet);
/*******************************************************************************
* Commit Result
******************************************************************************/
/**
* @brief Checks if processing the commit failed
* @param commitResultHandle Commit result handle
* @return true if commit processing failed
*/
DAVE_EXPORT bool daveCommitResultIsFailed(DAVECommitResultHandle commitResultHandle);
/**
* @brief Checks if the commit should be ignored
* @param commitResultHandle Commit result handle
* @return true if commit should be ignored
*/
DAVE_EXPORT bool daveCommitResultIsIgnored(DAVECommitResultHandle commitResultHandle);
/**
* @brief Gets the list of member IDs in the roster after the commit
* @param commitResultHandle Commit result handle
* @param[out] rosterIds Output buffer to array of roster member IDs (caller must free with daveFree)
* @param[out] rosterIdsLength Output length of the roster member IDs array
*/
DAVE_EXPORT void daveCommitResultGetRosterMemberIds(DAVECommitResultHandle commitResultHandle,
uint64_t** rosterIds,
size_t* rosterIdsLength);
/**
* @brief Gets the signature for a specific roster member
* @param commitResultHandle Commit result handle
* @param rosterId Roster member ID
* @param[out] signature Output buffer to signature bytes (caller must free with daveFree)
* @param[out] signatureLength Output length of the signature
*/
DAVE_EXPORT void daveCommitResultGetRosterMemberSignature(DAVECommitResultHandle commitResultHandle,
uint64_t rosterId,
uint8_t** signature,
size_t* signatureLength);
/**
* @brief Destroys a commit result and frees associated resources
* @param commitResultHandle Commit result handle to destroy
*/
DAVE_EXPORT void daveCommitResultDestroy(DAVECommitResultHandle commitResultHandle);
/*******************************************************************************
* Welcome Result
******************************************************************************/
/**
* @brief Gets the list of member IDs in the roster from the welcome message
* @param welcomeResultHandle Welcome result handle
* @param[out] rosterIds Output buffer to array of roster member IDs (caller must free with daveFree)
* @param[out] rosterIdsLength Output length of the roster member IDs array
*/
DAVE_EXPORT void daveWelcomeResultGetRosterMemberIds(DAVEWelcomeResultHandle welcomeResultHandle,
uint64_t** rosterIds,
size_t* rosterIdsLength);
/**
* @brief Gets the signature for a specific roster member
* @param welcomeResultHandle Welcome result handle
* @param rosterId Roster member ID
* @param[out] signature Output buffer to signature bytes (caller must free with daveFree)
* @param[out] signatureLength Output length of the signature
*/
DAVE_EXPORT void daveWelcomeResultGetRosterMemberSignature(DAVEWelcomeResultHandle welcomeResultHandle,
uint64_t rosterId,
uint8_t** signature,
size_t* signatureLength);
/**
* @brief Destroys a welcome result and frees associated resources
* @param welcomeResultHandle Welcome result handle to destroy
*/
DAVE_EXPORT void daveWelcomeResultDestroy(DAVEWelcomeResultHandle welcomeResultHandle);
/*******************************************************************************
* Encryptor
******************************************************************************/
/**
* @brief Creates a new media frame encryptor
* @return New encryptor handle. Must be destroyed with daveEncryptorDestroy()
*/
DAVE_EXPORT DAVEEncryptorHandle daveEncryptorCreate(void);
/**
* @brief Destroys an encryptor and frees associated resources
* @param encryptor Encryptor handle to destroy
*/
DAVE_EXPORT void daveEncryptorDestroy(DAVEEncryptorHandle encryptor);
/**
* @brief Sets the key ratchet for encryption
* @param encryptor Encryptor handle
* @param keyRatchet Key ratchet to use for encryption (does *not* take ownership)
*/
DAVE_EXPORT void daveEncryptorSetKeyRatchet(DAVEEncryptorHandle encryptor,
DAVEKeyRatchetHandle keyRatchet);
/**
* @brief Enables or disables passthrough mode (frames pass through unencrypted)
* @param encryptor Encryptor handle
* @param passthroughMode true to enable passthrough, false to encrypt
*/
DAVE_EXPORT void daveEncryptorSetPassthroughMode(DAVEEncryptorHandle encryptor,
bool passthroughMode);
/**
* @brief Associates an SSRC (Synchronization Source) with a specific codec
* @param encryptor Encryptor handle
* @param ssrc SSRC identifier
* @param codecType Codec type for this SSRC
*/
DAVE_EXPORT void daveEncryptorAssignSsrcToCodec(DAVEEncryptorHandle encryptor,
uint32_t ssrc,
DAVECodec codecType);
/**
* @brief Gets the current protocol version used by the encryptor
* @param encryptor Encryptor handle
* @return Protocol version number
*/
DAVE_EXPORT uint16_t daveEncryptorGetProtocolVersion(DAVEEncryptorHandle encryptor);
/**
* @brief Calculates the maximum ciphertext size for a given plaintext frame size
* @param encryptor Encryptor handle
* @param mediaType Media type (audio or video)
* @param frameSize Size of plaintext frame in bytes
* @return Maximum possible ciphertext size in bytes
*/
DAVE_EXPORT size_t daveEncryptorGetMaxCiphertextByteSize(DAVEEncryptorHandle encryptor,
DAVEMediaType mediaType,
size_t frameSize);
/**
* @brief Checks if the encryptor has a key ratchet
* @param encryptor Encryptor handle
* @return true if has key ratchet, false otherwise
*/
DAVE_EXPORT bool daveEncryptorHasKeyRatchet(DAVEEncryptorHandle encryptor);
/**
* @brief Checks if the encryptor is in passthrough mode
* @param encryptor Encryptor handle
* @return true if in passthrough mode, false otherwise
*/
DAVE_EXPORT bool daveEncryptorIsPassthroughMode(DAVEEncryptorHandle encryptor);
/**
* @brief Encrypts a media frame
* @param encryptor Encryptor handle
* @param mediaType Media type (audio or video)
* @param ssrc SSRC of the stream
* @param frame Pointer to plaintext frame data
* @param frameLength Length of plaintext frame
* @param[out] encryptedFrame Pointer to the output buffer the encrypted frame will be written to
* @param encryptedFrameCapacity Capacity of the output buffer
* @param[out] bytesWritten Number of bytes written to the output buffer
* @return Result code indicating success or failure
*/
DAVE_EXPORT DAVEEncryptorResultCode daveEncryptorEncrypt(DAVEEncryptorHandle encryptor,
DAVEMediaType mediaType,
uint32_t ssrc,
const uint8_t* frame,
size_t frameLength,
uint8_t* encryptedFrame,
size_t encryptedFrameCapacity,
size_t* bytesWritten);
/**
* @brief Sets a callback to be notified when the protocol version changes
* @param encryptor Encryptor handle
* @param callback Callback function
* @param userData User data passed to callback
*/
DAVE_EXPORT void daveEncryptorSetProtocolVersionChangedCallback(
DAVEEncryptorHandle encryptor,
DAVEEncryptorProtocolVersionChangedCallback callback,
void* userData);
/**
* @brief Gets encryption statistics
* @param encryptor Encryptor handle
* @param mediaType Media type (audio or video)
* @param[out] stats Pointer to the stats structure to be filled
*/
DAVE_EXPORT void daveEncryptorGetStats(DAVEEncryptorHandle encryptor,
DAVEMediaType mediaType,
DAVEEncryptorStats* stats);
/*******************************************************************************
* Decryptor
******************************************************************************/
/**
* @brief Creates a new media frame decryptor
* @return New decryptor handle. Must be destroyed with daveDecryptorDestroy()
*/
DAVE_EXPORT DAVEDecryptorHandle daveDecryptorCreate(void);
/**
* @brief Destroys a decryptor and frees associated resources
* @param decryptor Decryptor handle to destroy
*/
DAVE_EXPORT void daveDecryptorDestroy(DAVEDecryptorHandle decryptor);
/**
* @brief Transitions the decryptor to use a new key ratchet
* @param decryptor Decryptor handle
* @param keyRatchet New key ratchet to transition to (does *not* take ownership)
*/
DAVE_EXPORT void daveDecryptorTransitionToKeyRatchet(DAVEDecryptorHandle decryptor,
DAVEKeyRatchetHandle keyRatchet);
/**
* @brief Transitions to or from passthrough mode
* @param decryptor Decryptor handle
* @param passthroughMode true to enable passthrough, false to decrypt
*/
DAVE_EXPORT void daveDecryptorTransitionToPassthroughMode(DAVEDecryptorHandle decryptor,
bool passthroughMode);
/**
* @brief Decrypts an encrypted media frame
* @param decryptor Decryptor handle
* @param mediaType Media type (audio or video)
* @param encryptedFrame Pointer to the encrypted frame data
* @param encryptedFrameLength Length of the encrypted frame
* @param[out] frame Pointer to the output buffer the decrypted frame will be written to
* @param frameCapacity Capacity of the output buffer
* @param[out] bytesWritten Number of bytes written to the output buffer
* @return Result code indicating success or failure
*/
DAVE_EXPORT DAVEDecryptorResultCode daveDecryptorDecrypt(DAVEDecryptorHandle decryptor,
DAVEMediaType mediaType,
const uint8_t* encryptedFrame,
size_t encryptedFrameLength,
uint8_t* frame,
size_t frameCapacity,
size_t* bytesWritten);
/**
* @brief Calculates the maximum plaintext size for a given ciphertext frame size
* @param decryptor Decryptor handle
* @param mediaType Media type (audio or video)
* @param encryptedFrameSize Size of encrypted frame in bytes
* @return Maximum possible plaintext size in bytes
*/
DAVE_EXPORT size_t daveDecryptorGetMaxPlaintextByteSize(DAVEDecryptorHandle decryptor,
DAVEMediaType mediaType,
size_t encryptedFrameSize);
/**
* @brief Gets decryption statistics
* @param decryptor Decryptor handle
* @param mediaType Media type (audio or video)
* @param[out] stats Pointer to the stats structure to be filled
*/
DAVE_EXPORT void daveDecryptorGetStats(DAVEDecryptorHandle decryptor,
DAVEMediaType mediaType,
DAVEDecryptorStats* stats);
/*******************************************************************************
* Logging
******************************************************************************/
/**
* @brief Sets a global callback for receiving log messages from the library
* @param callback Log sink callback function
*/
DAVE_EXPORT void daveSetLogSinkCallback(DAVELogSinkCallback callback);
#ifdef __cplusplus
}
#endif
================================================
FILE: cpp/includes/dave/dave_interfaces.h
================================================
#pragma once
#include <functional>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <map>
#include <variant>
#include <chrono>
#include <vector>
#include <dave/array_view.h>
#include <dave/dave.h>
#include <dave/version.h>
namespace mlspp {
namespace bytes_ns {
struct bytes;
}; // namespace bytes_ns
struct SignaturePrivateKey;
} // namespace mlspp
namespace discord {
namespace dave {
using EncryptorStats = DAVEEncryptorStats;
using DecryptorStats = DAVEDecryptorStats;
using KeyGeneration = uint32_t;
using EncryptionKey = ::mlspp::bytes_ns::bytes;
class MlsKeyRatchet;
enum MediaType : uint8_t { Audio, Video };
enum Codec : uint8_t { Unknown, Opus, VP8, VP9, H264, H265, AV1 };
enum LoggingSeverity {
LS_VERBOSE,
LS_INFO,
LS_WARNING,
LS_ERROR,
LS_NONE,
};
// Returned in std::variant when a message is hard-rejected and should trigger a reset
struct failed_t {};
// Returned in std::variant when a message is soft-rejected and should not trigger a reset
struct ignored_t {};
// Map of ID-key pairs.
// In ProcessCommit, this lists IDs whose keys have been added, changed, or removed;
// an empty value value means a key was removed.
using RosterMap = std::map<uint64_t, std::vector<uint8_t>>;
// Return type for functions producing RosterMap or hard or soft failures
using RosterVariant = std::variant<failed_t, ignored_t, RosterMap>;
constexpr auto kDefaultTransitionDuration = std::chrono::seconds(10);
class IKeyRatchet {
public:
virtual ~IKeyRatchet() noexcept = default;
virtual EncryptionKey GetKey(KeyGeneration generation) noexcept = 0;
virtual void DeleteKey(KeyGeneration generation) noexcept = 0;
};
namespace mls {
#if defined(__ANDROID__)
typedef JNIEnv* KeyPairContextType;
#else
typedef const char* KeyPairContextType;
#endif
class ISession {
public:
virtual ~ISession() noexcept = default;
virtual void Init(ProtocolVersion version,
uint64_t groupId,
std::string const& selfUserId,
std::shared_ptr<::mlspp::SignaturePrivateKey>& transientKey) noexcept = 0;
virtual void Reset() noexcept = 0;
virtual void SetProtocolVersion(ProtocolVersion version) noexcept = 0;
virtual ProtocolVersion GetProtocolVersion() const noexcept = 0;
virtual std::vector<uint8_t> GetLastEpochAuthenticator() const noexcept = 0;
virtual void SetExternalSender(std::vector<uint8_t> const& externalSenderPackage) noexcept = 0;
virtual std::optional<std::vector<uint8_t>> ProcessProposals(
std::vector<uint8_t> proposals,
std::set<std::string> const& recognizedUserIDs) noexcept = 0;
virtual RosterVariant ProcessCommit(std::vector<uint8_t> commit) noexcept = 0;
virtual std::optional<RosterMap> ProcessWelcome(
std::vector<uint8_t> welcome,
std::set<std::string> const& recognizedUserIDs) noexcept = 0;
virtual std::vector<uint8_t> GetMarshalledKeyPackage() noexcept = 0;
virtual std::unique_ptr<IKeyRatchet> GetKeyRatchet(
std::string const& userId) const noexcept = 0;
using PairwiseFingerprintCallback = std::function<void(std::vector<uint8_t> const&)>;
virtual void GetPairwiseFingerprint(uint16_t version,
std::string const& userId,
PairwiseFingerprintCallback callback) const noexcept = 0;
};
using MLSFailureCallback = std::function<void(std::string const&, std::string const&)>;
std::unique_ptr<ISession> CreateSession(KeyPairContextType context,
std::string authSessionId,
MLSFailureCallback callback) noexcept;
} // namespace mls
class IEncryptor {
public:
enum ResultCode {
Success,
EncryptionFailure,
MissingKeyRatchet,
MissingCryptor,
TooManyAttempts,
};
virtual ~IEncryptor() = default;
virtual void SetKeyRatchet(std::unique_ptr<IKeyRatchet> keyRatchet) = 0;
virtual void SetPassthroughMode(bool passthroughMode) = 0;
virtual bool HasKeyRatchet() const = 0;
virtual bool IsPassthroughMode() const = 0;
virtual void AssignSsrcToCodec(uint32_t ssrc, Codec codecType) = 0;
virtual Codec CodecForSsrc(uint32_t ssrc) = 0;
virtual ResultCode Encrypt(MediaType mediaType,
uint32_t ssrc,
ArrayView<const uint8_t> frame,
ArrayView<uint8_t> encryptedFrame,
size_t* bytesWritten) = 0;
virtual size_t GetMaxCiphertextByteSize(MediaType mediaType, size_t frameSize) = 0;
virtual EncryptorStats GetStats(MediaType mediaType) const = 0;
using ProtocolVersionChangedCallback = std::function<void()>;
virtual void SetProtocolVersionChangedCallback(ProtocolVersionChangedCallback callback) = 0;
virtual ProtocolVersion GetProtocolVersion() const = 0;
};
std::unique_ptr<IEncryptor> CreateEncryptor();
class IDecryptor {
public:
using Duration = std::chrono::seconds;
enum ResultCode {
Success,
DecryptionFailure,
MissingKeyRatchet,
InvalidNonce,
MissingCryptor,
};
virtual ~IDecryptor() = default;
virtual void TransitionToKeyRatchet(std::unique_ptr<IKeyRatchet> keyRatchet,
Duration transitionExpiry = kDefaultTransitionDuration) = 0;
virtual void TransitionToPassthroughMode(
bool passthroughMode,
Duration transitionExpiry = kDefaultTransitionDuration) = 0;
virtual ResultCode Decrypt(MediaType mediaType,
ArrayView<const uint8_t> encryptedFrame,
ArrayView<uint8_t> frame,
size_t* bytesWritten) = 0;
virtual size_t GetMaxPlaintextByteSize(MediaType mediaType, size_t encryptedFrameSize) = 0;
virtual DecryptorStats GetStats(MediaType mediaType) const = 0;
};
std::unique_ptr<IDecryptor> CreateDecryptor();
static_assert(DAVE_CODEC_UNKNOWN == static_cast<int>(Codec::Unknown));
static_assert(DAVE_CODEC_OPUS == static_cast<int>(Codec::Opus));
static_assert(DAVE_CODEC_VP8 == static_cast<int>(Codec::VP8));
static_assert(DAVE_CODEC_VP9 == static_cast<int>(Codec::VP9));
static_assert(DAVE_CODEC_H264 == static_cast<int>(Codec::H264));
static_assert(DAVE_CODEC_H265 == static_cast<int>(Codec::H265));
static_assert(DAVE_CODEC_AV1 == static_cast<int>(Codec::AV1));
static_assert(DAVE_MEDIA_TYPE_AUDIO == static_cast<int>(MediaType::Audio));
static_assert(DAVE_MEDIA_TYPE_VIDEO == static_cast<int>(MediaType::Video));
static_assert(DAVE_ENCRYPTOR_RESULT_CODE_SUCCESS == static_cast<int>(IEncryptor::Success));
static_assert(DAVE_ENCRYPTOR_RESULT_CODE_ENCRYPTION_FAILURE ==
static_cast<int>(IEncryptor::EncryptionFailure));
static_assert(DAVE_ENCRYPTOR_RESULT_CODE_MISSING_KEY_RATCHET ==
static_cast<int>(IEncryptor::MissingKeyRatchet));
static_assert(DAVE_ENCRYPTOR_RESULT_CODE_MISSING_CRYPTOR ==
static_cast<int>(IEncryptor::MissingCryptor));
static_assert(DAVE_ENCRYPTOR_RESULT_CODE_TOO_MANY_ATTEMPTS ==
static_cast<int>(IEncryptor::TooManyAttempts));
static_assert(DAVE_DECRYPTOR_RESULT_CODE_SUCCESS == static_cast<int>(IDecryptor::Success));
static_assert(DAVE_DECRYPTOR_RESULT_CODE_DECRYPTION_FAILURE ==
static_cast<int>(IDecryptor::DecryptionFailure));
static_assert(DAVE_DECRYPTOR_RESULT_CODE_MISSING_KEY_RATCHET ==
static_cast<int>(IDecryptor::MissingKeyRatchet));
static_assert(DAVE_DECRYPTOR_RESULT_CODE_INVALID_NONCE ==
static_cast<int>(IDecryptor::InvalidNonce));
static_assert(DAVE_DECRYPTOR_RESULT_CODE_MISSING_CRYPTOR ==
static_cast<int>(IDecryptor::MissingCryptor));
static_assert(DAVE_LOGGING_SEVERITY_VERBOSE == static_cast<int>(LoggingSeverity::LS_VERBOSE));
static_assert(DAVE_LOGGING_SEVERITY_INFO == static_cast<int>(LoggingSeverity::LS_INFO));
static_assert(DAVE_LOGGING_SEVERITY_WARNING == static_cast<int>(LoggingSeverity::LS_WARNING));
static_assert(DAVE_LOGGING_SEVERITY_ERROR == static_cast<int>(LoggingSeverity::LS_ERROR));
static_assert(DAVE_LOGGING_SEVERITY_NONE == static_cast<int>(LoggingSeverity::LS_NONE));
} // namespace dave
} // namespace discord
================================================
FILE: cpp/includes/dave/logger.h
================================================
#pragma once
#include <sstream>
#include <dave/dave_interfaces.h>
#if !defined(DISCORD_LOG)
#define DISCORD_LOG_FILE_LINE(sev, file, line) ::discord::dave::LogStreamer(sev, file, line)
#define DISCORD_LOG(sev) DISCORD_LOG_FILE_LINE(::discord::dave::sev, __FILE__, __LINE__)
#endif
namespace discord {
namespace dave {
using LogSink = void (*)(LoggingSeverity severity,
const char* file,
int line,
const std::string& message);
void SetLogSink(LogSink sink);
class LogStreamer {
public:
LogStreamer(LoggingSeverity severity, const char* file, int line);
~LogStreamer();
template <typename T>
LogStreamer& operator<<(const T& value)
{
stream_ << value;
return *this;
}
private:
LoggingSeverity severity_;
const char* file_;
int line_;
std::ostringstream stream_;
};
} // namespace dave
} // namespace discord
================================================
FILE: cpp/includes/dave/version.h
================================================
#pragma once
#include <stdint.h>
#include <dave/dave.h>
namespace discord {
namespace dave {
using ProtocolVersion = uint16_t;
using SignatureVersion = uint8_t;
ProtocolVersion MaxSupportedProtocolVersion();
} // namespace dave
} // namespace discord
================================================
FILE: cpp/src/bindings_capi.cpp
================================================
#include <dave/dave_interfaces.h>
#include <atomic>
#include <cstdint>
#include <cstring>
#include <mls/messages.h>
#include <dave/logger.h>
#include <dave/version.h>
#include "mls_key_ratchet.h"
#define ARG_CHECK(arg) \
if (arg == nullptr) { \
fprintf(stderr, "ERROR: %s is null\n", #arg); \
assert(false); \
return; \
}
#define ARG_CHECK_RET(arg, ret) \
if (arg == nullptr) { \
fprintf(stderr, "ERROR: %s is null\n", #arg); \
assert(false); \
return ret; \
}
std::unique_ptr<discord::dave::MlsKeyRatchet> CopyKeyRatchet(DAVEKeyRatchetHandle keyRatchet)
{
auto mlsKeyRatchet = reinterpret_cast<discord::dave::MlsKeyRatchet*>(keyRatchet);
if (!mlsKeyRatchet) {
return nullptr;
}
auto hashRatchet = mlsKeyRatchet->GetHashRatchet();
auto cipherSuite = hashRatchet.suite;
auto baseSecret = hashRatchet.next_secret;
return std::make_unique<discord::dave::MlsKeyRatchet>(cipherSuite, std::move(baseSecret));
}
void CopyVectorToOutputBuffer(std::vector<uint8_t> const& vector, uint8_t** data, size_t* length)
{
if (data == nullptr || length == nullptr) {
return;
}
if (vector.empty()) {
*data = nullptr;
*length = 0;
return;
}
*data = reinterpret_cast<uint8_t*>(malloc(vector.size()));
memcpy(*data, vector.data(), vector.size());
*length = vector.size();
}
void GetRosterMemberIds(const discord::dave::RosterMap& rosterMap,
uint64_t** rosterIds,
size_t* rosterIdsLength)
{
*rosterIdsLength = rosterMap.size();
*rosterIds = reinterpret_cast<uint64_t*>(malloc(*rosterIdsLength * sizeof(uint64_t)));
size_t i = 0;
for (const auto& [key, value] : rosterMap) {
(*rosterIds)[i++] = key;
}
}
void GetRosterMemberSignature(const discord::dave::RosterMap& rosterMap,
uint64_t rosterId,
uint8_t** signature,
size_t* signatureLength)
{
CopyVectorToOutputBuffer(rosterMap.at(rosterId), signature, signatureLength);
}
uint16_t daveMaxSupportedProtocolVersion(void)
{
return discord::dave::MaxSupportedProtocolVersion();
}
void daveFree(void* ptr)
{
free(ptr);
}
DAVESessionHandle daveSessionCreate(void* context,
const char* authSessionId,
DAVEMLSFailureCallback callback,
void* userData)
{
discord::dave::mls::MLSFailureCallback mlsFailureCallback;
if (callback != nullptr) {
mlsFailureCallback = [callback, userData](std::string source, std::string reason) {
callback(source.c_str(), reason.c_str(), userData);
};
};
auto contextType = static_cast<discord::dave::mls::KeyPairContextType>(context);
auto authSessionIdStr = authSessionId ? std::string(authSessionId) : std::string();
auto session =
discord::dave::mls::CreateSession(contextType, authSessionIdStr, mlsFailureCallback);
return reinterpret_cast<DAVESessionHandle>(session.release());
}
void daveSessionDestroy(DAVESessionHandle sessionHandle)
{
auto session = reinterpret_cast<discord::dave::mls::ISession*>(sessionHandle);
delete session;
}
void daveSessionInit(DAVESessionHandle sessionHandle,
uint16_t version,
uint64_t groupId,
const char* selfUserId)
{
ARG_CHECK(sessionHandle);
auto session = reinterpret_cast<discord::dave::mls::ISession*>(sessionHandle);
auto selfUserIdStr = selfUserId ? std::string(selfUserId) : std::string();
std::shared_ptr<::mlspp::SignaturePrivateKey> transientKey;
session->Init(version, groupId, selfUserIdStr, transientKey);
}
void daveSessionReset(DAVESessionHandle sessionHandle)
{
ARG_CHECK(sessionHandle);
auto session = reinterpret_cast<discord::dave::mls::ISession*>(sessionHandle);
session->Reset();
}
void daveSessionSetProtocolVersion(DAVESessionHandle sessionHandle, uint16_t version)
{
ARG_CHECK(sessionHandle);
auto session = reinterpret_cast<discord::dave::mls::ISession*>(sessionHandle);
session->SetProtocolVersion(version);
}
uint16_t daveSessionGetProtocolVersion(DAVESessionHandle sessionHandle)
{
ARG_CHECK_RET(sessionHandle, 0);
auto session = reinterpret_cast<discord::dave::mls::ISession*>(sessionHandle);
return session->GetProtocolVersion();
}
void daveSessionGetLastEpochAuthenticator(DAVESessionHandle sessionHandle,
uint8_t** authenticator,
size_t* length)
{
ARG_CHECK(sessionHandle);
auto session = reinterpret_cast<discord::dave::mls::ISession*>(sessionHandle);
auto lastEpochAuthenticator = session->GetLastEpochAuthenticator();
CopyVectorToOutputBuffer(lastEpochAuthenticator, authenticator, length);
}
void daveSessionSetExternalSender(DAVESessionHandle sessionHandle,
const uint8_t* externalSender,
size_t length)
{
ARG_CHECK(sessionHandle);
auto session = reinterpret_cast<discord::dave::mls::ISession*>(sessionHandle);
auto externalSenderVec = std::vector<uint8_t>(externalSender, externalSender + length);
session->SetExternalSender(externalSenderVec);
}
void daveSessionProcessProposals(DAVESessionHandle sessionHandle,
const uint8_t* proposals,
size_t length,
const char** recognizedUserIds,
size_t recognizedUserIdsLength,
uint8_t** commitWelcomeBytes,
size_t* commitWelcomeBytesLength)
{
ARG_CHECK(sessionHandle);
auto session = reinterpret_cast<discord::dave::mls::ISession*>(sessionHandle);
auto proposalsVec = std::vector<uint8_t>(proposals, proposals + length);
auto recognizedUserIdsSet =
std::set<std::string>(recognizedUserIds, recognizedUserIds + recognizedUserIdsLength);
auto result =
session->ProcessProposals(std::move(proposalsVec), std::move(recognizedUserIdsSet));
if (result) {
CopyVectorToOutputBuffer(*result, commitWelcomeBytes, commitWelcomeBytesLength);
}
}
DAVECommitResultHandle daveSessionProcessCommit(DAVESessionHandle sessionHandle,
const uint8_t* commit,
size_t length)
{
ARG_CHECK_RET(sessionHandle, nullptr);
auto session = reinterpret_cast<discord::dave::mls::ISession*>(sessionHandle);
auto commitVec = std::vector<uint8_t>(commit, commit + length);
auto rosterVariant = session->ProcessCommit(std::move(commitVec));
auto rosterVariantPtr = new discord::dave::RosterVariant(std::move(rosterVariant));
return reinterpret_cast<DAVECommitResultHandle>(rosterVariantPtr);
}
DAVEWelcomeResultHandle daveSessionProcessWelcome(DAVESessionHandle sessionHandle,
const uint8_t* welcome,
size_t length,
const char** recognizedUserIds,
size_t recognizedUserIdsLength)
{
ARG_CHECK_RET(sessionHandle, nullptr);
auto session = reinterpret_cast<discord::dave::mls::ISession*>(sessionHandle);
auto welcomeVec = std::vector<uint8_t>(welcome, welcome + length);
auto recognizedUserIdsSet =
std::set<std::string>(recognizedUserIds, recognizedUserIds + recognizedUserIdsLength);
auto result = session->ProcessWelcome(std::move(welcomeVec), std::move(recognizedUserIdsSet));
if (!result) {
return nullptr;
}
auto rosterMapPtr = new discord::dave::RosterMap(std::move(*result));
return reinterpret_cast<DAVEWelcomeResultHandle>(rosterMapPtr);
}
void daveSessionGetMarshalledKeyPackage(DAVESessionHandle sessionHandle,
uint8_t** keyPackage,
size_t* length)
{
ARG_CHECK(sessionHandle);
auto session = reinterpret_cast<discord::dave::mls::ISession*>(sessionHandle);
auto keyPackageVec = session->GetMarshalledKeyPackage();
CopyVectorToOutputBuffer(keyPackageVec, keyPackage, length);
}
DAVEKeyRatchetHandle daveSessionGetKeyRatchet(DAVESessionHandle sessionHandle, const char* userId)
{
ARG_CHECK_RET(sessionHandle, nullptr);
auto session = reinterpret_cast<discord::dave::mls::ISession*>(sessionHandle);
auto userIdStr = userId ? std::string(userId) : std::string();
auto keyRatchetPtr = session->GetKeyRatchet(userIdStr);
return reinterpret_cast<DAVEKeyRatchetHandle>(keyRatchetPtr.release());
}
void daveSessionGetPairwiseFingerprint(DAVESessionHandle sessionHandle,
uint16_t version,
const char* userId,
DAVEPairwiseFingerprintCallback callback,
void* userData)
{
ARG_CHECK(sessionHandle);
auto session = reinterpret_cast<discord::dave::mls::ISession*>(sessionHandle);
auto userIdStr = userId ? std::string(userId) : std::string();
session->GetPairwiseFingerprint(
version, userIdStr, [callback, userData](std::vector<uint8_t> const& fingerprint) {
callback(fingerprint.data(), fingerprint.size(), userData);
});
}
void daveKeyRatchetDestroy(DAVEKeyRatchetHandle keyRatchet)
{
delete reinterpret_cast<discord::dave::MlsKeyRatchet*>(keyRatchet);
}
bool daveCommitResultIsFailed(DAVECommitResultHandle commitResultHandle)
{
ARG_CHECK_RET(commitResultHandle, false);
auto commitResult = reinterpret_cast<discord::dave::RosterVariant*>(commitResultHandle);
return std::holds_alternative<discord::dave::failed_t>(*commitResult);
}
bool daveCommitResultIsIgnored(DAVECommitResultHandle commitResultHandle)
{
ARG_CHECK_RET(commitResultHandle, false);
auto commitResult = reinterpret_cast<discord::dave::RosterVariant*>(commitResultHandle);
return std::holds_alternative<discord::dave::ignored_t>(*commitResult);
}
void daveCommitResultGetRosterMemberIds(DAVECommitResultHandle commitResultHandle,
uint64_t** rosterIds,
size_t* rosterIdsLength)
{
ARG_CHECK(commitResultHandle);
auto commitResult = reinterpret_cast<discord::dave::RosterVariant*>(commitResultHandle);
if (!std::holds_alternative<discord::dave::RosterMap>(*commitResult)) {
*rosterIds = nullptr;
*rosterIdsLength = 0;
return;
}
GetRosterMemberIds(
std::get<discord::dave::RosterMap>(*commitResult), rosterIds, rosterIdsLength);
}
void daveCommitResultGetRosterMemberSignature(DAVECommitResultHandle commitResultHandle,
uint64_t rosterId,
uint8_t** signature,
size_t* signatureLength)
{
ARG_CHECK(commitResultHandle);
auto commitResult = reinterpret_cast<discord::dave::RosterVariant*>(commitResultHandle);
if (!std::holds_alternative<discord::dave::RosterMap>(*commitResult)) {
*signature = nullptr;
*signatureLength = 0;
return;
}
GetRosterMemberSignature(
std::get<discord::dave::RosterMap>(*commitResult), rosterId, signature, signatureLength);
}
void daveCommitResultDestroy(DAVECommitResultHandle commitResultHandle)
{
auto commitResult = reinterpret_cast<discord::dave::RosterVariant*>(commitResultHandle);
delete commitResult;
}
void daveWelcomeResultGetRosterMemberIds(DAVEWelcomeResultHandle welcomeResultHandle,
uint64_t** rosterIds,
size_t* rosterIdsLength)
{
ARG_CHECK(welcomeResultHandle);
auto welcomeResult = reinterpret_cast<discord::dave::RosterMap*>(welcomeResultHandle);
GetRosterMemberIds(*welcomeResult, rosterIds, rosterIdsLength);
}
void daveWelcomeResultGetRosterMemberSignature(DAVEWelcomeResultHandle welcomeResultHandle,
uint64_t rosterId,
uint8_t** signature,
size_t* signatureLength)
{
ARG_CHECK(welcomeResultHandle);
auto welcomeResult = reinterpret_cast<discord::dave::RosterMap*>(welcomeResultHandle);
GetRosterMemberSignature(*welcomeResult, rosterId, signature, signatureLength);
}
void daveWelcomeResultDestroy(DAVEWelcomeResultHandle welcomeResultHandle)
{
auto welcomeResult = reinterpret_cast<discord::dave::RosterMap*>(welcomeResultHandle);
delete welcomeResult;
}
DAVEEncryptorHandle daveEncryptorCreate()
{
auto encryptor = discord::dave::CreateEncryptor();
return reinterpret_cast<DAVEEncryptorHandle>(encryptor.release());
}
void daveEncryptorDestroy(DAVEEncryptorHandle encryptorHandle)
{
auto encryptor = reinterpret_cast<discord::dave::IEncryptor*>(encryptorHandle);
delete encryptor;
}
void daveEncryptorSetKeyRatchet(DAVEEncryptorHandle encryptorHandle,
DAVEKeyRatchetHandle keyRatchet)
{
ARG_CHECK(encryptorHandle);
auto encryptor = reinterpret_cast<discord::dave::IEncryptor*>(encryptorHandle);
auto keyRatchetCopy = CopyKeyRatchet(keyRatchet);
encryptor->SetKeyRatchet(std::move(keyRatchetCopy));
}
void daveEncryptorSetPassthroughMode(DAVEEncryptorHandle encryptorHandle, bool passthroughMode)
{
ARG_CHECK(encryptorHandle);
auto encryptor = reinterpret_cast<discord::dave::IEncryptor*>(encryptorHandle);
encryptor->SetPassthroughMode(passthroughMode);
}
void daveEncryptorAssignSsrcToCodec(DAVEEncryptorHandle encryptorHandle,
uint32_t ssrc,
DAVECodec codecType)
{
ARG_CHECK(encryptorHandle);
auto encryptor = reinterpret_cast<discord::dave::IEncryptor*>(encryptorHandle);
encryptor->AssignSsrcToCodec(ssrc, static_cast<discord::dave::Codec>(codecType));
}
uint16_t daveEncryptorGetProtocolVersion(DAVEEncryptorHandle encryptorHandle)
{
ARG_CHECK_RET(encryptorHandle, 0);
auto encryptor = reinterpret_cast<discord::dave::IEncryptor*>(encryptorHandle);
return encryptor->GetProtocolVersion();
}
size_t daveEncryptorGetMaxCiphertextByteSize(DAVEEncryptorHandle encryptorHandle,
DAVEMediaType mediaType,
size_t frameSize)
{
ARG_CHECK_RET(encryptorHandle, 0);
auto encryptor = reinterpret_cast<discord::dave::IEncryptor*>(encryptorHandle);
return encryptor->GetMaxCiphertextByteSize(static_cast<discord::dave::MediaType>(mediaType),
frameSize);
}
bool daveEncryptorHasKeyRatchet(DAVEEncryptorHandle encryptorHandle)
{
ARG_CHECK_RET(encryptorHandle, false);
auto encryptor = reinterpret_cast<discord::dave::IEncryptor*>(encryptorHandle);
return encryptor->HasKeyRatchet();
}
bool daveEncryptorIsPassthroughMode(DAVEEncryptorHandle encryptorHandle)
{
ARG_CHECK_RET(encryptorHandle, false);
auto encryptor = reinterpret_cast<discord::dave::IEncryptor*>(encryptorHandle);
return encryptor->IsPassthroughMode();
}
DAVEEncryptorResultCode daveEncryptorEncrypt(DAVEEncryptorHandle encryptorHandle,
DAVEMediaType mediaType,
uint32_t ssrc,
const uint8_t* frame,
size_t frameLength,
uint8_t* encryptedFrame,
size_t encryptedFrameCapacity,
size_t* bytesWritten)
{
ARG_CHECK_RET(encryptorHandle, DAVE_ENCRYPTOR_RESULT_CODE_ENCRYPTION_FAILURE);
auto encryptor = reinterpret_cast<discord::dave::IEncryptor*>(encryptorHandle);
auto frameView = discord::dave::MakeArrayView(frame, frameLength);
auto encryptedFrameView = discord::dave::MakeArrayView(encryptedFrame, encryptedFrameCapacity);
auto result = encryptor->Encrypt(static_cast<discord::dave::MediaType>(mediaType),
ssrc,
frameView,
encryptedFrameView,
bytesWritten);
return static_cast<DAVEEncryptorResultCode>(result);
}
void daveEncryptorSetProtocolVersionChangedCallback(
DAVEEncryptorHandle encryptorHandle,
DAVEEncryptorProtocolVersionChangedCallback callback,
void* userData)
{
ARG_CHECK(encryptorHandle);
auto encryptor = reinterpret_cast<discord::dave::IEncryptor*>(encryptorHandle);
encryptor->SetProtocolVersionChangedCallback([callback, userData]() { callback(userData); });
}
void daveEncryptorGetStats(DAVEEncryptorHandle encryptorHandle,
DAVEMediaType mediaType,
DAVEEncryptorStats* stats)
{
ARG_CHECK(encryptorHandle);
auto encryptor = reinterpret_cast<discord::dave::IEncryptor*>(encryptorHandle);
*stats = encryptor->GetStats(static_cast<discord::dave::MediaType>(mediaType));
}
DAVEDecryptorHandle daveDecryptorCreate()
{
auto decryptor = discord::dave::CreateDecryptor();
return reinterpret_cast<DAVEDecryptorHandle>(decryptor.release());
}
void daveDecryptorDestroy(DAVEDecryptorHandle decryptorHandle)
{
auto decryptor = reinterpret_cast<discord::dave::IDecryptor*>(decryptorHandle);
delete decryptor;
}
void daveDecryptorTransitionToKeyRatchet(DAVEDecryptorHandle decryptorHandle,
DAVEKeyRatchetHandle keyRatchet)
{
ARG_CHECK(decryptorHandle);
auto decryptor = reinterpret_cast<discord::dave::IDecryptor*>(decryptorHandle);
auto keyRatchetCopy = CopyKeyRatchet(keyRatchet);
decryptor->TransitionToKeyRatchet(std::move(keyRatchetCopy));
}
void daveDecryptorTransitionToPassthroughMode(DAVEDecryptorHandle decryptorHandle,
bool passthroughMode)
{
ARG_CHECK(decryptorHandle);
auto decryptor = reinterpret_cast<discord::dave::IDecryptor*>(decryptorHandle);
decryptor->TransitionToPassthroughMode(passthroughMode);
}
DAVEDecryptorResultCode daveDecryptorDecrypt(DAVEDecryptorHandle decryptorHandle,
DAVEMediaType mediaType,
const uint8_t* encryptedFrame,
size_t encryptedFrameLength,
uint8_t* frame,
size_t frameCapacity,
size_t* bytesWritten)
{
ARG_CHECK_RET(decryptorHandle, DAVE_DECRYPTOR_RESULT_CODE_DECRYPTION_FAILURE);
auto decryptor = reinterpret_cast<discord::dave::IDecryptor*>(decryptorHandle);
auto encryptedFrameView = discord::dave::MakeArrayView(encryptedFrame, encryptedFrameLength);
auto frameView = discord::dave::MakeArrayView(frame, frameCapacity);
auto result = decryptor->Decrypt(static_cast<discord::dave::MediaType>(mediaType),
encryptedFrameView,
frameView,
bytesWritten);
return static_cast<DAVEDecryptorResultCode>(result);
}
size_t daveDecryptorGetMaxPlaintextByteSize(DAVEDecryptorHandle decryptorHandle,
DAVEMediaType mediaType,
size_t encryptedFrameSize)
{
ARG_CHECK_RET(decryptorHandle, 0);
auto decryptor = reinterpret_cast<discord::dave::IDecryptor*>(decryptorHandle);
return decryptor->GetMaxPlaintextByteSize(static_cast<discord::dave::MediaType>(mediaType),
encryptedFrameSize);
}
void daveDecryptorGetStats(DAVEDecryptorHandle decryptorHandle,
DAVEMediaType mediaType,
DAVEDecryptorStats* stats)
{
ARG_CHECK(decryptorHandle);
auto decryptor = reinterpret_cast<discord::dave::IDecryptor*>(decryptorHandle);
*stats = decryptor->GetStats(static_cast<discord::dave::MediaType>(mediaType));
}
static std::atomic<DAVELogSinkCallback> gLogSinkCallback{nullptr};
void LogSinkCallback(discord::dave::LoggingSeverity severity,
const char* file,
int line,
const std::string& message)
{
auto callback = gLogSinkCallback.load();
if (callback) {
callback(static_cast<DAVELoggingSeverity>(severity), file, line, message.c_str());
}
}
void daveSetLogSinkCallback(DAVELogSinkCallback callback)
{
gLogSinkCallback.store(callback);
discord::dave::SetLogSink(callback ? LogSinkCallback : nullptr);
}
================================================
FILE: cpp/src/bindings_wasm.cpp
================================================
#include <cstdint>
#include <memory>
#include <set>
#include <vector>
#include <emscripten.h>
#include <emscripten/bind.h>
#include <emscripten/val.h>
#include <mls/crypto.h>
#include <mls/key_schedule.h>
#include <dave/logger.h>
#include <dave/version.h>
#include "common.h"
#include "decryptor.h"
#include "encryptor.h"
#include "mls/parameters.h"
#include "mls/session.h"
#include "mls_key_ratchet.h"
using namespace emscripten;
namespace discord {
namespace dave {
val ToOwnedTypedArray(const uint8_t* data, size_t size)
{
val array = val::array();
for (size_t i = 0; i < size; i++) {
array.call<void>("push", data[i]);
}
return array;
}
val ToOwnedTypedArray(const ::mlspp::bytes_ns::bytes& data)
{
return ToOwnedTypedArray(data.data(), data.size());
}
val ToOwnedTypedArray(const std::vector<uint8_t>& data)
{
return ToOwnedTypedArray(data.data(), data.size());
}
val MlsKeyRatchetToJS(std::unique_ptr<MlsKeyRatchet> keyRatchet)
{
if (!keyRatchet) {
return val::null();
}
auto hashRatchet = keyRatchet->GetHashRatchet();
auto value = val::object();
value.set("cipherSuite", static_cast<uint16_t>(hashRatchet.suite.cipher_suite()));
value.set("baseSecret", ToOwnedTypedArray(hashRatchet.next_secret));
return value;
}
std::unique_ptr<MlsKeyRatchet> MlsKeyRatchetFromJS(val keyRatchet)
{
if (keyRatchet.isNull()) {
return nullptr;
}
auto cipherSuite = ::mlspp::CipherSuite(
static_cast<::mlspp::CipherSuite::ID>(keyRatchet["cipherSuite"].as<uint16_t>()));
auto baseSecret = emscripten::convertJSArrayToNumberVector<uint8_t>(keyRatchet["baseSecret"]);
auto baseSecretBytes = ::mlspp::bytes_ns::bytes(baseSecret);
return std::make_unique<MlsKeyRatchet>(cipherSuite, baseSecretBytes);
}
namespace mls {
class TransientKeys {
public:
std::shared_ptr<::mlspp::SignaturePrivateKey> GetTransientPrivateKey(ProtocolVersion version)
{
auto it = keys_.find(version);
if (it == keys_.end()) {
auto ciphersuite = CiphersuiteForProtocolVersion(version);
auto key = std::make_shared<::mlspp::SignaturePrivateKey>(
::mlspp::SignaturePrivateKey::generate(ciphersuite));
it = keys_.emplace(version, key).first;
}
return it->second;
}
void Clear() { keys_.clear(); }
private:
std::map<ProtocolVersion, std::shared_ptr<::mlspp::SignaturePrivateKey>> keys_;
};
class SessionWrapper {
public:
SessionWrapper(std::string ctx, std::string authSessionId, val callback)
{
session_ = std::make_unique<Session>(
ctx.c_str(), authSessionId, [callback](std::string source, std::string message) {
callback(source, message);
});
}
void Init(ProtocolVersion version,
uint64_t groupId,
std::string const& selfUserId,
std::shared_ptr<::mlspp::SignaturePrivateKey>& transientKey)
{
session_->Init(version, groupId, selfUserId, transientKey);
}
void Reset() { session_->Reset(); }
void SetProtocolVersion(ProtocolVersion version) { session_->SetProtocolVersion(version); }
ProtocolVersion GetProtocolVersion() { return session_->GetProtocolVersion(); }
val GetLastEpochAuthenticator()
{
return ToOwnedTypedArray(session_->GetLastEpochAuthenticator());
}
void SetExternalSender(val externalSender)
{
if (!externalSender.isNull()) {
std::vector<uint8_t> externalSenderVec =
emscripten::convertJSArrayToNumberVector<uint8_t>(externalSender);
session_->SetExternalSender(externalSenderVec);
}
else {
DISCORD_LOG(LS_ERROR) << "External sender is null";
}
}
val ProcessProposals(val proposals, val recognizedUserIDs)
{
std::vector<uint8_t> proposalsVec =
emscripten::convertJSArrayToNumberVector<uint8_t>(proposals);
auto recognizedUserIDsVec = emscripten::vecFromJSArray<std::string>(recognizedUserIDs);
auto recognizedUserIDsSet =
std::set<std::string>(recognizedUserIDsVec.begin(), recognizedUserIDsVec.end());
auto bytes = session_->ProcessProposals(proposalsVec, recognizedUserIDsSet);
if (!bytes) {
return val::null();
}
return ToOwnedTypedArray(*bytes);
}
val ProcessCommit(val commit)
{
std::vector<uint8_t> commitVec = emscripten::convertJSArrayToNumberVector<uint8_t>(commit);
auto processedCommit = session_->ProcessCommit(commitVec);
auto failed = std::holds_alternative<dave::failed_t>(processedCommit);
auto ignored = std::holds_alternative<dave::ignored_t>(processedCommit);
auto rosterUpdate = GetOptional<dave::RosterMap>(std::move(processedCommit));
val result = val::object();
result.set("failed", failed);
result.set("ignored", ignored);
val rosterObj = val::null();
if (rosterUpdate) {
rosterObj = val::object();
for (const auto& [key, value] : *rosterUpdate) {
rosterObj.set(key, ToOwnedTypedArray(value));
}
}
result.set("rosterUpdate", rosterObj);
return result;
}
val ProcessWelcome(val welcome, val recognizedUserIDs)
{
auto welcomeVec = emscripten::convertJSArrayToNumberVector<uint8_t>(welcome);
auto recognizedUserIDsVec = emscripten::vecFromJSArray<std::string>(recognizedUserIDs);
auto recognizedUserIDsSet =
std::set<std::string>(recognizedUserIDsVec.begin(), recognizedUserIDsVec.end());
auto roster = session_->ProcessWelcome(welcomeVec, recognizedUserIDsSet);
if (!roster) {
return val::null();
}
val rosterObj = val::object();
for (const auto& [key, value] : *roster) {
rosterObj.set(key, ToOwnedTypedArray(value));
}
return rosterObj;
}
val GetMarshalledKeyPackage() { return ToOwnedTypedArray(session_->GetMarshalledKeyPackage()); }
val GetKeyRatchet(std::string const& userId)
{
auto keyRatchet = session_->GetKeyRatchet(userId);
auto mlsKeyRatchet =
std::unique_ptr<MlsKeyRatchet>(static_cast<MlsKeyRatchet*>(keyRatchet.release()));
return MlsKeyRatchetToJS(std::move(mlsKeyRatchet));
}
private:
std::unique_ptr<mls::Session> session_;
};
} // namespace mls
class EncryptorWrapper {
public:
EncryptorWrapper() { encryptor_ = std::make_unique<Encryptor>(); }
void SetKeyRatchet(val keyRatchet)
{
encryptor_->SetKeyRatchet(MlsKeyRatchetFromJS(keyRatchet));
}
void SetPassthroughMode(bool passthroughMode)
{
encryptor_->SetPassthroughMode(passthroughMode);
}
void AssignSsrcToCodec(uint32_t ssrc, Codec codecType)
{
encryptor_->AssignSsrcToCodec(ssrc, codecType);
}
ProtocolVersion GetProtocolVersion() { return encryptor_->GetProtocolVersion(); }
size_t GetMaxCiphertextByteSize(MediaType mediaType, size_t plaintextByteSize)
{
return encryptor_->GetMaxCiphertextByteSize(mediaType, plaintextByteSize);
}
size_t Encrypt(MediaType mediaType,
uint32_t ssrc,
int framePtr,
size_t frameLength,
size_t frameCapacity)
{
auto frame = reinterpret_cast<uint8_t*>(framePtr);
auto frameView = MakeArrayView(const_cast<const uint8_t*>(frame), frameLength);
auto encryptedFrameMaxSize = GetMaxCiphertextByteSize(mediaType, frameLength);
if (frameCapacity < encryptedFrameMaxSize) {
DISCORD_LOG(LS_ERROR) << "Frame capacity is less than the maximum ciphertext size";
return 0;
}
auto encryptedFrameView = MakeArrayView(frame, encryptedFrameMaxSize);
size_t bytesWritten = 0;
auto result =
encryptor_->Encrypt(mediaType, ssrc, frameView, encryptedFrameView, &bytesWritten);
if (result != 0) {
return 0;
}
return bytesWritten;
}
void SetProtocolVersionChangedCallback(val callback)
{
encryptor_->SetProtocolVersionChangedCallback([callback]() { callback(); });
}
private:
std::unique_ptr<Encryptor> encryptor_;
};
class DecryptorWrapper {
public:
DecryptorWrapper() { decryptor_ = std::make_unique<Decryptor>(); }
void TransitionToKeyRatchet(val keyRatchet)
{
decryptor_->TransitionToKeyRatchet(MlsKeyRatchetFromJS(keyRatchet));
}
void TransitionToPassthroughMode(bool passthroughMode)
{
decryptor_->TransitionToPassthroughMode(passthroughMode);
}
size_t GetMaxPlaintextByteSize(MediaType mediaType, size_t ciphertextByteSize)
{
return decryptor_->GetMaxPlaintextByteSize(mediaType, ciphertextByteSize);
}
size_t Decrypt(MediaType mediaType, int framePtr, size_t frameLength, size_t frameCapacity)
{
auto frame = reinterpret_cast<uint8_t*>(framePtr);
auto frameView = MakeArrayView(const_cast<const uint8_t*>(frame), frameLength);
auto maxPlaintextByteSize = decryptor_->GetMaxPlaintextByteSize(mediaType, frameLength);
if (frameCapacity < maxPlaintextByteSize) {
DISCORD_LOG(LS_ERROR) << "Frame capacity is less than the maximum plaintext size";
return 0;
}
auto plaintextView = MakeArrayView(frame, maxPlaintextByteSize);
size_t bytesWritten = 0;
auto result = decryptor_->Decrypt(mediaType, frameView, plaintextView, &bytesWritten);
if (result != Decryptor::ResultCode::Success) {
return 0;
}
return bytesWritten;
}
private:
std::unique_ptr<Decryptor> decryptor_;
};
} // namespace dave
} // namespace discord
EMSCRIPTEN_BINDINGS(dave)
{
constant("kInitTransitionId", discord::dave::kInitTransitionId);
constant("kDisabledVersion", discord::dave::kDisabledVersion);
enum_<discord::dave::MediaType>("MediaType")
.value("Audio", discord::dave::MediaType::Audio)
.value("Video", discord::dave::MediaType::Video);
enum_<discord::dave::Codec>("Codec")
.value("Unknown", discord::dave::Codec::Unknown)
.value("Opus", discord::dave::Codec::Opus)
.value("VP8", discord::dave::Codec::VP8)
.value("VP9", discord::dave::Codec::VP9)
.value("H264", discord::dave::Codec::H264)
.value("H265", discord::dave::Codec::H265)
.value("AV1", discord::dave::Codec::AV1);
function("MaxSupportedProtocolVersion", &discord::dave::MaxSupportedProtocolVersion);
class_<::mlspp::SignaturePrivateKey>("SignaturePrivateKey")
.smart_ptr<std::shared_ptr<::mlspp::SignaturePrivateKey>>("SignaturePrivateKeyPtr");
class_<discord::dave::mls::TransientKeys>("TransientKeys")
.constructor<>()
.function("GetTransientPrivateKey",
&discord::dave::mls::TransientKeys::GetTransientPrivateKey)
.function("Clear", &discord::dave::mls::TransientKeys::Clear);
class_<discord::dave::mls::SessionWrapper>("Session")
.constructor<std::string, std::string, val>()
.function("Init", &discord::dave::mls::SessionWrapper::Init)
.function("Reset", &discord::dave::mls::SessionWrapper::Reset)
.function("SetProtocolVersion", &discord::dave::mls::SessionWrapper::SetProtocolVersion)
.function("GetProtocolVersion", &discord::dave::mls::SessionWrapper::GetProtocolVersion)
.function("GetLastEpochAuthenticator",
&discord::dave::mls::SessionWrapper::GetLastEpochAuthenticator)
.function("SetExternalSender", &discord::dave::mls::SessionWrapper::SetExternalSender)
.function("ProcessProposals", &discord::dave::mls::SessionWrapper::ProcessProposals)
.function("ProcessCommit", &discord::dave::mls::SessionWrapper::ProcessCommit)
.function("ProcessWelcome", &discord::dave::mls::SessionWrapper::ProcessWelcome)
.function("GetMarshalledKeyPackage",
&discord::dave::mls::SessionWrapper::GetMarshalledKeyPackage)
.function("GetKeyRatchet", &discord::dave::mls::SessionWrapper::GetKeyRatchet);
class_<discord::dave::EncryptorWrapper>("Encryptor")
.constructor<>()
.function("SetKeyRatchet", &discord::dave::EncryptorWrapper::SetKeyRatchet)
.function("SetPassthroughMode", &discord::dave::EncryptorWrapper::SetPassthroughMode)
.function("AssignSsrcToCodec", &discord::dave::EncryptorWrapper::AssignSsrcToCodec)
.function("GetProtocolVersion", &discord::dave::EncryptorWrapper::GetProtocolVersion)
.function("GetMaxCiphertextByteSize",
&discord::dave::EncryptorWrapper::GetMaxCiphertextByteSize)
.function(
"Encrypt", &discord::dave::EncryptorWrapper::Encrypt, emscripten::allow_raw_pointers())
.function("SetProtocolVersionChangedCallback",
&discord::dave::EncryptorWrapper::SetProtocolVersionChangedCallback);
class_<discord::dave::DecryptorWrapper>("Decryptor")
.constructor<>()
.function("TransitionToKeyRatchet", &discord::dave::DecryptorWrapper::TransitionToKeyRatchet)
.function("TransitionToPassthroughMode",
&discord::dave::DecryptorWrapper::TransitionToPassthroughMode)
.function("GetMaxPlaintextByteSize",
&discord::dave::DecryptorWrapper::GetMaxPlaintextByteSize)
.function(
"Decrypt", &discord::dave::DecryptorWrapper::Decrypt, emscripten::allow_raw_pointers());
}
================================================
FILE: cpp/src/boringssl_cryptor.cpp
================================================
#include "boringssl_cryptor.h"
#include <openssl/err.h>
#include <bytes/bytes.h>
#include <dave/logger.h>
#include "common.h"
namespace discord {
namespace dave {
void PrintSSLErrors()
{
ERR_print_errors_cb(
[](const char* str, size_t len, [[maybe_unused]] void* ctx) {
DISCORD_LOG(LS_ERROR) << std::string(str, len);
return 1;
},
nullptr);
}
BoringSSLCryptor::BoringSSLCryptor(const EncryptionKey& encryptionKey)
{
EVP_AEAD_CTX_zero(&cipherCtx_);
auto initResult = EVP_AEAD_CTX_init(&cipherCtx_,
EVP_aead_aes_128_gcm(),
encryptionKey.data(),
encryptionKey.size(),
kAesGcm128TruncatedTagBytes,
nullptr);
if (initResult != 1) {
DISCORD_LOG(LS_ERROR) << "Failed to initialize AEAD context";
PrintSSLErrors();
}
}
BoringSSLCryptor::~BoringSSLCryptor()
{
EVP_AEAD_CTX_cleanup(&cipherCtx_);
}
bool BoringSSLCryptor::Encrypt(ArrayView<uint8_t> ciphertextBufferOut,
ArrayView<const uint8_t> plaintextBuffer,
ArrayView<const uint8_t> nonceBuffer,
ArrayView<const uint8_t> additionalData,
ArrayView<uint8_t> tagBufferOut)
{
if (cipherCtx_.aead == nullptr) {
DISCORD_LOG(LS_ERROR) << "Encrypt: AEAD context is not initialized";
return false;
}
size_t tagSizeOut;
auto encryptResult = EVP_AEAD_CTX_seal_scatter(&cipherCtx_,
ciphertextBufferOut.data(),
tagBufferOut.data(),
&tagSizeOut,
kAesGcm128TruncatedTagBytes,
nonceBuffer.data(),
kAesGcm128NonceBytes,
plaintextBuffer.data(),
plaintextBuffer.size(),
nullptr,
0,
additionalData.data(),
additionalData.size());
if (encryptResult != 1) {
DISCORD_LOG(LS_ERROR) << "Failed to encrypt data";
PrintSSLErrors();
}
return encryptResult == 1;
}
bool BoringSSLCryptor::Decrypt(ArrayView<uint8_t> plaintextBufferOut,
ArrayView<const uint8_t> ciphertextBuffer,
ArrayView<const uint8_t> tagBuffer,
ArrayView<const uint8_t> nonceBuffer,
ArrayView<const uint8_t> additionalData)
{
if (cipherCtx_.aead == nullptr) {
DISCORD_LOG(LS_ERROR) << "Decrypt: AEAD context is not initialized";
return false;
}
auto decryptResult = EVP_AEAD_CTX_open_gather(&cipherCtx_,
plaintextBufferOut.data(),
nonceBuffer.data(),
kAesGcm128NonceBytes,
ciphertextBuffer.data(),
ciphertextBuffer.size(),
tagBuffer.data(),
kAesGcm128TruncatedTagBytes,
additionalData.data(),
additionalData.size());
return decryptResult == 1;
}
} // namespace dave
} // namespace discord
================================================
FILE: cpp/src/boringssl_cryptor.h
================================================
#pragma once
#include <openssl/aead.h>
#include "cryptor.h"
namespace discord {
namespace dave {
class BoringSSLCryptor : public ICryptor {
public:
BoringSSLCryptor(const EncryptionKey& encryptionKey);
~BoringSSLCryptor();
bool IsValid() const { return cipherCtx_.aead != nullptr; }
bool Encrypt(ArrayView<uint8_t> ciphertextBufferOut,
ArrayView<const uint8_t> plaintextBuffer,
ArrayView<const uint8_t> nonceBuffer,
ArrayView<const uint8_t> additionalData,
ArrayView<uint8_t> tagBufferOut) override;
bool Decrypt(ArrayView<uint8_t> plaintextBufferOut,
ArrayView<const uint8_t> ciphertextBuffer,
ArrayView<const uint8_t> tagBuffer,
ArrayView<const uint8_t> nonceBuffer,
ArrayView<const uint8_t> additionalData) override;
private:
EVP_AEAD_CTX cipherCtx_;
};
} // namespace dave
} // namespace discord
================================================
FILE: cpp/src/codec_utils.cpp
================================================
#include "codec_utils.h"
#include <cassert>
#include <limits>
#include <optional>
#include <dave/logger.h>
#include "common.h"
#include "utils/leb128.h"
namespace discord {
namespace dave {
namespace codec_utils {
UnencryptedFrameHeaderSize BytesCoveringH264PPS(const uint8_t* payload,
const uint64_t sizeRemaining)
{
// 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 kEmulationPreventionByte = 0x03;
uint64_t payloadBitIndex = 0;
auto zeroBitCount = 0;
auto parsedExpGolombValues = 0;
while (payloadBitIndex < sizeRemaining * 8 && parsedExpGolombValues < 3) {
auto bitIndex = payloadBitIndex % 8;
auto byteIndex = payloadBitIndex / 8;
auto payloadByte = payload[byteIndex];
// if we're starting a new byte
// check if this is an emulation prevention byte
// which we skip over
if (bitIndex == 0) {
if (byteIndex >= 2 && payloadByte == kEmulationPreventionByte &&
payload[byteIndex - 1] == 0 && payload[byteIndex - 2] == 0) {
payloadBitIndex += 8;
continue;
}
}
if ((payloadByte & (1 << (7 - bitIndex))) == 0) {
// still in the run of leading zero bits
++zeroBitCount;
++payloadBitIndex;
if (zeroBitCount >= 32) {
assert(false && "Unexpectedly large exponential golomb encoded value");
return 0;
}
}
else {
// we hit a one
// skip forward the number of bits dictated by the leading number of zeroes
parsedExpGolombValues += 1;
payloadBitIndex += 1 + zeroBitCount;
zeroBitCount = 0;
}
}
// return the number of bytes that covers the last exp golomb encoded value
auto result = (payloadBitIndex / 8) + 1;
if (result > std::numeric_limits<UnencryptedFrameHeaderSize>::max()) {
DISCORD_LOG(LS_WARNING)
<< "BytesCoveringH264PPS result cannot fit in UnencryptedFrameHeaderSize";
return 0;
}
else {
return static_cast<UnencryptedFrameHeaderSize>(result);
}
}
const uint8_t kH26XNaluLongStartCode[] = {0, 0, 0, 1};
constexpr uint8_t kH26XNaluShortStartSequenceSize = 3;
using IndexStartCodeSizePair = std::pair<size_t, size_t>;
std::optional<IndexStartCodeSizePair> FindNextH26XNaluIndex(const uint8_t* buffer,
const size_t bufferSize,
const size_t searchStartIndex = 0)
{
constexpr uint8_t kH26XStartCodeHighestPossibleValue = 1;
constexpr uint8_t kH26XStartCodeEndByteValue = 1;
constexpr uint8_t kH26XStartCodeLeadingBytesValue = 0;
if (bufferSize < kH26XNaluShortStartSequenceSize) {
return std::nullopt;
}
// look for NAL unit 3 or 4 byte start code
for (size_t i = searchStartIndex; i < bufferSize - kH26XNaluShortStartSequenceSize;) {
if (buffer[i + 2] > kH26XStartCodeHighestPossibleValue) {
// third byte is not 0 or 1, can't be a start code
i += kH26XNaluShortStartSequenceSize;
}
else if (buffer[i + 2] == kH26XStartCodeEndByteValue) {
// third byte matches the start code end byte, might be a start code sequence
if (buffer[i + 1] == kH26XStartCodeLeadingBytesValue &&
buffer[i] == kH26XStartCodeLeadingBytesValue) {
// confirmed start sequence {0, 0, 1}
auto nalUnitStartIndex = i + kH26XNaluShortStartSequenceSize;
if (i >= 1 && buffer[i - 1] == kH26XStartCodeLeadingBytesValue) {
// 4 byte start code
return std::optional<IndexStartCodeSizePair>({nalUnitStartIndex, 4});
}
else {
// 3 byte start code
return std::optional<IndexStartCodeSizePair>({nalUnitStartIndex, 3});
}
}
i += kH26XNaluShortStartSequenceSize;
}
else {
// third byte is 0, might be a four byte start code
++i;
}
}
return std::nullopt;
}
bool ProcessFrameOpus(OutboundFrameProcessor& processor, ArrayView<const uint8_t> frame)
{
processor.AddEncryptedBytes(frame.data(), frame.size());
return true;
}
bool ProcessFrameVp8(OutboundFrameProcessor& processor, ArrayView<const uint8_t> frame)
{
constexpr uint8_t kVP8KeyFrameUnencryptedBytes = 10;
constexpr uint8_t kVP8DeltaFrameUnencryptedBytes = 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 unencryptedHeaderBytes = 0;
if ((frame.data()[0] & 0x01) == 0) {
unencryptedHeaderBytes = kVP8KeyFrameUnencryptedBytes;
}
else {
unencryptedHeaderBytes = kVP8DeltaFrameUnencryptedBytes;
}
processor.AddUnencryptedBytes(frame.data(), unencryptedHeaderBytes);
processor.AddEncryptedBytes(frame.data() + unencryptedHeaderBytes,
frame.size() - unencryptedHeaderBytes);
return true;
}
bool ProcessFrameVp9(OutboundFrameProcessor& processor, ArrayView<const uint8_t> frame)
{
// payload descriptor is unencrypted in each packet
// and includes all information the depacketizer needs
processor.AddEncryptedBytes(frame.data(), frame.size());
return true;
}
bool ProcessFrameH264(OutboundFrameProcessor& processor, ArrayView<const uint8_t> 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 kH264SBit = 0x80;
constexpr uint8_t kH264NalHeaderTypeMask = 0x1F;
constexpr uint8_t kH264NalTypeSlice = 1;
constexpr uint8_t kH264NalTypeIdr = 5;
constexpr uint8_t kH264NalUnitHeaderSize = 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() < kH26XNaluShortStartSequenceSize + kH264NalUnitHeaderSize) {
assert(false && "H264 frame is too small to contain a NAL unit");
DISCORD_LOG(LS_WARNING) << "H264 frame is too small to contain a NAL unit";
return false;
}
auto naluIndexPair = FindNextH26XNaluIndex(frame.data(), frame.size());
while (naluIndexPair && naluIndexPair->first < frame.size() - 1) {
auto [nalUnitStartIndex, startCodeSize] = *naluIndexPair;
auto nalType = frame.data()[nalUnitStartIndex] & kH264NalHeaderTypeMask;
// 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.AddUnencryptedBytes(kH26XNaluLongStartCode, sizeof(kH26XNaluLongStartCode));
auto nextNaluIndexPair =
FindNextH26XNaluIndex(frame.data(), frame.size(), nalUnitStartIndex);
auto nextNaluStart = nextNaluIndexPair.has_value()
? nextNaluIndexPair->first - nextNaluIndexPair->second
: frame.size();
if (nalType == kH264NalTypeSlice || nalType == kH264NalTypeIdr) {
// once we've hit a slice or an IDR
// we just need to cover getting to the PPS ID
auto nalUnitPayloadStart = nalUnitStartIndex + kH264NalUnitHeaderSize;
auto nalUnitPPSBytes = BytesCoveringH264PPS(frame.data() + nalUnitPayloadStart,
frame.size() - nalUnitPayloadStart);
processor.AddUnencryptedBytes(frame.data() + nalUnitStartIndex,
kH264NalUnitHeaderSize + nalUnitPPSBytes);
processor.AddEncryptedBytes(
frame.data() + nalUnitStartIndex + kH264NalUnitHeaderSize + nalUnitPPSBytes,
nextNaluStart - nalUnitStartIndex - kH264NalUnitHeaderSize - nalUnitPPSBytes);
}
else {
// copy the whole NAL unit
processor.AddUnencryptedBytes(frame.data() + nalUnitStartIndex,
nextNaluStart - nalUnitStartIndex);
}
naluIndexPair = nextNaluIndexPair;
}
return true;
}
bool ProcessFrameH265(OutboundFrameProcessor& processor, ArrayView<const uint8_t> 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 kH265NalHeaderTypeMask = 0x7E;
constexpr uint8_t kH265NalTypeVclCutoff = 32;
constexpr uint8_t kH265NalUnitHeaderSize = 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() < kH26XNaluShortStartSequenceSize + kH265NalUnitHeaderSize) {
assert(false && "H265 frame is too small to contain a NAL unit");
DISCORD_LOG(LS_WARNING) << "H265 frame is too small to contain a NAL unit";
return false;
}
// look for NAL unit 3 or 4 byte start code
auto naluIndexPair = FindNextH26XNaluIndex(frame.data(), frame.size());
while (naluIndexPair && naluIndexPair->first < frame.size() - 1) {
auto [nalUnitStartIndex, startCodeSize] = *naluIndexPair;
uint8_t nalType = (frame.data()[nalUnitStartIndex] & kH265NalHeaderTypeMask) >> 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.AddUnencryptedBytes(kH26XNaluLongStartCode, sizeof(kH26XNaluLongStartCode));
auto nextNaluIndexPair =
FindNextH26XNaluIndex(frame.data(), frame.size(), nalUnitStartIndex);
auto nextNaluStart = nextNaluIndexPair.has_value()
? nextNaluIndexPair->first - nextNaluIndexPair->second
: frame.size();
if (nalType < kH265NalTypeVclCutoff) {
// found a VCL NAL, encrypt the payload only
processor.AddUnencryptedBytes(frame.data() + nalUnitStartIndex, kH265NalUnitHeaderSize);
processor.AddEncryptedBytes(frame.data() + nalUnitStartIndex + kH265NalUnitHeaderSize,
nextNaluStart - nalUnitStartIndex - kH265NalUnitHeaderSize);
}
else {
// copy the whole NAL unit
processor.AddUnencryptedBytes(frame.data() + nalUnitStartIndex,
nextNaluStart - nalUnitStartIndex);
}
naluIndexPair = nextNaluIndexPair;
}
return true;
}
bool ProcessFrameAv1(OutboundFrameProcessor& processor, ArrayView<const uint8_t> frame)
{
constexpr uint8_t kAv1ObuHeaderHasExtensionMask = 0b0'0000'100;
constexpr uint8_t kAv1ObuHeaderHasSizeMask = 0b0'0000'010;
constexpr uint8_t kAv1ObuHeaderTypeMask = 0b0'1111'000;
constexpr uint8_t kObuTypeTemporalDelimiter = 2;
constexpr uint8_t kObuTypeTileList = 8;
constexpr uint8_t kObuTypePadding = 15;
constexpr uint8_t kObuExtensionSizeBytes = 1;
size_t i = 0;
while (i < frame.size()) {
// Read the OBU header.
size_t obuHeaderIndex = i;
uint8_t obuHeader = frame.data()[obuHeaderIndex];
i += sizeof(obuHeader);
bool obuHasExtension = obuHeader & kAv1ObuHeaderHasExtensionMask;
bool obuHasSize = obuHeader & kAv1ObuHeaderHasSizeMask;
int obuType = (obuHeader & kAv1ObuHeaderTypeMask) >> 3;
if (obuHasExtension) {
// Skip extension byte
i += kObuExtensionSizeBytes;
}
if (i >= frame.size()) {
// Malformed frame
assert(false && "Malformed AV1 frame: header overflows frame");
DISCORD_LOG(LS_WARNING) << "Malformed AV1 frame: header overflows frame";
return false;
}
size_t obuPayloadSize = 0;
if (obuHasSize) {
// Read payload size
const uint8_t* start = frame.data() + i;
const uint8_t* ptr = start;
obuPayloadSize = ReadLeb128(ptr, frame.end());
if (!ptr) {
// Malformed frame
assert(false && "Malformed AV1 frame: invalid LEB128 size");
DISCORD_LOG(LS_WARNING) << "Malformed AV1 frame: invalid LEB128 size";
return false;
}
i += ptr - start;
}
else {
// If the size is not present, the OBU extends to the end of the frame.
obuPayloadSize = frame.size() - i;
}
const auto obuPayloadIndex = i;
if (i + obuPayloadSize > frame.size()) {
// Malformed frame
assert(false && "Malformed AV1 frame: payload overflows frame");
DISCORD_LOG(LS_WARNING) << "Malformed AV1 frame: payload overflows frame";
return false;
}
i += obuPayloadSize;
// We only copy the OBUs that will not get dropped by the packetizer
if (obuType != kObuTypeTemporalDelimiter && obuType != kObuTypeTileList &&
obuType != kObuTypePadding) {
// 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 rewrittenWithoutSize = false;
if (i == frame.size() && obuHasSize) {
// Flip the "has size" bit
obuHeader &= ~kAv1ObuHeaderHasSizeMask;
rewrittenWithoutSize = true;
}
// write the OBU header unencrypted
processor.AddUnencryptedBytes(&obuHeader, sizeof(obuHeader));
if (obuHasExtension) {
// write the extension byte unencrypted
processor.AddUnencryptedBytes(frame.data() + obuHeaderIndex + sizeof(obuHeader),
kObuExtensionSizeBytes);
}
// write the OBU payload size unencrypted if it was present and we didn't rewrite
// without it
if (obuHasSize && !rewrittenWithoutSize) {
// 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[Leb128MaxSize];
size_t additionalBytesToWrite = WriteLeb128(obuPayloadSize, leb128Buffer);
processor.AddUnencryptedBytes(leb128Buffer, additionalBytesToWrite);
}
// add the OBU payload, encrypted
processor.AddEncryptedBytes(frame.data() + obuPayloadIndex, obuPayloadSize);
}
}
return true;
}
bool ValidateEncryptedFrame(OutboundFrameProcessor& processor, ArrayView<uint8_t> frame)
{
auto codec = processor.GetCodec();
if (codec != Codec::H264 && codec != Codec::H265) {
return true;
}
static_assert(kH26XNaluShortStartSequenceSize - 1 >= 0, "Padding will overflow!");
constexpr size_t Padding = kH26XNaluShortStartSequenceSize - 1;
const auto& unencryptedRanges = processor.GetUnencryptedRanges();
// 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 encryptedSectionStart = 0;
for (auto& range : unencryptedRanges) {
if (encryptedSectionStart == range.offset) {
encryptedSectionStart += range.size;
continue;
}
auto start = encryptedSectionStart - std::min(encryptedSectionStart, size_t{Padding});
auto end = std::min(range.offset + Padding, frame.size());
if (FindNextH26XNaluIndex(frame.data() + start, end - start)) {
return false;
}
encryptedSectionStart = range.offset + range.size;
}
if (encryptedSectionStart == frame.size()) {
return true;
}
auto start = encryptedSectionStart - std::min(encryptedSectionStart, size_t{Padding});
auto end = frame.size();
if (FindNextH26XNaluIndex(frame.data() + start, end - start)) {
return false;
}
return true;
}
} // namespace codec_utils
} // namespace dave
} // namespace discord
================================================
FILE: cpp/src/codec_utils.h
================================================
#pragma once
#include <dave/array_view.h>
#include "common.h"
#include "frame_processors.h"
namespace discord {
namespace dave {
namespace codec_utils {
bool ProcessFrameOpus(OutboundFrameProcessor& processor, ArrayView<const uint8_t> frame);
bool ProcessFrameVp8(OutboundFrameProcessor& processor, ArrayView<const uint8_t> frame);
bool ProcessFrameVp9(OutboundFrameProcessor& processor, ArrayView<const uint8_t> frame);
bool ProcessFrameH264(OutboundFrameProcessor& processor, ArrayView<const uint8_t> frame);
bool ProcessFrameH265(OutboundFrameProcessor& processor, ArrayView<const uint8_t> frame);
bool ProcessFrameAv1(OutboundFrameProcessor& processor, ArrayView<const uint8_t> frame);
bool ValidateEncryptedFrame(OutboundFrameProcessor& processor, ArrayView<uint8_t> frame);
} // namespace codec_utils
} // namespace dave
} // namespace discord
================================================
FILE: cpp/src/common.h
================================================
#pragma once
#include <array>
#include <chrono>
#include <map>
#include <optional>
#include <string>
#include <variant>
#include <vector>
#include <dave/version.h>
namespace discord {
namespace dave {
using UnencryptedFrameHeaderSize = uint16_t;
using TruncatedSyncNonce = uint32_t;
using MagicMarker = uint16_t;
using TransitionId = uint16_t;
using SupplementalBytesSize = uint8_t;
constexpr MagicMarker kMarkerBytes = 0xFAFA;
// Layout constants
constexpr size_t kAesGcm128KeyBytes = 16;
constexpr size_t kAesGcm128NonceBytes = 12;
constexpr size_t kAesGcm128TruncatedSyncNonceBytes = 4;
constexpr size_t kAesGcm128TruncatedSyncNonceOffset =
kAesGcm128NonceBytes - kAesGcm128TruncatedSyncNonceBytes;
constexpr size_t kAesGcm128TruncatedTagBytes = 8;
constexpr size_t kRatchetGenerationBytes = 1;
constexpr size_t kRatchetGenerationShiftBits =
8 * (kAesGcm128TruncatedSyncNonceBytes - kRatchetGenerationBytes);
constexpr size_t kSupplementalBytes =
kAesGcm128TruncatedTagBytes + sizeof(SupplementalBytesSize) + sizeof(MagicMarker);
constexpr size_t kTransformPaddingBytes = 64;
// Timing constants
constexpr auto kCryptorExpiry = std::chrono::seconds(10);
// Behavior constants
constexpr auto kInitTransitionId = 0;
constexpr auto kDisabledVersion = 0;
constexpr auto kMaxGenerationGap = 250;
constexpr auto kMaxMissingNonces = 1000;
constexpr auto kGenerationWrap = 1 << (8 * kRatchetGenerationBytes);
constexpr auto kMaxFramesPerSecond = 50 + 2 * 60; // 50 audio frames + 2 * 60fps video streams
constexpr std::array<uint8_t, 3> kOpusSilencePacket = {0xF8, 0xFF, 0xFE};
// Utility routine for variant return types
template <class T, class V>
inline std::optional<T> GetOptional(V&& variant)
{
if (auto map = std::get_if<T>(&variant)) {
if constexpr (std::is_rvalue_reference_v<decltype(variant)>) {
return std::move(*map);
}
else {
return *map;
}
}
else {
return std::nullopt;
}
}
} // namespace dave
} // namespace discord
================================================
FILE: cpp/src/cryptor.cpp
================================================
#include "cryptor.h"
#ifdef WITH_BORINGSSL
#include "boringssl_cryptor.h"
#else
#include "openssl_cryptor.h"
#endif
namespace discord {
namespace dave {
std::unique_ptr<ICryptor> CreateCryptor(const EncryptionKey& encryptionKey)
{
#ifdef WITH_BORINGSSL
auto cryptor = std::make_unique<BoringSSLCryptor>(encryptionKey);
#else
auto cryptor = std::make_unique<OpenSSLCryptor>(encryptionKey);
#endif
return cryptor->IsValid() ? std::move(cryptor) : nullptr;
}
} // namespace dave
} // namespace discord
================================================
FILE: cpp/src/cryptor.h
================================================
#pragma once
#include <memory>
#include <dave/array_view.h>
#include <dave/dave_interfaces.h>
namespace discord {
namespace dave {
class ICryptor {
public:
virtual ~ICryptor() = default;
virtual bool Encrypt(ArrayView<uint8_t> ciphertextBufferOut,
ArrayView<const uint8_t> plaintextBuffer,
ArrayView<const uint8_t> nonceBuffer,
ArrayView<const uint8_t> additionalData,
ArrayView<uint8_t> tagBufferOut) = 0;
virtual bool Decrypt(ArrayView<uint8_t> plaintextBufferOut,
ArrayView<const uint8_t> ciphertextBuffer,
ArrayView<const uint8_t> tagBuffer,
ArrayView<const uint8_t> nonceBuffer,
ArrayView<const uint8_t> additionalData) = 0;
};
std::unique_ptr<ICryptor> CreateCryptor(const EncryptionKey& encryptionKey);
} // namespace dave
} // namespace discord
================================================
FILE: cpp/src/cryptor_manager.cpp
================================================
#include "cryptor_manager.h"
#include <limits>
#include <dave/logger.h>
#include <bytes/bytes.h>
using namespace std::chrono_literals;
namespace discord {
namespace dave {
KeyGeneration ComputeWrappedGeneration(KeyGeneration oldest, KeyGeneration 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 % kGenerationWrap;
auto factor = oldest / kGenerationWrap + (generation < remainder ? 1 : 0);
return factor * kGenerationWrap + generation;
}
BigNonce ComputeWrappedBigNonce(KeyGeneration generation, TruncatedSyncNonce nonce)
{
// Remove the generation bits from the nonce
auto maskedNonce = nonce & ((1 << kRatchetGenerationShiftBits) - 1);
// Add the wrapped generation bits back in
return static_cast<BigNonce>(generation) << kRatchetGenerationShiftBits | maskedNonce;
}
CryptorManager::CryptorManager(const IClock& clock, std::unique_ptr<IKeyRatchet> keyRatchet)
: clock_(clock)
, keyRatchet_(std::move(keyRatchet))
, ratchetCreation_(clock.Now())
, ratchetExpiry_(TimePoint::max())
{
}
bool CryptorManager::CanProcessNonce(KeyGeneration generation, TruncatedSyncNonce nonce) const
{
if (!newestProcessedNonce_) {
return true;
}
auto bigNonce = ComputeWrappedBigNonce(generation, nonce);
return bigNonce > *newestProcessedNonce_ ||
std::find(missingNonces_.rbegin(), missingNonces_.rend(), bigNonce) != missingNonces_.rend();
}
ICryptor* CryptorManager::GetCryptor(KeyGeneration generation)
{
CleanupExpiredCryptors();
if (generation < oldestGeneration_) {
DISCORD_LOG(LS_INFO) << "Received frame with old generation: " << generation
<< ", oldest generation: " << oldestGeneration_;
return nullptr;
}
if (generation > newestGeneration_ + kMaxGenerationGap) {
DISCORD_LOG(LS_INFO) << "Received frame with future generation: " << generation
<< ", newest generation: " << newestGeneration_;
return nullptr;
}
auto ratchetLifetimeSec =
std::chrono::duration_cast<std::chrono::seconds>(clock_.Now() - ratchetCreation_).count();
auto maxLifetimeFrames = kMaxFramesPerSecond * ratchetLifetimeSec;
auto maxLifetimeGenerations = maxLifetimeFrames >> kRatchetGenerationShiftBits;
if (generation > maxLifetimeGenerations) {
DISCORD_LOG(LS_INFO) << "Received frame with generation " << generation
<< " beyond ratchet max lifetime generations: "
<< maxLifetimeGenerations
<< ", ratchet lifetime: " << ratchetLifetimeSec << "s";
return nullptr;
}
auto it = cryptors_.find(generation);
if (it == cryptors_.end()) {
// We don't have a cryptor for this generation, create one
std::tie(it, std::ignore) = cryptors_.emplace(generation, MakeExpiringCryptor(generation));
}
// Return a non-owning pointer to the cryptor
auto& [cryptor, expiry] = it->second;
return cryptor.get();
}
void CryptorManager::ReportCryptorSuccess(KeyGeneration generation, TruncatedSyncNonce nonce)
{
auto bigNonce = ComputeWrappedBigNonce(generation, nonce);
// Add any missing nonces to the queue
if (!newestProcessedNonce_) {
newestProcessedNonce_ = bigNonce;
}
else if (bigNonce > *newestProcessedNonce_) {
auto missingNonces =
std::min(bigNonce - *newestProcessedNonce_ - 1, static_cast<uint64_t>(kMaxMissingNonces));
while (!missingNonces_.empty() &&
missingNonces_.size() + missingNonces > kMaxMissingNonces) {
missingNonces_.pop_front();
}
for (auto i = bigNonce - missingNonces; i < bigNonce; ++i) {
missingNonces_.push_back(i);
}
// Update the newest processed nonce
newestProcessedNonce_ = bigNonce;
}
else {
auto it = std::find(missingNonces_.begin(), missingNonces_.end(), bigNonce);
if (it != missingNonces_.end()) {
missingNonces_.erase(it);
}
}
if (generation <= newestGeneration_ || cryptors_.find(generation) == cryptors_.end()) {
return;
}
DISCORD_LOG(LS_INFO) << "Reporting cryptor success, generation: " << generation;
newestGeneration_ = generation;
// Update the expiry time for all old cryptors
const auto expiryTime = clock_.Now() + kCryptorExpiry;
for (auto& [gen, cryptor] : cryptors_) {
if (gen < newestGeneration_) {
DISCORD_LOG(LS_INFO) << "Updating expiry for cryptor, generation: " << gen;
cryptor.expiry = std::min(cryptor.expiry, expiryTime);
}
}
}
KeyGeneration CryptorManager::ComputeWrappedGeneration(KeyGeneration generation) const
{
return ::discord::dave::ComputeWrappedGeneration(oldestGeneration_, generation);
}
CryptorManager::ExpiringCryptor CryptorManager::MakeExpiringCryptor(KeyGeneration generation)
{
// Get the new key from the ratchet
auto encryptionKey = keyRatchet_->GetKey(generation);
auto expiryTime = TimePoint::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 < newestGeneration_) {
DISCORD_LOG(LS_INFO) << "Creating cryptor for old generation: " << generation;
expiryTime = clock_.Now() + kCryptorExpiry;
}
else {
DISCORD_LOG(LS_INFO) << "Creating cryptor for new generation: " << generation;
}
return {CreateCryptor(encryptionKey), expiryTime};
}
void CryptorManager::CleanupExpiredCryptors()
{
for (auto it = cryptors_.begin(); it != cryptors_.end();) {
auto& [generation, cryptor] = *it;
bool expired = cryptor.expiry < clock_.Now();
if (expired) {
DISCORD_LOG(LS_INFO) << "Removing expired cryptor, generation: " << generation;
}
it = expired ? cryptors_.erase(it) : ++it;
}
while (oldestGeneration_ < newestGeneration_ &&
cryptors_.find(oldestGeneration_) == cryptors_.end()) {
DISCORD_LOG(LS_INFO) << "Deleting key for old generation: " << oldestGeneration_;
keyRatchet_->DeleteKey(oldestGeneration_);
++oldestGeneration_;
}
}
} // namespace dave
} // namespace discord
================================================
FILE: cpp/src/cryptor_manager.h
================================================
#pragma once
#include <deque>
#include <memory>
#include <optional>
#include <unordered_map>
#include "common.h"
#include "cryptor.h"
#include "utils/clock.h"
namespace discord {
namespace dave {
KeyGeneration ComputeWrappedGeneration(KeyGeneration oldest, KeyGeneration generation);
using BigNonce = uint64_t;
BigNonce ComputeWrappedBigNonce(KeyGeneration generation, TruncatedSyncNonce nonce);
class CryptorManager {
public:
using TimePoint = typename IClock::TimePoint;
CryptorManager(const IClock& clock, std::unique_ptr<IKeyRatchet> keyRatchet);
void UpdateExpiry(TimePoint expiry) { ratchetExpiry_ = expiry; }
bool IsExpired() const { return clock_.Now() > ratchetExpiry_; }
bool CanProcessNonce(KeyGeneration generation, TruncatedSyncNonce nonce) const;
KeyGeneration ComputeWrappedGeneration(KeyGeneration generation) const;
ICryptor* GetCryptor(KeyGeneration generation);
void ReportCryptorSuccess(KeyGeneration generation, TruncatedSyncNonce nonce);
private:
struct ExpiringCryptor {
std::unique_ptr<ICryptor> cryptor;
TimePoint expiry;
};
ExpiringCryptor MakeExpiringCryptor(KeyGeneration generation);
void CleanupExpiredCryptors();
const IClock& clock_;
std::unique_ptr<IKeyRatchet> keyRatchet_;
std::unordered_map<KeyGeneration, ExpiringCryptor> cryptors_;
TimePoint ratchetCreation_;
TimePoint ratchetExpiry_;
KeyGeneration oldestGeneration_{0};
KeyGeneration newestGeneration_{0};
std::optional<BigNonce> newestProcessedNonce_;
std::deque<BigNonce> missingNonces_;
};
} // namespace dave
} // namespace discord
================================================
FILE: cpp/src/decryptor.cpp
================================================
#include "decryptor.h"
#include <cstring>
#include <bytes/bytes.h>
#include <dave/logger.h>
#include "common.h"
#include "utils/leb128.h"
#include "utils/scope_exit.h"
using namespace std::chrono_literals;
namespace discord {
namespace dave {
constexpr auto kStatsInterval = 10s;
std::unique_ptr<IDecryptor> CreateDecryptor()
{
return std::make_unique<Decryptor>();
}
void Decryptor::TransitionToKeyRatchet(std::unique_ptr<IKeyRatchet> keyRatchet,
Duration transitionExpiry)
{
DISCORD_LOG(LS_INFO) << "Transitioning to new key ratchet: " << keyRatchet.get()
<< ", expiry: " << transitionExpiry.count();
// Update the expiry time for all existing cryptor managers
UpdateCryptorManagerExpiry(transitionExpiry);
if (keyRatchet) {
cryptorManagers_.emplace_back(clock_, std::move(keyRatchet));
}
}
void Decryptor::TransitionToPassthroughMode(bool passthroughMode, Duration transitionExpiry)
{
if (passthroughMode) {
allowPassThroughUntil_ = TimePoint::max();
}
else {
// Update the pass through mode expiry
auto maxExpiry = clock_.Now() + transitionExpiry;
allowPassThroughUntil_ = std::min(allowPassThroughUntil_, maxExpiry);
}
}
Decryptor::ResultCode Decryptor::Decrypt(MediaType mediaType,
ArrayView<const uint8_t> encryptedFrame,
ArrayView<uint8_t> frame,
size_t* bytesWritten)
{
if (mediaType != Audio && mediaType != Video) {
DISCORD_LOG(LS_WARNING) << "Decrypt failed, invalid media type: "
<< static_cast<int>(mediaType);
*bytesWritten = 0;
return ResultCode::DecryptionFailure;
}
auto& stats = stats_[mediaType];
auto start = clock_.Now();
auto localFrame = GetOrCreateFrameProcessor();
ScopeExit cleanup([&] { ReturnFrameProcessor(std::move(localFrame)); });
// Skip decrypting for silence frames
if (mediaType == Audio && encryptedFrame.size() == kOpusSilencePacket.size() &&
memcmp(encryptedFrame.data(), kOpusSilencePacket.data(), kOpusSilencePacket.size()) == 0) {
DISCORD_LOG(LS_VERBOSE) << "Decrypt skipping silence of size: " << encryptedFrame.size();
auto copySize = std::min(frame.size(), encryptedFrame.size());
if (encryptedFrame.data() != frame.data()) {
memcpy(frame.data(), encryptedFrame.data(), copySize);
}
*bytesWritten = copySize;
return ResultCode::Success;
}
// Remove any expired cryptor manager
CleanupExpiredCryptorManagers();
// 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
localFrame->ParseFrame(encryptedFrame);
// If the frame is not encrypted and we can pass it through, do it
bool canUsePassThrough = allowPassThroughUntil_ > start;
if (!localFrame->IsEncrypted() && canUsePassThrough) {
auto copySize = std::min(frame.size(), encryptedFrame.size());
if (encryptedFrame.data() != frame.data()) {
memcpy(frame.data(), encryptedFrame.data(), copySize);
}
stats_[mediaType].passthroughCount++;
*bytesWritten = copySize;
return ResultCode::Success;
}
// If the frame is not encrypted and we can't pass it through, fail
if (!localFrame->IsEncrypted()) {
DISCORD_LOG(LS_INFO)
<< "Decrypt failed, frame is not encrypted and pass through is disabled";
stats_[mediaType].decryptFailureCount++;
*bytesWritten = 0;
return ResultCode::DecryptionFailure;
}
// Try and decrypt with each valid cryptor
// reverse iterate to try the newest cryptors first
auto result = ResultCode::MissingKeyRatchet;
for (auto it = cryptorManagers_.rbegin(); it != cryptorManagers_.rend(); ++it) {
auto& cryptorManager = *it;
result = DecryptImpl(cryptorManager, mediaType, *localFrame);
if (result == ResultCode::Success) {
break;
}
}
size_t reconstructedFrameSize = 0;
if (result == ResultCode::Success) {
stats.decryptSuccessCount++;
reconstructedFrameSize = localFrame->ReconstructFrame(frame);
}
else {
stats.decryptFailureCount++;
DISCORD_LOG(LS_WARNING) << "Decrypt failed, no valid cryptor found, type: "
<< (mediaType ? "video" : "audio")
<< ", encrypted frame size: " << encryptedFrame.size()
<< ", plaintext frame size: " << frame.size()
<< ", number of cryptor managers: " << cryptorManagers_.size()
<< ", pass through enabled: " << (canUsePassThrough ? "yes" : "no");
if (result == ResultCode::InvalidNonce) {
stats.decryptInvalidNonceCount++;
}
else if (result == ResultCode::MissingKeyRatchet) {
stats.decryptMissingKeyCount++;
}
}
auto end = clock_.Now();
if (end > lastStatsTime_ + kStatsInterval) {
lastStatsTime_ = end;
DISCORD_LOG(LS_INFO) << "Decrypted audio: " << stats_[Audio].decryptSuccessCount
<< ", video: " << stats_[Video].decryptSuccessCount
<< ". Failed audio: " << stats_[Audio].decryptFailureCount
<< ", video: " << stats_[Video].decryptFailureCount;
}
stats.decryptDuration +=
std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
*bytesWritten = reconstructedFrameSize;
return result;
}
Decryptor::ResultCode Decryptor::DecryptImpl(CryptorManager& cryptorManager,
MediaType mediaType,
InboundFrameProcessor& encryptedFrame)
{
auto tag = encryptedFrame.GetTag();
auto truncatedNonce = encryptedFrame.GetTruncatedNonce();
auto authenticatedData = encryptedFrame.GetAuthenticatedData();
auto ciphertext = encryptedFrame.GetCiphertext();
auto plaintext = encryptedFrame.GetPlaintext();
// expand the truncated nonce to the full sized one needed for decryption
auto nonceBuffer = std::array<uint8_t, kAesGcm128NonceBytes>();
memcpy(nonceBuffer.data() + kAesGcm128TruncatedSyncNonceOffset,
&truncatedNonce,
kAesGcm128TruncatedSyncNonceBytes);
auto nonceBufferView = MakeArrayView<const uint8_t>(nonceBuffer.data(), nonceBuffer.size());
auto generation =
cryptorManager.ComputeWrappedGeneration(truncatedNonce >> kRatchetGenerationShiftBits);
if (!cryptorManager.CanProcessNonce(generation, truncatedNonce)) {
DISCORD_LOG(LS_INFO) << "Decrypt failed, cannot process nonce: " << truncatedNonce;
return ResultCode::InvalidNonce;
}
// Get the cryptor for this generation
ICryptor* cryptor = cryptorManager.GetCryptor(generation);
if (!cryptor) {
DISCORD_LOG(LS_INFO) << "Decrypt failed, no cryptor found for generation: " << generation;
return ResultCode::MissingCryptor;
}
// perform the decryption
bool success = cryptor->Decrypt(plaintext, ciphertext, tag, nonceBufferView, authenticatedData);
stats_[mediaType].decryptAttempts++;
if (success) {
cryptorManager.ReportCryptorSuccess(generation, truncatedNonce);
}
return success ? ResultCode::Success : ResultCode::DecryptionFailure;
}
size_t Decryptor::GetMaxPlaintextByteSize([[maybe_unused]] MediaType mediaType,
size_t encryptedFrameSize)
{
return encryptedFrameSize;
}
void Decryptor::UpdateCryptorManagerExpiry(Duration expiry)
{
auto maxExpiryTime = clock_.Now() + expiry;
for (auto& cryptorManager : cryptorManagers_) {
cryptorManager.UpdateExpiry(maxExpiryTime);
}
}
void Decryptor::CleanupExpiredCryptorManagers()
{
while (!cryptorManagers_.empty() && cryptorManagers_.front().IsExpired()) {
DISCORD_LOG(LS_INFO) << "Removing expired cryptor manager.";
cryptorManagers_.pop_front();
}
}
std::unique_ptr<InboundFrameProcessor> Decryptor::GetOrCreateFrameProcessor()
{
std::lock_guard<std::mutex> lock(frameProcessorsMutex_);
if (frameProcessors_.empty()) {
return std::make_unique<InboundFrameProcessor>();
}
auto frameProcessor = std::move(frameProcessors_.back());
frameProcessors_.pop_back();
return frameProcessor;
}
void Decryptor::ReturnFrameProcessor(std::unique_ptr<InboundFrameProcessor> frameProcessor)
{
std::lock_guard<std::mutex> lock(frameProcessorsMutex_);
frameProcessors_.push_back(std::move(frameProcessor));
}
} // namespace dave
} // namespace discord
================================================
FILE: cpp/src/decryptor.h
================================================
#pragma once
#include <array>
#include <deque>
#include <functional>
#include <memory>
#include <mutex>
#include <vector>
#include <dave/dave_interfaces.h>
#include <dave/version.h>
#include "codec_utils.h"
#include "common.h"
#include "cryptor.h"
#include "cryptor_manager.h"
#include "frame_processors.h"
#include "utils/clock.h"
namespace discord {
namespace dave {
class IKeyRatchet;
class Decryptor final : public IDecryptor {
public:
using Duration = std::chrono::seconds;
virtual ~Decryptor() noexcept = default;
virtual void TransitionToKeyRatchet(
std::unique_ptr<IKeyRatchet> keyRatchet,
Duration transitionExpiry = kDefaultTransitionDuration) override;
virtual void TransitionToPassthroughMode(
bool passthroughMode,
Duration transitionExpiry = kDefaultTransitionDuration) override;
virtual ResultCode Decrypt(MediaType mediaType,
ArrayView<const uint8_t> encryptedFrame,
ArrayView<uint8_t> frame,
size_t* bytesWritten) override;
virtual size_t GetMaxPlaintextByteSize(MediaType mediaType, size_t encryptedFrameSize) override;
virtual DecryptorStats GetStats(MediaType mediaType) const override
{
return stats_[mediaType];
}
private:
using TimePoint = IClock::TimePoint;
Decryptor::ResultCode DecryptImpl(CryptorManager& cryptor,
MediaType mediaType,
InboundFrameProcessor& encryptedFrame);
void UpdateCryptorManagerExpiry(Duration expiry);
void CleanupExpiredCryptorManagers();
std::unique_ptr<InboundFrameProcessor> GetOrCreateFrameProcessor();
void ReturnFrameProcessor(std::unique_ptr<InboundFrameProcessor> frameProcessor);
Clock clock_;
std::deque<CryptorManager> cryptorManagers_;
std::mutex frameProcessorsMutex_;
std::vector<std::unique_ptr<InboundFrameProcessor>> frameProcessors_;
TimePoint allowPassThroughUntil_{TimePoint::min()};
TimePoint lastStatsTime_{TimePoint::min()};
std::array<DecryptorStats, 2> stats_;
};
} // namespace dave
} // namespace discord
================================================
FILE: cpp/src/encryptor.cpp
================================================
#include "encryptor.h"
#include <algorithm>
#include <cstring>
#include <bytes/bytes.h>
#include <dave/array_view.h>
#include <dave/logger.h>
#include "codec_utils.h"
#include "common.h"
#include "cryptor_manager.h"
#include "utils/leb128.h"
#include "utils/scope_exit.h"
using namespace std::chrono_literals;
namespace discord {
namespace dave {
constexpr auto kStatsInterval = 10s;
std::unique_ptr<IEncryptor> CreateEncryptor()
{
return std::make_unique<Encryptor>();
}
void Encryptor::SetKeyRatchet(std::unique_ptr<IKeyRatchet> keyRatchet)
{
std::lock_guard<std::mutex> lock(keyGenMutex_);
keyRatchet_ = std::move(keyRatchet);
cryptor_ = nullptr;
currentKeyGeneration_ = 0;
truncatedNonce_ = 0;
}
void Encryptor::SetPassthroughMode(bool passthroughMode)
{
passthroughMode_ = passthroughMode;
UpdateCurrentProtocolVersion(passthroughMode ? 0 : MaxSupportedProtocolVersion());
}
Encryptor::ResultCode Encryptor::Encrypt(MediaType mediaType,
uint32_t ssrc,
ArrayView<const uint8_t> frame,
ArrayView<uint8_t> encryptedFrame,
size_t* bytesWritten)
{
if (mediaType != Audio && mediaType != Video) {
DISCORD_LOG(LS_WARNING) << "Encrypt failed, invalid media type: "
<< static_cast<int>(mediaType);
return ResultCode::EncryptionFailure;
}
auto& stats = stats_[mediaType];
if (passthroughMode_) {
// Pass frame through without encrypting
auto copySize = std::min(encryptedFrame.size(), frame.size());
memcpy(encryptedFrame.data(), frame.data(), copySize);
*bytesWritten = copySize;
stats.passthroughCount++;
return ResultCode::Success;
}
{
std::lock_guard<std::mutex> lock(keyGenMutex_);
if (!keyRatchet_) {
stats.encryptFailureCount++;
stats.encryptMissingKeyCount++;
return ResultCode::MissingKeyRatchet;
}
}
auto start = std::chrono::steady_clock::now();
auto result = ResultCode::Success;
// write the codec identifier
auto codec = CodecForSsrc(ssrc);
auto frameProcessor = GetOrCreateFrameProcessor();
ScopeExit cleanup([&] { ReturnFrameProcessor(std::move(frameProcessor)); });
frameProcessor->ProcessFrame(frame, codec);
const auto& unencryptedBytes = frameProcessor->GetUnencryptedBytes();
const auto& encryptedBytes = frameProcessor->GetEncryptedBytes();
auto& ciphertextBytes = frameProcessor->GetCiphertextBytes();
const auto& unencryptedRanges = frameProcessor->GetUnencryptedRanges();
auto unencryptedRangesSize = UnencryptedRangesSize(unencryptedRanges);
auto additionalData = MakeArrayView(unencryptedBytes.data(), unencryptedBytes.size());
auto plaintextBuffer = MakeArrayView(encryptedBytes.data(), encryptedBytes.size());
auto ciphertextBuffer = MakeArrayView(ciphertextBytes.data(), ciphertextBytes.size());
auto frameSize = encryptedBytes.size() + unencryptedBytes.size();
auto tagBuffer = MakeArrayView(encryptedFrame.data() + frameSize, kAesGcm128TruncatedTagBytes);
auto nonceBuffer = std::array<uint8_t, kAesGcm128NonceBytes>();
auto nonceBufferView = MakeArrayView<const uint8_t>(nonceBuffer.data(), nonceBuffer.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 [cryptor, truncatedNonce] = GetNextCryptorAndNonce();
if (!cryptor) {
stats.encryptMissingKeyCount++;
result = ResultCode::MissingCryptor;
break;
}
// write the truncated nonce to our temporary full nonce array
// (since the encryption call expects a full size nonce)
memcpy(nonceBuffer.data() + kAesGcm128TruncatedSyncNonceOffset,
&truncatedNonce,
kAesGcm128TruncatedSyncNonceBytes);
// encrypt the plaintext, adding the unencrypted header to the tag
bool success = cryptor->Encrypt(
ciphertextBuffer, plaintextBuffer, nonceBufferView, additionalData, tagBuffer);
stats.encryptAttempts++;
stats.encryptMaxAttempts = std::max(stats.encryptMaxAttempts, (uint64_t)attempt);
if (!success) {
assert(false && "Failed to encrypt frame");
result = ResultCode::EncryptionFailure;
break;
}
auto reconstructedFrameSize = frameProcessor->ReconstructFrame(encryptedFrame);
assert(reconstructedFrameSize == frameSize && "Failed to reconstruct frame");
auto nonceSize = Leb128Size(truncatedNonce);
auto truncatedNonceBuffer = MakeArrayView(tagBuffer.end(), nonceSize);
auto unencryptedRangesBuffer =
MakeArrayView(truncatedNonceBuffer.end(), unencryptedRangesSize);
auto supplementalBytesBuffer =
MakeArrayView(unencryptedRangesBuffer.end(), sizeof(SupplementalBytesSize));
auto markerBytesBuffer = MakeArrayView(supplementalBytesBuffer.end(), sizeof(MagicMarker));
// write the nonce
auto res = WriteLeb128(truncatedNonce, truncatedNonceBuffer.begin());
if (res != nonceSize) {
assert(false && "Failed to write truncated nonce");
result = ResultCode::EncryptionFailure;
break;
}
// write the unencrypted ranges
res = SerializeUnencryptedRanges(
unencryptedRanges, unencryptedRangesBuffer.begin(), unencryptedRangesBuffer.size());
if (res != unencryptedRangesSize) {
assert(false && "Failed to write unencrypted ranges");
result = ResultCode::EncryptionFailure;
break;
}
// write the supplemental bytes size
uint64_t supplementalBytesLarge = kSupplementalBytes + nonceSize + unencryptedRangesSize;
if (supplementalBytesLarge > std::numeric_limits<SupplementalBytesSize>::max()) {
assert(false && "Supplemental bytes size too large");
result = ResultCode::EncryptionFailure;
break;
}
SupplementalBytesSize supplementalBytes =
static_cast<SupplementalBytesSize>(supplementalBytesLarge);
memcpy(supplementalBytesBuffer.data(), &supplementalBytes, sizeof(SupplementalBytesSize));
// write the marker bytes, ends the frame
memcpy(markerBytesBuffer.data(), &kMarkerBytes, sizeof(MagicMarker));
auto encryptedFrameBytes = reconstructedFrameSize + kAesGcm128TruncatedTagBytes +
nonceSize + unencryptedRangesSize + sizeof(SupplementalBytesSize) + sizeof(MagicMarker);
if (codec_utils::ValidateEncryptedFrame(
*frameProcessor, MakeArrayView(encryptedFrame.data(), encryptedFrameBytes))) {
*bytesWritten = encryptedFrameBytes;
break;
}
else if (attempt >= MAX_CIPHERTEXT_VALIDATION_RETRIES) {
assert(false && "Failed to validate encrypted section for codec");
result = ResultCode::TooManyAttempts;
break;
}
}
auto now = std::chrono::steady_clock::now();
stats.encryptDuration +=
std::chrono::duration_cast<std::chrono::microseconds>(now - start).count();
if (result == ResultCode::Success) {
stats.encryptSuccessCount++;
}
else {
stats.encryptFailureCount++;
}
if (now > lastStatsTime_ + kStatsInterval) {
lastStatsTime_ = now;
DISCORD_LOG(LS_INFO) << "Encrypted audio: " << stats_[Audio].encryptSuccessCount
<< ", video: " << stats_[Video].encryptSuccessCount
<< ". Failed audio: " << stats_[Audio].encryptFailureCount
<< ", video: " << stats_[Video].encryptFailureCount;
DISCORD_LOG(LS_INFO) << "Last encrypted frame, type: "
<< (mediaType == Audio ? "audio" : "video") << ", ssrc: " << ssrc
<< ", size: " << frame.size();
}
return result;
}
size_t Encryptor::GetMaxCiphertextByteSize([[maybe_unused]] MediaType mediaType, size_t frameSize)
{
return frameSize + kSupplementalBytes + kTransformPaddingBytes;
}
void Encryptor::AssignSsrcToCodec(uint32_t ssrc, Codec codecType)
{
auto existingCodecIt = std::find_if(
ssrcCodecPairs_.begin(), ssrcCodecPairs_.end(), [ssrc](const SsrcCodecPair& pair) {
return pair.first == ssrc;
});
if (existingCodecIt == ssrcCodecPairs_.end()) {
ssrcCodecPairs_.emplace_back(ssrc, codecType);
}
else {
existingCodecIt->second = codecType;
}
}
Codec Encryptor::CodecForSsrc(uint32_t ssrc)
{
auto existingCodecIt = std::find_if(
ssrcCodecPairs_.begin(), ssrcCodecPairs_.end(), [ssrc](const SsrcCodecPair& pair) {
return pair.first == ssrc;
});
if (existingCodecIt != ssrcCodecPairs_.end()) {
return existingCodecIt->second;
}
else {
return Codec::Unknown;
}
}
std::unique_ptr<OutboundFrameProcessor> Encryptor::GetOrCreateFrameProcessor()
{
std::lock_guard<std::mutex> lock(frameProcessorsMutex_);
if (frameProcessors_.empty()) {
return std::make_unique<OutboundFrameProcessor>();
}
auto frameProcessor = std::move(frameProcessors_.back());
frameProcessors_.pop_back();
return frameProcessor;
}
void Encryptor::ReturnFrameProcessor(std::unique_ptr<OutboundFrameProcessor> frameProcessor)
{
std::lock_guard<std::mutex> lock(frameProcessorsMutex_);
frameProcessors_.push_back(std::move(frameProcessor));
}
Encryptor::CryptorAndNonce Encryptor::GetNextCryptorAndNonce()
{
std::lock_guard<std::mutex> lock(keyGenMutex_);
if (!keyRatchet_) {
return {nullptr, 0};
}
auto generation = ComputeWrappedGeneration(currentKeyGeneration_,
++truncatedNonce_ >> kRatchetGenerationShiftBits);
if (generation != currentKeyGeneration_ || !cryptor_) {
currentKeyGeneration_ = generation;
auto encryptionKey = keyRatchet_->GetKey(currentKeyGeneration_);
cryptor_ = CreateCryptor(encryptionKey);
}
return {cryptor_, truncatedNonce_};
}
void Encryptor::UpdateCurrentProtocolVersion(ProtocolVersion version)
{
if (version == currentProtocolVersion_) {
return;
}
currentProtocolVersion_ = version;
if (protocolVersionChangedCallback_) {
protocolVersionChangedCallback_();
}
}
} // namespace dave
} // namespace discord
================================================
FILE: cpp/src/encryptor.h
================================================
#pragma once
#include <array>
#include <atomic>
#include <chrono>
#include <functional>
#include <memory>
#include <mutex>
#include <vector>
#include <dave/dave_interfaces.h>
#include <dave/version.h>
#include "codec_utils.h"
#include "common.h"
#include "cryptor.h"
#include "frame_processors.h"
namespace discord {
namespace dave {
class Encryptor final : public IEncryptor {
public:
virtual ~Encryptor() noexcept = default;
virtual void SetKeyRatchet(std::unique_ptr<IKeyRatchet> keyRatchet) override;
virtual void SetPassthroughMode(bool passthroughMode) override;
virtual bool HasKeyRatchet() const override { return keyRatchet_ != nullptr; }
virtual bool IsPassthroughMode() const override { return passthroughMode_; }
virtual void AssignSsrcToCodec(uint32_t ssrc, Codec codecType) override;
virtual Codec CodecForSsrc(uint32_t ssrc) override;
virtual ResultCode Encrypt(MediaType mediaType,
uint32_t ssrc,
ArrayView<const uint8_t> frame,
ArrayView<uint8_t> encryptedFrame,
size_t* bytesWritten) override;
virtual size_t GetMaxCiphertextByteSize(MediaType mediaType, size_t frameSize) override;
virtual EncryptorStats GetStats(MediaType mediaType) const override
{
return stats_[mediaType];
}
using ProtocolVersionChangedCallback = std::function<void()>;
virtual void SetProtocolVersionChangedCallback(ProtocolVersionChangedCallback callback) override
{
protocolVersionChangedCallback_ = std::move(callback);
}
virtual ProtocolVersion GetProtocolVersion() const override { return currentProtocolVersion_; }
private:
std::unique_ptr<OutboundFrameProcessor> GetOrCreateFrameProcessor();
void ReturnFrameProcessor(std::unique_ptr<OutboundFrameProcessor> frameProcessor);
using CryptorAndNonce = std::pair<std::shared_ptr<ICryptor>, TruncatedSyncNonce>;
CryptorAndNonce GetNextCryptorAndNonce();
void UpdateCurrentProtocolVersion(ProtocolVersion version);
std::atomic_bool passthroughMode_{false};
std::mutex keyGenMutex_;
std::unique_ptr<IKeyRatchet> keyRatchet_;
std::shared_ptr<ICryptor> cryptor_;
KeyGeneration currentKeyGeneration_{0};
TruncatedSyncNonce truncatedNonce_{0};
std::mutex frameProcessorsMutex_;
std::vector<std::unique_ptr<OutboundFrameProcessor>> frameProcessors_;
using SsrcCodecPair = std::pair<uint32_t, Codec>;
std::vector<SsrcCodecPair> ssrcCodecPairs_;
using TimePoint = std::chrono::time_point<std::chrono::steady_clock>;
TimePoint lastStatsTime_{TimePoint::min()};
std::array<EncryptorStats, 2> stats_;
ProtocolVersionChangedCallback protocolVersionChangedCallback_;
ProtocolVersion currentProtocolVersion_{MaxSupportedProtocolVersion()};
};
} // namespace dave
} // namespace discord
================================================
FILE: cpp/src/frame_processors.cpp
================================================
#include "frame_processors.h"
#include <cassert>
#include <cstring>
#include <limits>
#include <optional>
#include <dave/array_view.h>
#include <dave/logger.h>
#include "codec_utils.h"
#include "utils/leb128.h"
#if defined(_MSC_VER)
#include <intrin.h>
#endif
namespace discord {
namespace dave {
std::pair<bool, size_t> OverflowAdd(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);
#else
bool didOverflow = __builtin_add_overflow(a, b, &res);
#endif
return {didOverflow, res};
}
uint8_t UnencryptedRangesSize(const Ranges& unencryptedRanges)
{
size_t size = 0;
for (const auto& range : unencryptedRanges) {
size += Leb128Size(range.offset);
size += Leb128Size(range.size);
}
assert(size <= std::numeric_limits<uint8_t>::max() &&
"Unencrypted ranges size exceeds 255 bytes");
return static_cast<uint8_t>(size);
}
uint8_t SerializeUnencryptedRanges(const Ranges& unencryptedRanges,
uint8_t* buffer,
size_t bufferSize)
{
auto writeAt = buffer;
auto end = buffer + bufferSize;
for (const auto& range : unencryptedRanges) {
auto rangeSize = Leb128Size(range.offset) + Leb128Size(range.size);
if (rangeSize > static_cast<size_t>(end - writeAt)) {
assert(false && "Buffer is too small to serialize unencrypted ranges");
break;
}
writeAt += WriteLeb128(range.offset, writeAt);
writeAt += WriteLeb128(range.size, writeAt);
}
assert(writeAt >= buffer);
return static_cast<uint8_t>(writeAt - buffer);
}
uint8_t DeserializeUnencryptedRanges(const uint8_t*& readAt,
const uint8_t bufferSize,
Ranges& unencryptedRanges)
{
auto start = readAt;
auto end = readAt + bufferSize;
while (readAt < end) {
size_t offset = ReadLeb128(readAt, end);
if (readAt == nullptr) {
break;
}
size_t size = ReadLeb128(readAt, end);
if (readAt == nullptr) {
break;
}
unencryptedRanges.push_back({offset, size});
}
if (readAt != end) {
DISCORD_LOG(LS_WARNING) << "Failed to deserialize unencrypted ranges";
unencryptedRanges.clear();
readAt = nullptr;
return 0;
}
return static_cast<uint8_t>(readAt - start);
}
bool ValidateUnencryptedRanges(const Ranges& unencryptedRanges, size_t frameSize)
{
if (unencryptedRanges.empty()) {
return true;
}
// validate that the ranges are in order and don't overlap
for (auto i = 0u; i < unencryptedRanges.size(); ++i) {
auto current = unencryptedRanges[i];
// The current range should not overflow into the next range
// or if it is the last range, the end of the frame
auto maxEnd =
i + 1 < unencryptedRanges.size() ? unencryptedRanges[i + 1].offset : frameSize;
auto [didOverflow, currentEnd] = OverflowAdd(current.offset, current.size);
if (didOverflow || currentEnd > maxEnd) {
DISCORD_LOG(LS_WARNING)
<< "Unencrypted range may overlap or be out of order: current offset: "
<< current.offset << ", current size: " << current.size << ", maximum end: " << maxEnd
<< ", frame size: " << frameSize;
return false;
}
}
return true;
}
size_t Reconstruct(Ranges ranges,
const std::vector<uint8_t>& rangeBytes,
const std::vector<uint8_t>& otherBytes,
const ArrayView<uint8_t>& output)
{
size_t frameIndex = 0;
size_t rangeBytesIndex = 0;
size_t otherBytesIndex = 0;
const auto CopyRangeBytes = [&](size_t size) {
assert(rangeBytesIndex + size <= rangeBytes.size());
assert(frameIndex + size <= output.size());
if ((rangeBytes.size() - rangeBytesIndex < size) || (output.size() - frameIndex < size)) {
return;
}
memcpy(output.data() + frameIndex, rangeBytes.data() + rangeBytesIndex, size);
rangeBytesIndex += size;
frameIndex += size;
};
const auto CopyOtherBytes = [&](size_t size) {
assert(otherBytesIndex + size <= otherBytes.size());
assert(frameIndex + size <= output.size());
if ((otherBytes.size() - otherBytesIndex < size) || (output.size() - frameIndex < size)) {
return;
}
memcpy(output.data() + frameIndex, otherBytes.data() + otherBytesIndex, size);
otherBytesIndex += size;
frameIndex += size;
};
for (const auto& range : ranges) {
if (range.offset > frameIndex) {
CopyOtherBytes(range.offset - frameIndex);
}
CopyRangeBytes(range.size);
}
if (otherBytesIndex < otherBytes.size()) {
CopyOtherBytes(otherBytes.size() - otherBytesIndex);
}
assert(rangeBytesIndex == rangeBytes.size());
assert(otherBytesIndex == otherBytes.size());
assert(frameIndex <= output.size());
return frameIndex;
}
void InboundFrameProcessor::Clear()
{
isEncrypted_ = false;
originalSize_ = 0;
truncatedNonce_ = std::numeric_limits<TruncatedSyncNonce>::max();
unencryptedRanges_.clear();
authenticated_.clear();
ciphertext_.clear();
plaintext_.clear();
}
void InboundFrameProcessor::ParseFrame(ArrayView<const uint8_t> frame)
{
Clear();
constexpr auto MinSupplementalBytesSize =
kAesGcm128TruncatedTagBytes + sizeof(SupplementalBytesSize) + sizeof(MagicMarker);
if (frame.size() < MinSupplementalBytesSize) {
DISCORD_LOG(LS_WARNING) << "Encrypted frame is too small to contain min supplemental bytes";
return;
}
// Check the frame ends with the magic marker
auto magicMarkerBuffer = frame.end() - sizeof(MagicMarker);
if (memcmp(magicMarkerBuffer, &kMarkerBytes, sizeof(MagicMarker)) != 0) {
return;
}
// Read the supplemental bytes size
SupplementalBytesSize supplementalBytesSize;
auto supplementalBytesSizeBuffer = magicMarkerBuffer - sizeof(SupplementalBytesSize);
assert(frame.begin() <= supplementalBytesSizeBuffer &&
supplementalBytesSizeBuffer <= frame.end());
memcpy(&supplementalBytesSize, supplementalBytesSizeBuffer, sizeof(SupplementalBytesSize));
// Check the frame is large enough to contain the supplemental bytes
if (frame.size() < supplementalBytesSize) {
DISCORD_LOG(LS_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 (supplementalBytesSize < MinSupplementalBytesSize) {
DISCORD_LOG(LS_WARNING)
<< "Supplemental bytes size is too small to contain supplemental bytes";
return;
}
auto supplementalBytesBuffer = frame.end() - supplementalBytesSize;
assert(frame.begin() <= supplementalBytesBuffer && supplementalBytesBuffer <= frame.end());
// Read the tag
tag_ = MakeArrayView(supplementalBytesBuffer, kAesGcm128TruncatedTagBytes);
// Read the nonce
auto nonceBuffer = supplementalBytesBuffer + kAesGcm128TruncatedTagBytes;
assert(frame.begin() <= nonceBuffer && nonceBuffer <= frame.end());
auto readAt = nonceBuffer;
auto end = supplementalBytesSizeBuffer;
truncatedNonce_ = static_cast<uint32_t>(ReadLeb128(readAt, end));
if (readAt == nullptr) {
DISCORD_LOG(LS_WARNING) << "Failed to read truncated nonce";
return;
}
// Read the unencrypted ranges
assert(nonceBuffer <= readAt && readAt <= end &&
end - readAt <= std::numeric_limits<uint8_t>::max());
auto unencryptedRangesSize = static_cast<uint8_t>(end - readAt);
DeserializeUnencryptedRanges(readAt, unencryptedRangesSize, unencryptedRanges_);
if (readAt == nullptr) {
DISCORD_LOG(LS_WARNING) << "Failed to read unencrypted ranges";
return;
}
if (!ValidateUnencryptedRanges(unencryptedRanges_, frame.size())) {
DISCORD_LOG(LS_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());
originalSize_ = frame.size();
// Split the frame into authenticated and ciphertext bytes
size_t frameIndex = 0;
for (const auto& range : unencryptedRanges_) {
auto encryptedBytes = range.offset - frameIndex;
if (encryptedBytes > 0) {
assert(frameIndex + encryptedBytes <= frame.size());
AddCiphertextBytes(frame.data() + frameIndex, encryptedBytes);
}
assert(range.offset + range.size <= frame.size());
AddAuthenticatedBytes(frame.data() + range.offset, range.size);
frameIndex = range.offset + range.size;
}
auto actualFrameSize = frame.size() - supplementalBytesSize;
if (frameIndex < actualFrameSize) {
AddCiphertextBytes(frame.data() + frameIndex, actualFrameSize - frameIndex);
}
// 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
isEncrypted_ = true;
}
size_t InboundFrameProcessor::ReconstructFrame(ArrayView<uint8_t> frame) const
{
if (!isEncrypted_) {
DISCORD_LOG(LS_WARNING) << "Cannot reconstruct an invalid encrypted frame";
return 0;
}
if (authenticated_.size() + plaintext_.size() > frame.size()) {
DISCORD_LOG(LS_WARNING) << "Frame is too small to contain the decrypted frame";
return 0;
}
return Reconstruct(unencryptedRanges_, authenticated_, plaintext_, frame);
}
void InboundFrameProcessor::AddAuthenticatedBytes(const uint8_t* data, size_t size)
{
authenticated_.resize(authenticated_.size() + size);
memcpy(authenticated_.data() + authenticated_.size() - size, data, size);
}
void InboundFrameProcessor::AddCiphertextBytes(const uint8_t* data, size_t size)
{
ciphertext_.resize(ciphertext_.size() + size);
memcpy(ciphertext_.data() + ciphertext_.size() - size, data, size);
}
void OutboundFrameProcessor::Reset()
{
codec_ = Codec::Unknown;
frameIndex_ = 0;
unencryptedBytes_.clear();
encryptedBytes_.clear();
unencryptedRanges_.clear();
}
void OutboundFrameProcessor::ProcessFrame(ArrayView<const uint8_t> frame, Codec codec)
{
Reset();
codec_ = codec;
unencryptedBytes_.reserve(frame.size());
encryptedBytes_.reserve(frame.size());
bool success = false;
switch (codec) {
case Codec::Opus:
success = codec_utils::ProcessFrameOpus(*this, frame);
break;
case Codec::VP8:
success = codec_utils::ProcessFrameVp8(*this, frame);
break;
case Codec::VP9:
success = codec_utils::ProcessFrameVp9(*this, frame);
break;
case Codec::H264:
success = codec_utils::ProcessFrameH264(*this, frame);
break;
case Codec::H265:
success = codec_utils::ProcessFrameH265(*this, frame);
break;
case Codec::AV1:
success = codec_utils::ProcessFrameAv1(*this, frame);
break;
default:
assert(false && "Unsupported codec for frame encryption");
break;
}
if (!success) {
frameIndex_ = 0;
unencryptedBytes_.clear();
encryptedBytes_.clear();
unencryptedRanges_.clear();
AddEncryptedBytes(frame.data(), frame.size());
}
ciphertextBytes_.resize(encryptedBytes_.size());
}
size_t OutboundFrameProcessor::ReconstructFrame(ArrayView<uint8_t> frame)
{
if (unencryptedBytes_.size() + ciphertextBytes_.size() > frame.size()) {
DISCORD_LOG(LS_WARNING) << "Frame is too small to contain the encrypted frame";
return 0;
}
return Reconstruct(unencryptedRanges_, unencryptedBytes_, ciphertextBytes_, frame);
}
void OutboundFrameProcessor::AddUnencryptedBytes(const uint8_t* bytes, size_t size)
{
if (!unencryptedRanges_.empty() &&
unencryptedRanges_.back().offset + unencryptedRanges_.back().size == frameIndex_) {
// extend the last range
unencryptedRanges_.back().size += size;
}
else {
// add a new range (offset, size)
unencryptedRanges_.push_back({frameIndex_, size});
}
unencryptedBytes_.resize(unencryptedBytes_.size() + size);
memcpy(unencryptedBytes_.data() + unencryptedBytes_.size() - size, bytes, size);
frameIndex_ += size;
}
void OutboundFrameProcessor::AddEncryptedBytes(const uint8_t* bytes, size_t size)
{
encryptedBytes_.resize(encryptedBytes_.size() + size);
memcpy(encryptedBytes_.data() + encryptedBytes_.size() - size, bytes, size);
frameIndex_ += size;
}
} // namespace dave
} // namespace discord
================================================
FILE: cpp/src/frame_processors.h
================================================
#pragma once
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
#include <dave/array_view.h>
#include <dave/dave_interfaces.h>
#include "common.h"
namespace discord {
namespace dave {
struct Range {
size_t offset;
size_t size;
};
using Ranges = std::vector<Range>;
uint8_t UnencryptedRangesSize(const Ranges& unencryptedRanges);
uint8_t SerializeUnencryptedRanges(const Ranges& unencryptedRanges,
uint8_t* buffer,
size_t bufferSize);
uint8_t DeserializeUnencryptedRanges(const uint8_t*& buffer,
const uint8_t bufferSize,
Ranges& unencryptedRanges);
bool ValidateUnencryptedRanges(const Ranges& unencryptedRanges, size_t frameSize);
class InboundFrameProcessor {
public:
void ParseFrame(ArrayView<const uint8_t> frame);
size_t ReconstructFrame(ArrayView<uint8_t> frame) const;
bool IsEncrypted() const { return isEncrypted_; }
size_t Size() const { return originalSize_; }
void Clear();
ArrayView<const uint8_t> GetTag() const { return tag_; }
TruncatedSyncNonce GetTruncatedNonce() const { return truncatedNonce_; }
ArrayView<const uint8_t> GetAuthenticatedData() const
{
return MakeArrayView(authenticated_.data(), authenticated_.size());
}
ArrayView<const uint8_t> GetCiphertext() const
{
return MakeArrayView(ciphertext_.data(), ciphertext_.size());
}
ArrayView<uint8_t> GetPlaintext() { return MakeArrayView(plaintext_); }
private:
void AddAuthenticatedBytes(const uint8_t* data, size_t size);
void AddCiphertextBytes(const uint8_t* data, size_t size);
bool isEncrypted_{false};
size_t originalSize_{0};
ArrayView<const uint8_t> tag_;
TruncatedSyncNonce truncatedNonce_;
Ranges unencryptedRanges_;
std::vector<uint8_t> authenticated_;
std::vector<uint8_t> ciphertext_;
std::vector<uint8_t> plaintext_;
};
class OutboundFrameProcessor {
public:
void ProcessFrame(ArrayView<const uint8_t> frame, Codec codec);
size_t ReconstructFrame(ArrayView<uint8_t> frame);
Codec GetCodec() const { return codec_; }
const std::vector<uint8_t>& GetUnencryptedBytes() const { return unencryptedBytes_; }
const std::vector<uint8_t>& GetEncryptedBytes() const { return encryptedBytes_; }
std::vector<uint8_t>& GetCiphertextBytes() { return ciphertextBytes_; }
const Ranges& GetUnencryptedRanges() const { return unencryptedRanges_; }
void Reset();
void AddUnencryptedBytes(const uint8_t* bytes, size_t size);
void AddEncryptedBytes(const uint8_t* bytes, size_t size);
private:
Codec codec_{Codec::Unknown};
size_t frameIndex_{0};
std::vector<uint8_t> unencryptedBytes_;
std::vector<uint8_t> encryptedBytes_;
std::vector<uint8_t> ciphertextBytes_;
Ranges unencryptedRanges_;
};
} // namespace dave
} // namespace discord
================================================
FILE: cpp/src/key_ratchet.h
================================================
#pragma once
#include <memory>
#include "common.h"
namespace discord {
namespace dave {
} // namespace dave
} // namespace discord
================================================
FILE: cpp/src/logger.cpp
================================================
#include <dave/logger.h>
#include <atomic>
#include <cstring>
#include <iostream>
namespace discord {
namespace dave {
std::atomic<LogSink> gLogSink = nullptr;
void SetLogSink(LogSink sink)
{
gLogSink = sink;
}
LogStreamer::LogStreamer(LoggingSeverity severity, const char* file, int line)
: severity_(severity)
, file_(file)
, line_(line)
{
}
LogStreamer::~LogStreamer()
{
std::string logLine = stream_.str();
if (logLine.empty()) {
return;
}
auto sink = gLogSink.load();
if (sink) {
sink(severity_, file_, line_, logLine);
return;
}
switch (severity_) {
case LS_VERBOSE:
case LS_INFO:
case LS_WARNING:
case LS_ERROR: {
const char* file = file_;
if (auto separator = strrchr(file, '/')) {
file = separator + 1;
}
std::cout << "(" << file << ":" << line_ << ") " << logLine << std::endl;
break;
}
case LS_NONE:
break;
}
}
} // namespace dave
} // namespace discord
================================================
FILE: cpp/src/mls/detail/persisted_key_pair.h
================================================
#pragma once
#include <memory>
#include <string>
#include <mls/crypto.h>
#include "mls/persisted_key_pair.h"
namespace discord {
namespace dave {
namespace mls {
namespace detail {
std::shared_ptr<::mlspp::SignaturePrivateKey> GetNativePersistedKeyPair(KeyPairContextType ctx,
const std::string& keyID,
::mlspp::CipherSuite suite,
bool& supported);
std::shared_ptr<::mlspp::SignaturePrivateKey> GetGenericPersistedKeyPair(
KeyPairContextType ctx,
const std::string& keyID,
::mlspp::CipherSuite suite);
bool DeleteNativePersistedKeyPair(KeyPairContextType ctx, const std::string& keyID);
bool DeleteGenericPersistedKeyPair(KeyPairContextType ctx, const std::string& keyID);
} // namespace detail
} // namespace mls
} // namespace dave
} // namespace discord
================================================
FILE: cpp/src/mls/detail/persisted_key_pair_apple.cpp
================================================
#include "mls/detail/persisted_key_pair.h"
#include <cassert>
#include <filesystem>
#include <fstream>
#include <functional>
#include <mutex>
#include <sstream>
#include <string>
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
#include <unistd.h>
#include <bytes/bytes.h>
#include <dave/logger.h>
#include <mls/crypto.h>
#include "mls/parameters.h"
static const CFStringRef KeyServiceLabel = CFSTR("Discord Secure Frames Key");
static const std::string KeyLabelPrefix = "Discord Secure Frames Key: ";
static const std::string KeyTagPrefix = "discord-secure-frames-key-";
#ifdef KEYCHAIN_ACCESS_GROUP_ID_SYMBOL
extern CFStringRef KEYCHAIN_ACCESS_GROUP_ID_SYMBOL;
#endif
static void AddAccessGroup([[maybe_unused]] CFMutableDictionaryRef dict)
{
#ifdef KEYCHAIN_ACCESS_GROUP_ID_SYMBOL
CFDictionaryAddValue(dict, kSecAttrAccessGroup, KEYCHAIN_ACCESS_GROUP_ID_SYMBOL);
#elif defined(KEYCHAIN_ACCESS_GROUP_ID)
CFDictionaryAddValue(dict, kSecAttrAccessGroup, CFSTR(#KEYCHAIN_ACCESS_GROUP_ID));
#endif
}
template <class T = CFTypeRef>
struct ScopedCFTypeRef {
ScopedCFTypeRef() = default;
ScopedCFTypeRef(T ref)
: ref_(ref)
{
}
ScopedCFTypeRef(ScopedCFTypeRef& other)
: ref_(other.ref_)
{
if (ref_) {
CFRetain(ref_);
}
}
ScopedCFTypeRef(ScopedCFTypeRef&& other)
: ref_(std::exchange(other.ref_, nullptr))
{
}
~ScopedCFTypeRef() { release(); }
ScopedCFTypeRef& operator=(T ref)
{
release();
ref_ = ref;
return *this;
}
void release()
{
if (ref_) {
CFRelease(ref_);
}
ref_ = nullptr;
}
T& get() { return ref_; }
T* getPtr() { return &ref_; }
CFTypeRef* getGenericPtr() { return (CFTypeRef*)getPtr(); }
operator T&() { return get(); }
explicit operator bool() { return ref_ != nullptr; }
T ref_ = nullptr;
};
static std::string ConvertCFString(CFStringRef string)
{
if (const char* str = CFStringGetCStringPtr(string, kCFStringEncodingUTF8)) {
return str;
}
CFIndex len = CFStringGetLength(string);
std::string ret(CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8), 0);
CFStringGetBytes(string,
CFRangeMake(0, len),
kCFStringEncodingUTF8,
'?',
false,
(UInt8*)ret.data(),
ret.size(),
&len);
ret.resize(len);
return ret;
}
static std::string SecStatusToString(OSStatus status)
{
std::string ret = std::to_string(status);
if (__builtin_available(macOS 10.3, iOS 11.3, *)) {
ScopedCFTypeRef string = SecCopyErrorMessageString(status, NULL);
if (string) {
ret += " (";
ret += ConvertCFString(string);
ret += ")";
}
}
return ret;
}
static std::string ErrorToString(CFErrorRef error)
{
if (!error) {
return "(null)";
}
if (__builtin_available(macOS 10.3, iOS 11.3, *)) {
CFIndex status = CFErrorGetCode(error);
ScopedCFTypeRef string = CFErrorCopyFailureReason(error);
if (string) {
std::string ret = std::to_string(status);
ret += " (";
ret += ConvertCFString(string);
ret += ")";
return ret;
}
}
if (ScopedCFTypeRef string = CFErrorCopyDescription(error)) {
return ConvertCFString(string);
}
return "(unknown)";
}
namespace discord {
namespace dave {
namespace mls {
namespace detail {
std::shared_ptr<::mlspp::SignaturePrivateKey> GetNativePersistedKeyPair(
[[maybe_unused]] KeyPairContextType ctx,
const std::string& id,
::mlspp::CipherSuite suite,
bool& supported)
{
std::shared_ptr<::mlspp::SignaturePrivateKey> ret;
CFStringRef keyType = nullptr;
int keySize = 0;
std::function<bytes(CFDataRef)> convertKey;
ScopedCFTypeRef query = CFDictionaryCreateMutable(
NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
CFDictionaryAddValue(query, kSecUseAuthenticationUI, kSecUseAuthenticationUISkip);
AddAccessGroup(query);
auto suiteId = suite.cipher_suite();
switch (suiteId) {
case ::mlspp::CipherSuite::ID::P256_AES128GCM_SHA256_P256:
case ::mlspp::CipherSuite::ID::P384_AES256GCM_SHA384_P384:
case ::mlspp::CipherSuite::ID::P521_AES256GCM_SHA512_P521:
supported = true;
keyType = kSecAttrKeyTypeECSECPrimeRandom;
if (suiteId == ::mlspp::CipherSuite::ID::P521_AES256GCM_SHA512_P521) {
keySize = 521;
}
else if (suiteId == ::mlspp::CipherSuite::ID::P384_AES256GCM_SHA384_P384) {
keySize = 384;
}
else {
keySize = 256;
}
convertKey = [keySize](CFDataRef data) {
// https://developer.apple.com/documentation/security/1643698-seckeycopyexternalrepresentation
// Input has a 1-byte header (always 0x04, per ANSI X9.63), followed by 3
// keySize-bit left-padded byte-aligned big-endian integers: X, Y, and K.
// X and Y are the public key (represented as the coordinates);
// K is the private key.
bytes ret;
constexpr size_t HeaderSize = 1;
constexpr size_t ValueCount = 3;
constexpr size_t PublicValues = 2;
constexpr uint8_t HeaderByte = 0x04;
// Convert keySize from bits to bytes (rounding up)
CFIndex byteLen = (keySize + 7) / 8;
CFIndex len = CFDataGetLength(data);
if (len < 0 || (size_t)len < HeaderSize + ValueCount * byteLen) {
DISCORD_LOG(LS_ERROR)
<< "Exported key blob too small in GetPersistedKeyPair/convertKey: " << len;
return ret;
}
const uint8_t* ptr = CFDataGetBytePtr(data);
if (ptr[0] != HeaderByte) {
DISCORD_LOG(LS_ERROR)
<< "Exported key blob has unexpected format in GetPersistedKeyPair/convertKey: "
<< ptr[0];
return ret;
}
// Skip header, X, and Y, and extract K.
ptr += HeaderSize + PublicValues * byteLen;
ret.as_vec().assign(ptr, ptr + byteLen);
return ret;
};
break;
default:
// Other suites will need to store keys as generic data items
return nullptr;
}
assert(keyType && keySize && convertKey);
ScopedCFTypeRef<CFNumberRef> sizeRef = CFNumberCreate(NULL, kCFNumberIntType, &keySize);
std::string labelString = KeyLabelPrefix + id;
std::string tagString = KeyTagPrefix + id;
ScopedCFTypeRef labelStringRef =
CFStringCreateWithCString(NULL, labelString.c_str(), kCFStringEncodingUTF8);
ScopedCFTypeRef tagDataRef =
CFDataCreate(NULL, (const UInt8*)tagString.c_str(), tagString.size());
CFDictionaryAddValue(query, kSecClass, kSecClassKey);
CFDictionaryAddValue(query, kSecAttrKeyType, keyType);
CFDictionaryAddValue(query, kSecAttrApplicationTag, tagDataRef);
CFDictionaryAddValue(query, kSecAttrCanSign, kCFBooleanTrue);
ScopedCFTypeRef<CFErrorRef> cfError;
ScopedCFTypeRef<SecKeyRef> key;
// If we get errSecMissingEntitlement, try again with the file-based keychain
constexpr int AttemptCount = 2;
for (int attempt = 0; attempt < AttemptCount && !key; attempt++) {
cfError.release();
CFBooleanRef useDataProtection = attempt == 0 ? kCFBooleanTrue : kCFBooleanFalse;
if (__builtin_available(macOS 10.15, *)) {
CFDictionarySetValue(query, kSecUseDataProtectionKeychain, useDataProtection);
}
else if (attempt == 1) {
return nullptr;
}
OSStatus status = SecItemCopyMatching(query, key.getGenericPtr());
if (status == errSecSuccess) {
ScopedCFTypeRef updateQuery = CFDictionaryCreateMutableCopy(NULL, 0, query);
ScopedCFTypeRef updateAttrs = CFDictionaryCreateMutable(
NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionaryRemoveValue(updateQuery, kSecReturnRef);
CFDictionaryAddValue(
updateAttrs, kSecAttrAccessible, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly);
// Best effort
OSStatus updateStatus = SecItemUpdate(query, updateAttrs);
DISCORD_LOG(LS_INFO) << "Attempted to update permissions on existing key: "
<< SecStatusToString(updateStatus);
}
if (status == errSecItemNotFound) {
DISCORD_LOG(LS_INFO) << "Item not found in GetPersistedKeyPair; generating new: "
<< SecStatusToString(status);
ScopedCFTypeRef params = CFDictionaryCreateMutable(
NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
AddAccessGroup(params);
CFDictionaryAddValue(params, kSecAttrKeyType, keyType);
CFDictionaryAddValue(params, kSecAttrKeySizeInBits, sizeRef);
CFDictionaryAddValue(params, kSecAttrCanEncrypt, kCFBooleanFalse);
CFDictionaryAddValue(params, kSecAttrCanDecrypt, kCFBooleanFalse);
CFDictionaryAddValue(params, kSecAttrCanWrap, kCFBooleanFalse);
CFDictionaryAddValue(params, kSecAttrCanUnwrap, kCFBooleanFalse);
CFDictionaryAddValue(
params, kSecAttrAccessible, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly);
if (__builtin_available(macOS 10.15, *)) {
CFDictionaryAddValue(params, kSecUseDataProtectionKeychain, useDataProtection);
}
ScopedCFTypeRef privParams = CFDictionaryCreateMutable(
NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(privParams, kSecAttrIsPermanent, kCFBooleanTrue);
CFDictionaryAddValue(privParams, kSecAttrLabel, labelStringRef);
CFDictionaryAddValue(privParams, kSecAttrApplicationTag, tagDataRef);
CFDictionaryAddValue(params, kSecPrivateKeyAttrs, privParams);
key = SecKeyCreateRandomKey(params, cfError.getPtr());
if (!key || cfError) {
DISCORD_LOG(LS_WARNING)
<< "Failed to create key in GetPersistedKeyPair: " << ErrorToString(cfError);
if (!cfError || CFErrorGetCode(cfError) != errSecMissingEntitlement) {
return nullptr;
}
key.release();
}
}
else if (status != 0 || !key) {
DISCORD_LOG(LS_WARNING)
<< "Item not found GetPersistedKeyPair: " << SecStatusToString(status);
if (status != errSecMissingEntitlement) {
return nullptr;
}
}
}
if (!key) {
return nullptr;
}
ScopedCFTypeRef data = SecKeyCopyExternalRepresentation(key, cfError.getPtr());
if (!data) {
DISCORD_LOG(LS_ERROR) << "Failed to export key in GetPersistedKeyPair: "
<< ErrorToString(cfError);
return nullptr;
}
bytes converted = convertKey(data);
if (converted.empty()) {
DISCORD_LOG(LS_ERROR) << "Failed to convert key in GetPersistedKeyPair";
return nullptr;
}
return std::make_shared<::mlspp::SignaturePrivateKey>(
::mlspp::SignaturePrivateKey::parse(suite, converted));
}
std::shared_ptr<::mlspp::SignaturePrivateKey> GetGenericPersistedKeyPair(
[[maybe_unused]] KeyPairContextType ctx,
const std::string& id,
::mlspp::CipherSuite suite)
{
::mlspp::SignaturePrivateKey ret;
ScopedCFTypeRef query = CFDictionaryCreateMutable(
NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
ScopedCFTypeRef accountString =
CFStringCreateWithCString(NULL, id.c_str(), kCFStringEncodingUTF8);
CFDictionaryAddValue(query, kSecReturnData, kCFBooleanTrue);
CFDictionaryAddValue(query, kSecUseAuthenticationUI, kSecUseAuthenticationUISkip);
CFDictionaryAddValue(query, kSecAttrService, KeyServiceLabel);
CFDictionaryAddValue(query, kSecAttrAccount, accountString);
CFDictionaryAddValue(query, kSecClass, kSecClassGenericPassword);
AddAccessGroup(query);
// If we get errSecMissingEntitlement, try again with the file-based keychain
constexpr int AttemptCount = 2;
for (int attempt = 0; attempt < AttemptCount && ret.public_key.data.empty(); attempt++) {
if (__builtin_available(macOS 10.15, *)) {
CFDictionarySetValue(query,
kSecUseDataProtectionKeychain,
attempt == 0 ? kCFBooleanTrue : kCFBooleanFalse);
}
else if (attempt == 1) {
return nullptr;
}
ScopedCFTypeRef<CFDataRef> result;
OSStatus status = SecItemCopyMatching(query, result.getGenericPtr());
std::string curstr;
if (status == 0 && result) {
curstr.assign((char*)CFDataGetBytePtr(result), CFDataGetLength(result));
try {
ret = ::mlspp::SignaturePrivateKey::from_jwk(suite, curstr);
}
catch (std::exception& ex) {
DISCORD_LOG(LS_WARNING)
<< "Failed to parse key in GetPersistedKeyPair: " << ex.what();
return nullptr;
}
}
else if (status == errSecItemNotFound) {
DISCORD_LOG(LS_INFO) << "Did not receive item in GetPersistedKeyPair; generating new: "
<< SecStatusToString(status);
ret = ::mlspp::SignaturePrivateKey::generate(suite);
std::string newstr = ret.to_jwk(suite);
ScopedCFTypeRef data =
CFDataCreate(NULL, (const UInt8*)newstr.c_str(), newstr.length());
CFDictionaryRemoveValue(query, kSecReturnData);
CFDictionaryAddValue(query, kSecValueData, data);
status = SecItemAdd(query, nullptr);
if (status) {
DISCORD_LOG(LS_WARNING) << "Failed to create keychain item in GetPersistedKeyPair: "
<< SecStatusToString(status);
if (status != errSecMissingEntitlement) {
return nullptr;
}
ret = ::mlspp::SignaturePrivateKey();
}
}
else {
DISCORD_LOG(LS_WARNING)
<< "Failed to retrieve item in GetPersistedKeyPair: " << SecStatusToString(status);
if (status != errSecMissingEntitlement) {
return nullptr;
}
}
}
if (!ret.public_key.data.empty()) {
return std::make_shared<::mlspp::SignaturePrivateKey>(std::move(ret));
}
else {
return nullptr;
}
}
static bool DeleteWithQuery(CFMutableDictionaryRef query)
{
#if !TARGET_OS_IPHONE
if (__builtin_available(macOS 10.15, *)) {
CFDictionarySetValue(query, kSecUseDataProtectionKeychain, kCFBooleanTrue);
}
#endif
auto ret = SecItemDelete(query);
#if !TARGET_OS_IPHONE
if (__builtin_available(macOS 10.15, *)) {
if (ret == errSecMissingEntitlement) {
CFDictionarySetValue(query, kSecUseDataProtectionKeychain, kCFBooleanFalse);
ret = SecItemDelete(query);
}
}
#endif
return ret == errSecSuccess;
}
bool DeleteNativePersistedKeyPair([[maybe_unused]] KeyPairContextType ctx, const std::string& id)
{
std::string tagString = KeyTagPrefix + id;
ScopedCFTypeRef tagDataRef =
CFDataCreate(NULL, (const UInt8*)tagString.c_str(), tagString.size());
ScopedCFTypeRef query = CFDictionaryCreateMutable(
NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(query, kSecClass, kSecClassKey);
CFDictionaryAddValue(query, kSecAttrApplicationTag, tagDataRef);
AddAccessGroup(query);
return DeleteWithQuery(query);
}
bool DeleteGenericPersistedKeyPair([[maybe_unused]] KeyPairContextType ctx, const std::string& id)
{
ScopedCFTypeRef accountString =
CFStringCreateWithCString(NULL, id.c_str(), kCFStringEncodingUTF8);
ScopedCFTypeRef query = CFDictionaryCreateMutable(
NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(query, kSecAttrService, KeyServiceLabel);
CFDictionaryAddValue(query, kSecAttrAccount, accountString);
CFDictionaryAddValue(query, kSecClass, kSecClassGenericPassword);
AddAccessGroup(query);
return DeleteWithQuery(query);
}
} // namespace detail
} // namespace mls
} // namespace dave
} // namespace discord
================================================
FILE: cpp/src/mls/detail/persisted_key_pair_generic.cpp
================================================
#include "mls/detail/persisted_key_pair.h"
#include <cassert>
#include <filesystem>
#include <fstream>
#include <functional>
#include <mutex>
#include <sstream>
#include <string>
#ifdef _WIN32
#include <io.h>
#else
#include <sys/stat.h>
#include <unistd.h>
#endif
#include <fcntl.h>
#include <bytes/bytes.h>
#include <dave/logger.h>
#include <mls/crypto.h>
#include "mls/parameters.h"
static const std::string_view KeyStorageDir = "Discord Key Storage";
static std::filesystem::path GetKeyStorageDirectory()
{
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 // __ANDROID__
#if defined(_WIN32)
if (const wchar_t* appdata = _wgetenv(L"LOCALAPPDATA")) {
dir = std::filesystem::path(appdata);
}
#else // _WIN32
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 // !_WIN32
else {
return dir;
}
#endif // !__ANDROID__
return dir / KeyStorageDir;
}
namespace discord {
namespace dave {
namespace mls {
namespace detail {
std::shared_ptr<::mlspp::SignaturePrivateKey> GetGenericPersistedKeyPair(
[[maybe_unused]] KeyPairContextType ctx,
const std::string& id,
::mlspp::CipherSuite suite)
{
::mlsp
gitextract_k2hmfbp_/
├── .github/
│ ├── actions/
│ │ └── prepare-build/
│ │ └── action.yaml
│ └── workflows/
│ └── main.yaml
├── .gitignore
├── .gitmodules
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── cpp/
│ ├── .clang-format
│ ├── .gitignore
│ ├── CMakeLists.txt
│ ├── Makefile
│ ├── README.md
│ ├── afl-driver/
│ │ └── src/
│ │ └── main.cpp
│ ├── includes/
│ │ └── dave/
│ │ ├── array_view.h
│ │ ├── dave.h
│ │ ├── dave_interfaces.h
│ │ ├── logger.h
│ │ └── version.h
│ ├── src/
│ │ ├── bindings_capi.cpp
│ │ ├── bindings_wasm.cpp
│ │ ├── boringssl_cryptor.cpp
│ │ ├── boringssl_cryptor.h
│ │ ├── codec_utils.cpp
│ │ ├── codec_utils.h
│ │ ├── common.h
│ │ ├── cryptor.cpp
│ │ ├── cryptor.h
│ │ ├── cryptor_manager.cpp
│ │ ├── cryptor_manager.h
│ │ ├── decryptor.cpp
│ │ ├── decryptor.h
│ │ ├── encryptor.cpp
│ │ ├── encryptor.h
│ │ ├── frame_processors.cpp
│ │ ├── frame_processors.h
│ │ ├── key_ratchet.h
│ │ ├── logger.cpp
│ │ ├── mls/
│ │ │ ├── detail/
│ │ │ │ ├── persisted_key_pair.h
│ │ │ │ ├── persisted_key_pair_apple.cpp
│ │ │ │ ├── persisted_key_pair_generic.cpp
│ │ │ │ ├── persisted_key_pair_null.cpp
│ │ │ │ └── persisted_key_pair_win.cpp
│ │ │ ├── parameters.cpp
│ │ │ ├── parameters.h
│ │ │ ├── persisted_key_pair.cpp
│ │ │ ├── persisted_key_pair.h
│ │ │ ├── persisted_key_pair_null.cpp
│ │ │ ├── session.cpp
│ │ │ ├── session.h
│ │ │ ├── user_credential.cpp
│ │ │ ├── user_credential.h
│ │ │ ├── util.cpp
│ │ │ └── util.h
│ │ ├── mls_key_ratchet.cpp
│ │ ├── mls_key_ratchet.h
│ │ ├── openssl_cryptor.cpp
│ │ ├── openssl_cryptor.h
│ │ ├── utils/
│ │ │ ├── clock.h
│ │ │ ├── leb128.cpp
│ │ │ ├── leb128.h
│ │ │ └── scope_exit.h
│ │ └── version.cpp
│ ├── test/
│ │ ├── CMakeLists.txt
│ │ ├── capi/
│ │ │ ├── CMakeLists.txt
│ │ │ ├── basic_tests.c
│ │ │ ├── external_sender_wrapper.cpp
│ │ │ ├── external_sender_wrapper.h
│ │ │ ├── test_helpers.c
│ │ │ └── test_helpers.h
│ │ ├── codec_utils_tests.cpp
│ │ ├── cryptor_manager_tests.cpp
│ │ ├── cryptor_tests.cpp
│ │ ├── dave_test.cpp
│ │ ├── dave_test.h
│ │ ├── external_sender.cpp
│ │ ├── external_sender.h
│ │ ├── static_key_ratchet.cpp
│ │ ├── static_key_ratchet.h
│ │ └── xssl_cryptor_tests.cpp
│ └── vcpkg-alts/
│ ├── boringssl/
│ │ ├── overlay-ports/
│ │ │ └── mlspp/
│ │ │ ├── portfile.cmake
│ │ │ └── vcpkg.json
│ │ └── vcpkg.json
│ ├── openssl_1.1/
│ │ ├── overlay-ports/
│ │ │ └── mlspp/
│ │ │ ├── portfile.cmake
│ │ │ └── vcpkg.json
│ │ └── vcpkg.json
│ ├── openssl_3/
│ │ ├── overlay-ports/
│ │ │ └── mlspp/
│ │ │ ├── portfile.cmake
│ │ │ └── vcpkg.json
│ │ └── vcpkg.json
│ └── wasm/
│ ├── overlay-ports/
│ │ └── mlspp/
│ │ ├── portfile.cmake
│ │ └── vcpkg.json
│ └── vcpkg.json
├── js/
│ ├── .gitignore
│ ├── .npmrc
│ ├── README.md
│ ├── __tests__/
│ │ ├── DisplayableCode-test.ts
│ │ ├── KeyFingerprint-test.ts
│ │ ├── KeySerialization-test.ts
│ │ └── PairwiseFingerprint-test.ts
│ ├── jest-setup.js
│ ├── jest.config.js
│ ├── package.json
│ ├── src/
│ │ ├── DisplayableCode.ts
│ │ ├── KeyFingerprint.ts
│ │ ├── KeySerialization.ts
│ │ ├── PairwiseFingerprint.ts
│ │ ├── index.ts
│ │ └── wasm.ts
│ ├── tsconfig.json
│ └── wasm/
│ └── .gitignore
└── samples/
└── typescript/
├── DaveSessionManager.ts
└── README.md
SYMBOL INDEX (411 symbols across 69 files)
FILE: cpp/afl-driver/src/main.cpp
function LLVMFuzzerTestOneInput (line 15) | int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
FILE: cpp/includes/dave/array_view.h
function namespace (line 7) | namespace discord {
FILE: cpp/includes/dave/dave.h
type DAVECodec (line 50) | typedef enum {
type DAVEMediaType (line 63) | typedef enum {
type DAVEEncryptorResultCode (line 71) | typedef enum {
type DAVEDecryptorResultCode (line 82) | typedef enum {
type DAVELoggingSeverity (line 93) | typedef enum {
type DAVEEncryptorStats (line 138) | typedef struct DAVEEncryptorStats {
type DAVEDecryptorStats (line 151) | typedef struct DAVEDecryptorStats {
FILE: cpp/includes/dave/dave_interfaces.h
function namespace (line 17) | namespace mlspp {
type MediaType (line 36) | enum MediaType : uint8_t { Audio, Video }
type Codec (line 37) | enum Codec : uint8_t { Unknown, Opus, VP8, VP9, H264, H265, AV1 }
type LoggingSeverity (line 38) | enum LoggingSeverity {
type failed_t (line 47) | struct failed_t {}
type ignored_t (line 50) | struct ignored_t {}
function class (line 62) | class IKeyRatchet {
function namespace (line 69) | namespace mls {
FILE: cpp/includes/dave/logger.h
function namespace (line 11) | namespace discord {
FILE: cpp/includes/dave/version.h
function namespace (line 7) | namespace discord {
FILE: cpp/src/bindings_capi.cpp
function CopyKeyRatchet (line 28) | std::unique_ptr<discord::dave::MlsKeyRatchet> CopyKeyRatchet(DAVEKeyRatc...
function CopyVectorToOutputBuffer (line 42) | void CopyVectorToOutputBuffer(std::vector<uint8_t> const& vector, uint8_...
function GetRosterMemberIds (line 59) | void GetRosterMemberIds(const discord::dave::RosterMap& rosterMap,
function GetRosterMemberSignature (line 71) | void GetRosterMemberSignature(const discord::dave::RosterMap& rosterMap,
function daveMaxSupportedProtocolVersion (line 79) | uint16_t daveMaxSupportedProtocolVersion(void)
function daveFree (line 84) | void daveFree(void* ptr)
function DAVESessionHandle (line 89) | DAVESessionHandle daveSessionCreate(void* context,
function daveSessionDestroy (line 108) | void daveSessionDestroy(DAVESessionHandle sessionHandle)
function daveSessionInit (line 114) | void daveSessionInit(DAVESessionHandle sessionHandle,
function daveSessionReset (line 126) | void daveSessionReset(DAVESessionHandle sessionHandle)
function daveSessionSetProtocolVersion (line 133) | void daveSessionSetProtocolVersion(DAVESessionHandle sessionHandle, uint...
function daveSessionGetProtocolVersion (line 140) | uint16_t daveSessionGetProtocolVersion(DAVESessionHandle sessionHandle)
function daveSessionGetLastEpochAuthenticator (line 147) | void daveSessionGetLastEpochAuthenticator(DAVESessionHandle sessionHandle,
function daveSessionSetExternalSender (line 157) | void daveSessionSetExternalSender(DAVESessionHandle sessionHandle,
function daveSessionProcessProposals (line 167) | void daveSessionProcessProposals(DAVESessionHandle sessionHandle,
function DAVECommitResultHandle (line 188) | DAVECommitResultHandle daveSessionProcessCommit(DAVESessionHandle sessio...
function DAVEWelcomeResultHandle (line 202) | DAVEWelcomeResultHandle daveSessionProcessWelcome(DAVESessionHandle sess...
function daveSessionGetMarshalledKeyPackage (line 223) | void daveSessionGetMarshalledKeyPackage(DAVESessionHandle sessionHandle,
function DAVEKeyRatchetHandle (line 233) | DAVEKeyRatchetHandle daveSessionGetKeyRatchet(DAVESessionHandle sessionH...
function daveSessionGetPairwiseFingerprint (line 242) | void daveSessionGetPairwiseFingerprint(DAVESessionHandle sessionHandle,
function daveKeyRatchetDestroy (line 257) | void daveKeyRatchetDestroy(DAVEKeyRatchetHandle keyRatchet)
function daveCommitResultIsFailed (line 262) | bool daveCommitResultIsFailed(DAVECommitResultHandle commitResultHandle)
function daveCommitResultIsIgnored (line 269) | bool daveCommitResultIsIgnored(DAVECommitResultHandle commitResultHandle)
function daveCommitResultGetRosterMemberIds (line 276) | void daveCommitResultGetRosterMemberIds(DAVECommitResultHandle commitRes...
function daveCommitResultGetRosterMemberSignature (line 291) | void daveCommitResultGetRosterMemberSignature(DAVECommitResultHandle com...
function daveCommitResultDestroy (line 307) | void daveCommitResultDestroy(DAVECommitResultHandle commitResultHandle)
function daveWelcomeResultGetRosterMemberIds (line 313) | void daveWelcomeResultGetRosterMemberIds(DAVEWelcomeResultHandle welcome...
function daveWelcomeResultGetRosterMemberSignature (line 322) | void daveWelcomeResultGetRosterMemberSignature(DAVEWelcomeResultHandle w...
function daveWelcomeResultDestroy (line 332) | void daveWelcomeResultDestroy(DAVEWelcomeResultHandle welcomeResultHandle)
function DAVEEncryptorHandle (line 338) | DAVEEncryptorHandle daveEncryptorCreate()
function daveEncryptorDestroy (line 344) | void daveEncryptorDestroy(DAVEEncryptorHandle encryptorHandle)
function daveEncryptorSetKeyRatchet (line 350) | void daveEncryptorSetKeyRatchet(DAVEEncryptorHandle encryptorHandle,
function daveEncryptorSetPassthroughMode (line 359) | void daveEncryptorSetPassthroughMode(DAVEEncryptorHandle encryptorHandle...
function daveEncryptorAssignSsrcToCodec (line 366) | void daveEncryptorAssignSsrcToCodec(DAVEEncryptorHandle encryptorHandle,
function daveEncryptorGetProtocolVersion (line 375) | uint16_t daveEncryptorGetProtocolVersion(DAVEEncryptorHandle encryptorHa...
function daveEncryptorGetMaxCiphertextByteSize (line 382) | size_t daveEncryptorGetMaxCiphertextByteSize(DAVEEncryptorHandle encrypt...
function daveEncryptorHasKeyRatchet (line 392) | bool daveEncryptorHasKeyRatchet(DAVEEncryptorHandle encryptorHandle)
function daveEncryptorIsPassthroughMode (line 399) | bool daveEncryptorIsPassthroughMode(DAVEEncryptorHandle encryptorHandle)
function DAVEEncryptorResultCode (line 406) | DAVEEncryptorResultCode daveEncryptorEncrypt(DAVEEncryptorHandle encrypt...
function daveEncryptorSetProtocolVersionChangedCallback (line 427) | void daveEncryptorSetProtocolVersionChangedCallback(
function daveEncryptorGetStats (line 437) | void daveEncryptorGetStats(DAVEEncryptorHandle encryptorHandle,
function DAVEDecryptorHandle (line 446) | DAVEDecryptorHandle daveDecryptorCreate()
function daveDecryptorDestroy (line 452) | void daveDecryptorDestroy(DAVEDecryptorHandle decryptorHandle)
function daveDecryptorTransitionToKeyRatchet (line 458) | void daveDecryptorTransitionToKeyRatchet(DAVEDecryptorHandle decryptorHa...
function daveDecryptorTransitionToPassthroughMode (line 467) | void daveDecryptorTransitionToPassthroughMode(DAVEDecryptorHandle decryp...
function DAVEDecryptorResultCode (line 475) | DAVEDecryptorResultCode daveDecryptorDecrypt(DAVEDecryptorHandle decrypt...
function daveDecryptorGetMaxPlaintextByteSize (line 494) | size_t daveDecryptorGetMaxPlaintextByteSize(DAVEDecryptorHandle decrypto...
function daveDecryptorGetStats (line 504) | void daveDecryptorGetStats(DAVEDecryptorHandle decryptorHandle,
function LogSinkCallback (line 515) | void LogSinkCallback(discord::dave::LoggingSeverity severity,
function daveSetLogSinkCallback (line 526) | void daveSetLogSinkCallback(DAVELogSinkCallback callback)
FILE: cpp/src/bindings_wasm.cpp
type discord (line 25) | namespace discord {
type dave (line 26) | namespace dave {
function val (line 28) | val ToOwnedTypedArray(const uint8_t* data, size_t size)
function val (line 37) | val ToOwnedTypedArray(const ::mlspp::bytes_ns::bytes& data)
function val (line 42) | val ToOwnedTypedArray(const std::vector<uint8_t>& data)
function val (line 47) | val MlsKeyRatchetToJS(std::unique_ptr<MlsKeyRatchet> keyRatchet)
function MlsKeyRatchetFromJS (line 62) | std::unique_ptr<MlsKeyRatchet> MlsKeyRatchetFromJS(val keyRatchet)
type mls (line 76) | namespace mls {
class TransientKeys (line 78) | class TransientKeys {
method GetTransientPrivateKey (line 80) | std::shared_ptr<::mlspp::SignaturePrivateKey> GetTransientPrivat...
method Clear (line 92) | void Clear() { keys_.clear(); }
class SessionWrapper (line 98) | class SessionWrapper {
method SessionWrapper (line 100) | SessionWrapper(std::string ctx, std::string authSessionId, val c...
method Init (line 108) | void Init(ProtocolVersion version,
method Reset (line 116) | void Reset() { session_->Reset(); }
method SetProtocolVersion (line 118) | void SetProtocolVersion(ProtocolVersion version) { session_->Set...
method ProtocolVersion (line 120) | ProtocolVersion GetProtocolVersion() { return session_->GetProto...
method val (line 122) | val GetLastEpochAuthenticator()
method SetExternalSender (line 127) | void SetExternalSender(val externalSender)
method val (line 139) | val ProcessProposals(val proposals, val recognizedUserIDs)
method val (line 154) | val ProcessCommit(val commit)
method val (line 178) | val ProcessWelcome(val welcome, val recognizedUserIDs)
method val (line 197) | val GetMarshalledKeyPackage() { return ToOwnedTypedArray(session...
method val (line 199) | val GetKeyRatchet(std::string const& userId)
class EncryptorWrapper (line 213) | class EncryptorWrapper {
method EncryptorWrapper (line 215) | EncryptorWrapper() { encryptor_ = std::make_unique<Encryptor>(); }
method SetKeyRatchet (line 217) | void SetKeyRatchet(val keyRatchet)
method SetPassthroughMode (line 222) | void SetPassthroughMode(bool passthroughMode)
method AssignSsrcToCodec (line 227) | void AssignSsrcToCodec(uint32_t ssrc, Codec codecType)
method ProtocolVersion (line 232) | ProtocolVersion GetProtocolVersion() { return encryptor_->GetProto...
method GetMaxCiphertextByteSize (line 234) | size_t GetMaxCiphertextByteSize(MediaType mediaType, size_t plaint...
method Encrypt (line 239) | size_t Encrypt(MediaType mediaType,
method SetProtocolVersionChangedCallback (line 266) | void SetProtocolVersionChangedCallback(val callback)
class DecryptorWrapper (line 275) | class DecryptorWrapper {
method DecryptorWrapper (line 277) | DecryptorWrapper() { decryptor_ = std::make_unique<Decryptor>(); }
method TransitionToKeyRatchet (line 279) | void TransitionToKeyRatchet(val keyRatchet)
method TransitionToPassthroughMode (line 284) | void TransitionToPassthroughMode(bool passthroughMode)
method GetMaxPlaintextByteSize (line 289) | size_t GetMaxPlaintextByteSize(MediaType mediaType, size_t ciphert...
method Decrypt (line 294) | size_t Decrypt(MediaType mediaType, int framePtr, size_t frameLeng...
function EMSCRIPTEN_BINDINGS (line 321) | EMSCRIPTEN_BINDINGS(dave)
FILE: cpp/src/boringssl_cryptor.cpp
type discord (line 10) | namespace discord {
type dave (line 11) | namespace dave {
function PrintSSLErrors (line 13) | void PrintSSLErrors()
FILE: cpp/src/boringssl_cryptor.h
function namespace (line 7) | namespace discord {
FILE: cpp/src/codec_utils.cpp
type discord (line 12) | namespace discord {
type dave (line 13) | namespace dave {
type codec_utils (line 14) | namespace codec_utils {
function UnencryptedFrameHeaderSize (line 16) | UnencryptedFrameHeaderSize BytesCoveringH264PPS(const uint8_t* pay...
function FindNextH26XNaluIndex (line 82) | std::optional<IndexStartCodeSizePair> FindNextH26XNaluIndex(const ...
function ProcessFrameOpus (line 128) | bool ProcessFrameOpus(OutboundFrameProcessor& processor, ArrayView...
function ProcessFrameVp8 (line 134) | bool ProcessFrameVp8(OutboundFrameProcessor& processor, ArrayView<...
function ProcessFrameVp9 (line 166) | bool ProcessFrameVp9(OutboundFrameProcessor& processor, ArrayView<...
function ProcessFrameH264 (line 174) | bool ProcessFrameH264(OutboundFrameProcessor& processor, ArrayView...
function ProcessFrameH265 (line 239) | bool ProcessFrameH265(OutboundFrameProcessor& processor, ArrayView...
function ProcessFrameAv1 (line 296) | bool ProcessFrameAv1(OutboundFrameProcessor& processor, ArrayView<...
function ValidateEncryptedFrame (line 399) | bool ValidateEncryptedFrame(OutboundFrameProcessor& processor, Arr...
FILE: cpp/src/codec_utils.h
function namespace (line 8) | namespace discord {
FILE: cpp/src/common.h
function namespace (line 13) | namespace discord {
FILE: cpp/src/cryptor.cpp
type discord (line 9) | namespace discord {
type dave (line 10) | namespace dave {
function CreateCryptor (line 12) | std::unique_ptr<ICryptor> CreateCryptor(const EncryptionKey& encrypt...
FILE: cpp/src/cryptor.h
function namespace (line 8) | namespace discord {
FILE: cpp/src/cryptor_manager.cpp
type discord (line 11) | namespace discord {
type dave (line 12) | namespace dave {
function KeyGeneration (line 14) | KeyGeneration ComputeWrappedGeneration(KeyGeneration oldest, KeyGene...
function BigNonce (line 23) | BigNonce ComputeWrappedBigNonce(KeyGeneration generation, TruncatedS...
function ICryptor (line 50) | ICryptor* CryptorManager::GetCryptor(KeyGeneration generation)
function KeyGeneration (line 136) | KeyGeneration CryptorManager::ComputeWrappedGeneration(KeyGeneration...
FILE: cpp/src/cryptor_manager.h
function namespace (line 12) | namespace discord {
FILE: cpp/src/decryptor.cpp
type discord (line 14) | namespace discord {
type dave (line 15) | namespace dave {
function CreateDecryptor (line 19) | std::unique_ptr<IDecryptor> CreateDecryptor()
FILE: cpp/src/decryptor.h
function virtual (line 44) | virtual DecryptorStats GetStats(MediaType mediaType) const override
function TimePoint (line 68) | TimePoint allowPassThroughUntil_{TimePoint::min()};
FILE: cpp/src/encryptor.cpp
type discord (line 18) | namespace discord {
type dave (line 19) | namespace dave {
function CreateEncryptor (line 23) | std::unique_ptr<IEncryptor> CreateEncryptor()
function Codec (line 246) | Codec Encryptor::CodecForSsrc(uint32_t ssrc)
FILE: cpp/src/encryptor.h
function virtual (line 42) | virtual EncryptorStats GetStats(MediaType mediaType) const override
function virtual (line 48) | virtual void SetProtocolVersionChangedCallback(ProtocolVersionChangedCal...
function KeyGeneration (line 68) | KeyGeneration currentKeyGeneration_{0}
function TruncatedSyncNonce (line 69) | TruncatedSyncNonce truncatedNonce_{0}
function TimePoint (line 78) | TimePoint lastStatsTime_{TimePoint::min()};
FILE: cpp/src/frame_processors.cpp
type discord (line 18) | namespace discord {
type dave (line 19) | namespace dave {
function OverflowAdd (line 21) | std::pair<bool, size_t> OverflowAdd(size_t a, size_t b)
function UnencryptedRangesSize (line 34) | uint8_t UnencryptedRangesSize(const Ranges& unencryptedRanges)
function SerializeUnencryptedRanges (line 46) | uint8_t SerializeUnencryptedRanges(const Ranges& unencryptedRanges,
function DeserializeUnencryptedRanges (line 67) | uint8_t DeserializeUnencryptedRanges(const uint8_t*& readAt,
function ValidateUnencryptedRanges (line 96) | bool ValidateUnencryptedRanges(const Ranges& unencryptedRanges, size...
function Reconstruct (line 123) | size_t Reconstruct(Ranges ranges,
FILE: cpp/src/frame_processors.h
type Range (line 16) | struct Range {
function class (line 31) | class InboundFrameProcessor {
FILE: cpp/src/key_ratchet.h
function namespace (line 7) | namespace discord {
FILE: cpp/src/logger.cpp
type discord (line 7) | namespace discord {
type dave (line 8) | namespace dave {
function SetLogSink (line 12) | void SetLogSink(LogSink sink)
FILE: cpp/src/mls/detail/persisted_key_pair.h
function namespace (line 10) | namespace discord {
FILE: cpp/src/mls/detail/persisted_key_pair_apple.cpp
function AddAccessGroup (line 29) | static void AddAccessGroup([[maybe_unused]] CFMutableDictionaryRef dict)
type ScopedCFTypeRef (line 39) | struct ScopedCFTypeRef {
method ScopedCFTypeRef (line 40) | ScopedCFTypeRef() = default;
method ScopedCFTypeRef (line 41) | ScopedCFTypeRef(T ref)
method ScopedCFTypeRef (line 45) | ScopedCFTypeRef(ScopedCFTypeRef& other)
method ScopedCFTypeRef (line 52) | ScopedCFTypeRef(ScopedCFTypeRef&& other)
method ScopedCFTypeRef (line 59) | ScopedCFTypeRef& operator=(T ref)
method release (line 66) | void release()
method T (line 74) | T& get() { return ref_; }
method T (line 76) | T* getPtr() { return &ref_; }
method CFTypeRef (line 77) | CFTypeRef* getGenericPtr() { return (CFTypeRef*)getPtr(); }
function ConvertCFString (line 86) | static std::string ConvertCFString(CFStringRef string)
function SecStatusToString (line 109) | static std::string SecStatusToString(OSStatus status)
function ErrorToString (line 125) | static std::string ErrorToString(CFErrorRef error)
type discord (line 152) | namespace discord {
type dave (line 153) | namespace dave {
type mls (line 154) | namespace mls {
type detail (line 155) | namespace detail {
function GetNativePersistedKeyPair (line 157) | std::shared_ptr<::mlspp::SignaturePrivateKey> GetNativePersisted...
function GetGenericPersistedKeyPair (line 353) | std::shared_ptr<::mlspp::SignaturePrivateKey> GetGenericPersiste...
function DeleteWithQuery (line 443) | static bool DeleteWithQuery(CFMutableDictionaryRef query)
function DeleteNativePersistedKeyPair (line 465) | bool DeleteNativePersistedKeyPair([[maybe_unused]] KeyPairContex...
function DeleteGenericPersistedKeyPair (line 481) | bool DeleteGenericPersistedKeyPair([[maybe_unused]] KeyPairConte...
FILE: cpp/src/mls/detail/persisted_key_pair_generic.cpp
function GetKeyStorageDirectory (line 27) | static std::filesystem::path GetKeyStorageDirectory()
type discord (line 62) | namespace discord {
type dave (line 63) | namespace dave {
type mls (line 64) | namespace mls {
type detail (line 65) | namespace detail {
function GetGenericPersistedKeyPair (line 67) | std::shared_ptr<::mlspp::SignaturePrivateKey> GetGenericPersiste...
function DeleteGenericPersistedKeyPair (line 162) | bool DeleteGenericPersistedKeyPair([[maybe_unused]] KeyPairConte...
FILE: cpp/src/mls/detail/persisted_key_pair_null.cpp
type discord (line 3) | namespace discord {
type dave (line 4) | namespace dave {
type mls (line 5) | namespace mls {
type detail (line 6) | namespace detail {
function GetNativePersistedKeyPair (line 8) | std::shared_ptr<::mlspp::SignaturePrivateKey> GetNativePersisted...
function DeleteNativePersistedKeyPair (line 18) | bool DeleteNativePersistedKeyPair([[maybe_unused]] KeyPairContex...
FILE: cpp/src/mls/detail/persisted_key_pair_win.cpp
type ScopedNCryptHandle (line 29) | struct ScopedNCryptHandle {
method ScopedNCryptHandle (line 30) | ScopedNCryptHandle() = default;
method ScopedNCryptHandle (line 31) | ScopedNCryptHandle(T handle)
method ScopedNCryptHandle (line 35) | ScopedNCryptHandle(const ScopedNCryptHandle& other) = delete;
method ScopedNCryptHandle (line 36) | ScopedNCryptHandle(ScopedNCryptHandle&& other)
method ScopedNCryptHandle (line 43) | ScopedNCryptHandle& operator=(T handle)
method T (line 50) | T release() { return std::exchange(handle_, T()); }
method finalize (line 52) | void finalize()
method T (line 59) | T& get() { return handle_; }
method T (line 61) | T* getPtr() { return &handle_; }
type discord (line 70) | namespace discord {
type dave (line 71) | namespace dave {
type mls (line 72) | namespace mls {
type detail (line 73) | namespace detail {
function GetNativePersistedKeyPair (line 75) | std::shared_ptr<::mlspp::SignaturePrivateKey> GetNativePersisted...
function DeleteNativePersistedKeyPair (line 253) | bool DeleteNativePersistedKeyPair([[maybe_unused]] KeyPairContex...
FILE: cpp/src/mls/parameters.cpp
type discord (line 3) | namespace discord {
type dave (line 4) | namespace dave {
type mls (line 5) | namespace mls {
function CiphersuiteIDForProtocolVersion (line 7) | ::mlspp::CipherSuite::ID CiphersuiteIDForProtocolVersion(
function CiphersuiteForProtocolVersion (line 13) | ::mlspp::CipherSuite CiphersuiteForProtocolVersion(ProtocolVersion...
function CiphersuiteIDForSignatureVersion (line 18) | ::mlspp::CipherSuite::ID CiphersuiteIDForSignatureVersion(
function CiphersuiteForSignatureVersion (line 24) | ::mlspp::CipherSuite CiphersuiteForSignatureVersion(SignatureVersi...
function LeafNodeCapabilitiesForProtocolVersion (line 29) | ::mlspp::Capabilities LeafNodeCapabilitiesForProtocolVersion(Proto...
function LeafNodeExtensionsForProtocolVersion (line 39) | ::mlspp::ExtensionList LeafNodeExtensionsForProtocolVersion(
function GroupExtensionsForProtocolVersion (line 45) | ::mlspp::ExtensionList GroupExtensionsForProtocolVersion(
FILE: cpp/src/mls/parameters.h
function namespace (line 8) | namespace discord {
FILE: cpp/src/mls/persisted_key_pair.cpp
function MakeKeyID (line 19) | static std::string MakeKeyID(const std::string& sessionID, ::mlspp::Ciph...
type discord (line 28) | namespace discord {
type dave (line 29) | namespace dave {
type mls (line 30) | namespace mls {
function GetPersistedKeyPair (line 32) | static std::shared_ptr<::mlspp::SignaturePrivateKey> GetPersistedK...
function GetPersistedKeyPair (line 70) | std::shared_ptr<::mlspp::SignaturePrivateKey> GetPersistedKeyPair(...
function KeyAndSelfSignature (line 77) | KeyAndSelfSignature GetPersistedPublicKey(KeyPairContextType ctx,
function DeletePersistedKeyPair (line 97) | bool DeletePersistedKeyPair([[maybe_unused]] KeyPairContextType ctx,
FILE: cpp/src/mls/persisted_key_pair.h
function namespace (line 14) | namespace mlspp {
function namespace (line 18) | namespace discord {
FILE: cpp/src/mls/persisted_key_pair_null.cpp
type discord (line 3) | namespace discord {
type dave (line 4) | namespace dave {
type mls (line 5) | namespace mls {
function GetPersistedKeyPair (line 7) | std::shared_ptr<::mlspp::SignaturePrivateKey> GetPersistedKeyPair(
function DeletePersistedKeyPair (line 15) | bool DeletePersistedKeyPair([[maybe_unused]] KeyPairContextType,
FILE: cpp/src/mls/session.cpp
type discord (line 28) | namespace discord {
type dave (line 29) | namespace dave {
type mls (line 30) | namespace mls {
type QueuedProposal (line 32) | struct QueuedProposal {
function CreateSession (line 37) | std::unique_ptr<ISession> CreateSession(KeyPairContextType context,
function RosterVariant (line 347) | RosterVariant Session::ProcessCommit(std::vector<uint8_t> commit) ...
function RosterMap (line 478) | RosterMap Session::ReplaceState(std::unique_ptr<::mlspp::State>&& ...
FILE: cpp/src/mls/session.h
function namespace (line 17) | namespace mlspp {
function namespace (line 29) | namespace discord {
FILE: cpp/src/mls/user_credential.cpp
type discord (line 7) | namespace discord {
type dave (line 8) | namespace dave {
type mls (line 9) | namespace mls {
function CreateUserCredential (line 11) | ::mlspp::Credential CreateUserCredential(const std::string& userId,
function UserCredentialToString (line 21) | std::string UserCredentialToString(const ::mlspp::Credential& cred,
FILE: cpp/src/mls/user_credential.h
function namespace (line 9) | namespace discord {
FILE: cpp/src/mls/util.cpp
type discord (line 3) | namespace discord {
type dave (line 4) | namespace dave {
type mls (line 5) | namespace mls {
function BigEndianBytesFrom (line 7) | ::mlspp::bytes_ns::bytes BigEndianBytesFrom(uint64_t value) noexcept
function FromBigEndianBytes (line 19) | uint64_t FromBigEndianBytes(const ::mlspp::bytes_ns::bytes& buffer...
FILE: cpp/src/mls/util.h
function namespace (line 7) | namespace discord {
FILE: cpp/src/mls_key_ratchet.cpp
type discord (line 9) | namespace discord {
type dave (line 10) | namespace dave {
function EncryptionKey (line 19) | EncryptionKey MlsKeyRatchet::GetKey(KeyGeneration generation) noexcept
FILE: cpp/src/mls_key_ratchet.h
function namespace (line 8) | namespace discord {
FILE: cpp/src/openssl_cryptor.cpp
type discord (line 10) | namespace discord {
type dave (line 11) | namespace dave {
function PrintSSLErrors (line 13) | void PrintSSLErrors()
FILE: cpp/src/openssl_cryptor.h
function namespace (line 7) | namespace discord {
FILE: cpp/src/utils/clock.h
function namespace (line 5) | namespace discord {
FILE: cpp/src/utils/leb128.cpp
type discord (line 7) | namespace discord {
type dave (line 8) | namespace dave {
function Leb128Size (line 10) | size_t Leb128Size(uint64_t value)
function ReadLeb128 (line 20) | uint64_t ReadLeb128(const uint8_t*& readAt, const uint8_t* end)
function WriteLeb128 (line 46) | size_t WriteLeb128(uint64_t value, uint8_t* buffer)
FILE: cpp/src/utils/leb128.h
function namespace (line 6) | namespace discord {
FILE: cpp/src/utils/scope_exit.h
function class (line 10) | class [[nodiscard]] ScopeExit final {
FILE: cpp/src/version.cpp
type discord (line 3) | namespace discord {
type dave (line 4) | namespace dave {
function ProtocolVersion (line 8) | ProtocolVersion MaxSupportedProtocolVersion()
FILE: cpp/test/capi/basic_tests.c
type CRITICAL_SECTION (line 8) | typedef CRITICAL_SECTION mutex_t;
type CONDITION_VARIABLE (line 9) | typedef CONDITION_VARIABLE cond_t;
type pthread_mutex_t (line 13) | typedef pthread_mutex_t mutex_t;
type pthread_cond_t (line 14) | typedef pthread_cond_t cond_t;
function TestEncryptorCreateDestroy (line 35) | static int TestEncryptorCreateDestroy(void)
function TestDecryptorCreateDestroy (line 45) | static int TestDecryptorCreateDestroy(void)
function TestMaxProtocolVersion (line 55) | static int TestMaxProtocolVersion(void)
function TestEncryptorPassthrough (line 63) | static int TestEncryptorPassthrough(void)
function TestDecryptorPassthrough (line 115) | static int TestDecryptorPassthrough(void)
function TestPassthroughInOutBuffer (line 157) | static int TestPassthroughInOutBuffer(void)
function TestPassthroughTwoBuffers (line 220) | static int TestPassthroughTwoBuffers(void)
function TestSessionFailureCallback (line 287) | static void TestSessionFailureCallback(const char* source, const char* r...
type PairwiseFingerprintData (line 293) | typedef struct {
function PairwiseFingerprintDataInit (line 300) | static void PairwiseFingerprintDataInit(PairwiseFingerprintData* data)
function PairwiseFingerprintDataDestroy (line 313) | static void PairwiseFingerprintDataDestroy(PairwiseFingerprintData* data)
function PairwiseFingerprintDataWait (line 327) | static void PairwiseFingerprintDataWait(PairwiseFingerprintData* data)
function PairwiseFingerprintCallback (line 344) | static void PairwiseFingerprintCallback(const uint8_t* pairwiseFingerprint,
function TestSession (line 369) | static int TestSession(void)
function TestExceptions (line 668) | static int TestExceptions(void)
function main (line 689) | int main(void)
FILE: cpp/test/capi/external_sender_wrapper.cpp
function CopyVectorToOutputBuffer (line 10) | void CopyVectorToOutputBuffer(std::vector<uint8_t> const& vector, uint8_...
function DAVEExternalSenderHandle (line 29) | DAVEExternalSenderHandle daveExternalSenderCreate(uint64_t groupId)
function daveExternalSenderDestroy (line 37) | void daveExternalSenderDestroy(DAVEExternalSenderHandle externalSenderHa...
function daveExternalSenderGetMarshalledExternalSender (line 44) | void daveExternalSenderGetMarshalledExternalSender(DAVEExternalSenderHan...
function daveExternalSenderProposeAdd (line 54) | void daveExternalSenderProposeAdd(DAVEExternalSenderHandle externalSende...
function daveExternalSenderSplitCommitWelcome (line 68) | void daveExternalSenderSplitCommitWelcome(DAVEExternalSenderHandle exter...
FILE: cpp/test/capi/test_helpers.c
function HexDigitToValue (line 6) | static int HexDigitToValue(char c)
FILE: cpp/test/codec_utils_tests.cpp
type discord (line 14) | namespace discord {
type dave (line 15) | namespace dave {
type test (line 16) | namespace test {
function TEST_F (line 18) | TEST_F(DaveTests, RandomOpusFrame)
function TEST_F (line 43) | TEST_F(DaveTests, SplitReconstruct)
function TEST_F (line 70) | TEST_F(DaveTests, H264SliceOneByteExpGolomb)
function TEST_F (line 93) | TEST_F(DaveTests, H264ShortIDROneByteExpGolomb)
function TEST_F (line 116) | TEST_F(DaveTests, H264ShortIDRTwoByteExpGolomb)
function TEST_F (line 139) | TEST_F(DaveTests, H264LongIDROneByteExpGolomb)
function TEST_F (line 164) | TEST_F(DaveTests, H264LongIDRTwoByteExpGolomb)
function TEST_F (line 189) | TEST_F(DaveTests, H264EmulationPreventionInEarlyExpGolomb)
function TEST_F (line 209) | TEST_F(DaveTests, H264ThreeByteShortCodeExtension)
function TEST_F (line 247) | TEST_F(DaveTests, H264TwoSliceTest)
function TEST_F (line 273) | TEST_F(DaveTests, H265IdrSlice)
function TEST_F (line 296) | TEST_F(DaveTests, H265TsaSlice)
function TEST_F (line 315) | TEST_F(DaveTests, H265SimpleThreeByteCodeExtension)
function TEST_F (line 334) | TEST_F(DaveTests, H265MultipleThreeByteCodeExtensions)
function TEST_F (line 357) | TEST_F(DaveTests, H265TwoIdrSlice)
FILE: cpp/test/cryptor_manager_tests.cpp
type discord (line 17) | namespace discord {
type dave (line 18) | namespace dave {
type test (line 19) | namespace test {
class MockKeyRatchet (line 25) | class MockKeyRatchet : public IKeyRatchet {
method MockKeyRatchet (line 27) | MockKeyRatchet()
class MockClock (line 38) | class MockClock : public IClock {
method TimePoint (line 40) | TimePoint Now() const override { return now_; }
method SetNow (line 42) | void SetNow(TimePoint now) { now_ = now; }
method Advance (line 43) | void Advance(Duration duration) { now_ += duration; }
function TEST_F (line 49) | TEST_F(DaveTests, CryptorManagerCheckMaxGap)
function TEST_F (line 72) | TEST_F(DaveTests, CryptorManagerCheckExpiry)
function TEST_F (line 92) | TEST_F(DaveTests, CryptorManagerDeleteOldKeys)
function TEST_F (line 115) | TEST_F(DaveTests, CryptorManagerGenerationWrap)
function TEST_F (line 129) | TEST_F(DaveTests, CryptorManagerBigNonce)
function TEST_F (line 147) | TEST_F(DaveTests, CryptorManagerNoReprocess)
FILE: cpp/test/cryptor_tests.cpp
type discord (line 13) | namespace discord {
type dave (line 14) | namespace dave {
type test (line 15) | namespace test {
function TEST_F (line 21) | TEST_F(DaveTests, PassthroughInOutBuffer)
function TEST_F (line 55) | TEST_F(DaveTests, PassthroughTwoBuffers)
function TEST_F (line 90) | TEST_F(DaveTests, SilencePacketPassthrough)
function TEST_F (line 109) | TEST_F(DaveTests, RandomOpusFrameEncryptDecrypt)
FILE: cpp/test/dave_test.cpp
type discord (line 3) | namespace discord {
type dave (line 4) | namespace dave {
type test (line 5) | namespace test {
function GetBufferFromHex (line 7) | std::vector<uint8_t> GetBufferFromHex(const std::string_view& hex)
FILE: cpp/test/dave_test.h
function namespace (line 6) | namespace discord {
FILE: cpp/test/external_sender.cpp
type discord (line 6) | namespace discord {
type dave (line 7) | namespace dave {
type test (line 8) | namespace test {
function BigEndianBytesFrom (line 10) | ::mlspp::bytes_ns::bytes BigEndianBytesFrom(uint64_t value) noexcept
function CiphersuiteForProtocolVersion (line 22) | ::mlspp::CipherSuite CiphersuiteForProtocolVersion(
FILE: cpp/test/external_sender.h
function namespace (line 6) | namespace discord {
FILE: cpp/test/static_key_ratchet.cpp
type discord (line 10) | namespace discord {
type dave (line 11) | namespace dave {
type test (line 12) | namespace test {
function EncryptionKey (line 14) | EncryptionKey MakeStaticSenderKey(const std::string& userID)
function EncryptionKey (line 20) | EncryptionKey MakeStaticSenderKey(uint64_t u64userID)
function EncryptionKey (line 35) | EncryptionKey StaticKeyRatchet::GetKey(KeyGeneration generation) n...
FILE: cpp/test/static_key_ratchet.h
function namespace (line 7) | namespace discord {
FILE: cpp/test/xssl_cryptor_tests.cpp
type discord (line 14) | namespace discord {
type dave (line 15) | namespace dave {
type test (line 16) | namespace test {
function TEST_F (line 24) | TEST_F(DaveTests, XSSLEncryptDecrypt)
function TEST_F (line 60) | TEST_F(DaveTests, XSSLAdditionalDataAuth)
function TEST_F (line 93) | TEST_F(DaveTests, XSSLKeyDiff)
function TEST_F (line 127) | TEST_F(DaveTests, XSSLNonceDiff)
FILE: js/jest-setup.js
function convertAlgorithm (line 3) | function convertAlgorithm(name) {
FILE: js/src/DisplayableCode.ts
constant MAX_GROUP_SIZE (line 1) | const MAX_GROUP_SIZE = 8;
function generateDisplayableCode (line 3) | function generateDisplayableCode(data: Uint8Array, desiredLength: number...
FILE: js/src/KeyFingerprint.ts
constant VERSION_LEN (line 1) | const VERSION_LEN = 2;
constant UID_LEN (line 2) | const UID_LEN = 8;
function generateKeyFingerprint (line 4) | async function generateKeyFingerprint(version: number, key: Uint8Array, ...
FILE: js/src/KeySerialization.ts
function serializeKey (line 3) | function serializeKey(data: Uint8Array): string {
FILE: js/src/PairwiseFingerprint.ts
function compareArrays (line 29) | function compareArrays(a: Uint8Array, b: Uint8Array) {
function generatePairwiseFingerprint (line 37) | async function generatePairwiseFingerprint(
FILE: samples/typescript/DaveSessionManager.ts
constant MLS_NEW_GROUP_EXPECTED_EPOCH (line 3) | const MLS_NEW_GROUP_EXPECTED_EPOCH = '1';
constant DAVE_PROTOCOL_INIT_TRANSITION_ID (line 4) | const DAVE_PROTOCOL_INIT_TRANSITION_ID = 0;
class DaveSessionManager (line 6) | class DaveSessionManager {
method constructor (line 17) | constructor(dave: DaveModule, transientKeys: TransientKeys | null, sel...
method createUser (line 33) | public createUser(userId: string) {
method destroyUser (line 39) | public destroyUser(userId: string) {
method onSelectProtocolAck (line 47) | public onSelectProtocolAck(protocolVersion: number) {
method onDaveProtocolPrepareTransition (line 52) | public onDaveProtocolPrepareTransition(transitionId: number, protocolV...
method onDaveProtocolExecuteTransition (line 58) | public onDaveProtocolExecuteTransition(transitionId: number) {
method onDaveProtocolPrepareEpoch (line 63) | public onDaveProtocolPrepareEpoch(epoch: string, protocolVersion: numb...
method onDaveProtocolMLSExternalSenderPackage (line 72) | public onDaveProtocolMLSExternalSenderPackage(externalSenderPackage: A...
method onMLSProposals (line 77) | public onMLSProposals(proposals: ArrayBuffer) {
method onMLSPrepareCommitTransition (line 85) | public onMLSPrepareCommitTransition(transitionId: number, commit: Arra...
method onMLSWelcome (line 103) | public onMLSWelcome(transitionId: number, welcome: ArrayBuffer) {
method _sendMLSKeyPackage (line 119) | private _sendMLSKeyPackage() {
method _maybeSendDaveProtocolReadyForTransition (line 125) | private _maybeSendDaveProtocolReadyForTransition(transitionId: number) {
method _sendMLSCommitWelcome (line 132) | private _sendMLSCommitWelcome(commitWelcomeMessage: ArrayBuffer) {
method _flagMLSInvalidCommitWelcome (line 137) | private _flagMLSInvalidCommitWelcome(transitionId: number) {
method _setupKeyRatchetForUser (line 143) | private _setupKeyRatchetForUser(userId: string, protocolVersion: numbe...
method _handleDaveProtocolInit (line 148) | private _handleDaveProtocolInit(protocolVersion: number) {
method _handleDaveProtocolPrepareEpoch (line 158) | private _handleDaveProtocolPrepareEpoch(epoch: string, protocolVersion...
method _handleDaveProtocolExecuteTransition (line 169) | private _handleDaveProtocolExecuteTransition(transitionID: number): vo...
method _getRecognizedUserIDs (line 184) | private _getRecognizedUserIDs(): string[] {
method _makeUserKeyRatchet (line 188) | private _makeUserKeyRatchet(userId: string, protocolVersion: number): ...
method _prepareDaveProtocolRatchets (line 196) | private _prepareDaveProtocolRatchets(transitionID: number, protocolVer...
Condensed preview — 111 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (396K chars).
[
{
"path": ".github/actions/prepare-build/action.yaml",
"chars": 2572,
"preview": "name: Install build prerequisites\n\ninputs:\n runner:\n description: The runner on which the action is being run\n re"
},
{
"path": ".github/workflows/main.yaml",
"chars": 3003,
"preview": "name: cpp\n\non:\n push:\n branches: [\"main\"]\n pull_request:\n branches: [\"**\"]\n\nenv:\n CMAKE_BUILD_PARALLEL_LEVEL: 3"
},
{
"path": ".gitignore",
"chars": 9,
"preview": ".DS_Store"
},
{
"path": ".gitmodules",
"chars": 88,
"preview": "[submodule \"cpp/vcpkg\"]\n\tpath = cpp/vcpkg\n\turl = https://github.com/microsoft/vcpkg.git\n"
},
{
"path": "CONTRIBUTING.md",
"chars": 119,
"preview": "At this time we are not taking pull requests to this repository. We welcome reports and suggestions via Github Issues.\n"
},
{
"path": "LICENSE",
"chars": 1064,
"preview": "MIT License\n\nCopyright (c) 2024 Discord\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof"
},
{
"path": "README.md",
"chars": 465,
"preview": "## libdave\n\nThis repository contains the JS and C++ libraries which together implement Discord's Audio & Video End-to-En"
},
{
"path": "cpp/.clang-format",
"chars": 2257,
"preview": "---\nAccessModifierOffset: -4\nAlignAfterOpenBracket: true\nAlignConsecutiveAssignments: false\nAlignConsecutiveDeclarations"
},
{
"path": "cpp/.gitignore",
"chars": 6,
"preview": "build/"
},
{
"path": "cpp/CMakeLists.txt",
"chars": 8457,
"preview": "cmake_minimum_required(VERSION 3.20)\n\nproject(\n libdave\n VERSION 1.0\n LANGUAGES CXX C\n)\n\noption(REQUIRE_BORINGSSL \"Re"
},
{
"path": "cpp/Makefile",
"chars": 4327,
"preview": "# Options\nBUILD_DIR=build\nINSTALL_DIR=${BUILD_DIR}/install\nSANITIZERS=OFF\nBUILD_SHARED_LIBS=OFF\nPERSISTENT_KEYS=OFF\nTEST"
},
{
"path": "cpp/README.md",
"chars": 1022,
"preview": "## libdave C++\n\nContains the libdave C++ library, which handles the bulk of the DAVE protocol implementation for Discord"
},
{
"path": "cpp/afl-driver/src/main.cpp",
"chars": 968,
"preview": "#include <array>\n#include <cassert>\n#include <iostream>\n#include <memory>\n#include <unistd.h>\n\n#include <fuzzer/FuzzedDa"
},
{
"path": "cpp/includes/dave/array_view.h",
"chars": 795,
"preview": "#pragma once\n\n#include <cassert>\n#include <cstddef>\n#include <vector>\n\nnamespace discord {\nnamespace dave {\n\ntemplate <t"
},
{
"path": "cpp/includes/dave/dave.h",
"chars": 27564,
"preview": "/**\n * @file dave.h\n * @brief DAVE (Discord Audio/Video Encryption) C API\n *\n * This header provides the C API for end-t"
},
{
"path": "cpp/includes/dave/dave_interfaces.h",
"chars": 8380,
"preview": "#pragma once\n\n#include <functional>\n#include <memory>\n#include <optional>\n#include <set>\n#include <string>\n#include <map"
},
{
"path": "cpp/includes/dave/logger.h",
"chars": 945,
"preview": "#pragma once\n\n#include <sstream>\n\n#include <dave/dave_interfaces.h>\n\n#if !defined(DISCORD_LOG)\n#define DISCORD_LOG_FILE_"
},
{
"path": "cpp/includes/dave/version.h",
"chars": 257,
"preview": "#pragma once\n\n#include <stdint.h>\n\n#include <dave/dave.h>\n\nnamespace discord {\nnamespace dave {\n\nusing ProtocolVersion ="
},
{
"path": "cpp/src/bindings_capi.cpp",
"chars": 21532,
"preview": "#include <dave/dave_interfaces.h>\n\n#include <atomic>\n#include <cstdint>\n#include <cstring>\n\n#include <mls/messages.h>\n\n#"
},
{
"path": "cpp/src/bindings_wasm.cpp",
"chars": 13603,
"preview": "#include <cstdint>\n#include <memory>\n#include <set>\n#include <vector>\n\n#include <emscripten.h>\n#include <emscripten/bind"
},
{
"path": "cpp/src/boringssl_cryptor.cpp",
"chars": 3968,
"preview": "#include \"boringssl_cryptor.h\"\n\n#include <openssl/err.h>\n\n#include <bytes/bytes.h>\n#include <dave/logger.h>\n\n#include \"c"
},
{
"path": "cpp/src/boringssl_cryptor.h",
"chars": 968,
"preview": "#pragma once\n\n#include <openssl/aead.h>\n\n#include \"cryptor.h\"\n\nnamespace discord {\nnamespace dave {\n\nclass BoringSSLCryp"
},
{
"path": "cpp/src/codec_utils.cpp",
"chars": 17600,
"preview": "#include \"codec_utils.h\"\n\n#include <cassert>\n#include <limits>\n#include <optional>\n\n#include <dave/logger.h>\n\n#include \""
},
{
"path": "cpp/src/codec_utils.h",
"chars": 856,
"preview": "#pragma once\n\n#include <dave/array_view.h>\n\n#include \"common.h\"\n#include \"frame_processors.h\"\n\nnamespace discord {\nnames"
},
{
"path": "cpp/src/common.h",
"chars": 2023,
"preview": "#pragma once\n\n#include <array>\n#include <chrono>\n#include <map>\n#include <optional>\n#include <string>\n#include <variant>"
},
{
"path": "cpp/src/cryptor.cpp",
"chars": 516,
"preview": "#include \"cryptor.h\"\n\n#ifdef WITH_BORINGSSL\n#include \"boringssl_cryptor.h\"\n#else\n#include \"openssl_cryptor.h\"\n#endif\n\nna"
},
{
"path": "cpp/src/cryptor.h",
"chars": 972,
"preview": "#pragma once\n\n#include <memory>\n\n#include <dave/array_view.h>\n#include <dave/dave_interfaces.h>\n\nnamespace discord {\nnam"
},
{
"path": "cpp/src/cryptor_manager.cpp",
"chars": 6526,
"preview": "#include \"cryptor_manager.h\"\n\n#include <limits>\n\n#include <dave/logger.h>\n\n#include <bytes/bytes.h>\n\nusing namespace std"
},
{
"path": "cpp/src/cryptor_manager.h",
"chars": 1642,
"preview": "#pragma once\n\n#include <deque>\n#include <memory>\n#include <optional>\n#include <unordered_map>\n\n#include \"common.h\"\n#incl"
},
{
"path": "cpp/src/decryptor.cpp",
"chars": 8952,
"preview": "#include \"decryptor.h\"\n\n#include <cstring>\n\n#include <bytes/bytes.h>\n#include <dave/logger.h>\n\n#include \"common.h\"\n#incl"
},
{
"path": "cpp/src/decryptor.h",
"chars": 2191,
"preview": "#pragma once\n\n#include <array>\n#include <deque>\n#include <functional>\n#include <memory>\n#include <mutex>\n#include <vecto"
},
{
"path": "cpp/src/encryptor.cpp",
"chars": 11327,
"preview": "#include \"encryptor.h\"\n\n#include <algorithm>\n#include <cstring>\n\n#include <bytes/bytes.h>\n#include <dave/array_view.h>\n#"
},
{
"path": "cpp/src/encryptor.h",
"chars": 2924,
"preview": "#pragma once\n\n#include <array>\n#include <atomic>\n#include <chrono>\n#include <functional>\n#include <memory>\n#include <mut"
},
{
"path": "cpp/src/frame_processors.cpp",
"chars": 13187,
"preview": "#include \"frame_processors.h\"\n\n#include <cassert>\n#include <cstring>\n#include <limits>\n#include <optional>\n\n#include <da"
},
{
"path": "cpp/src/frame_processors.h",
"chars": 2972,
"preview": "#pragma once\n\n#include <cstdint>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include <dave/array_view.h>\n#i"
},
{
"path": "cpp/src/key_ratchet.h",
"chars": 135,
"preview": "#pragma once\n\n#include <memory>\n\n#include \"common.h\"\n\nnamespace discord {\nnamespace dave {\n\n} // namespace dave\n} // nam"
},
{
"path": "cpp/src/logger.cpp",
"chars": 1024,
"preview": "#include <dave/logger.h>\n\n#include <atomic>\n#include <cstring>\n#include <iostream>\n\nnamespace discord {\nnamespace dave {"
},
{
"path": "cpp/src/mls/detail/persisted_key_pair.h",
"chars": 986,
"preview": "#pragma once\n\n#include <memory>\n#include <string>\n\n#include <mls/crypto.h>\n\n#include \"mls/persisted_key_pair.h\"\n\nnamespa"
},
{
"path": "cpp/src/mls/detail/persisted_key_pair_apple.cpp",
"chars": 17028,
"preview": "#include \"mls/detail/persisted_key_pair.h\"\n\n#include <cassert>\n#include <filesystem>\n#include <fstream>\n#include <functi"
},
{
"path": "cpp/src/mls/detail/persisted_key_pair_generic.cpp",
"chars": 4990,
"preview": "#include \"mls/detail/persisted_key_pair.h\"\n\n#include <cassert>\n#include <filesystem>\n#include <fstream>\n#include <functi"
},
{
"path": "cpp/src/mls/detail/persisted_key_pair_null.cpp",
"chars": 651,
"preview": "#include \"mls/detail/persisted_key_pair.h\"\n\nnamespace discord {\nnamespace dave {\nnamespace mls {\nnamespace detail {\n\nstd"
},
{
"path": "cpp/src/mls/detail/persisted_key_pair_win.cpp",
"chars": 9701,
"preview": "#include \"mls/detail/persisted_key_pair.h\"\n\n#include <cassert>\n#include <filesystem>\n#include <fstream>\n#include <functi"
},
{
"path": "cpp/src/mls/parameters.cpp",
"chars": 1703,
"preview": "#include \"parameters.h\"\n\nnamespace discord {\nnamespace dave {\nnamespace mls {\n\n::mlspp::CipherSuite::ID CiphersuiteIDFor"
},
{
"path": "cpp/src/mls/parameters.h",
"chars": 931,
"preview": "#pragma once\n\n#include <dave/version.h>\n#include <mls/core_types.h>\n#include <mls/crypto.h>\n#include <mls/messages.h>\n\nn"
},
{
"path": "cpp/src/mls/persisted_key_pair.cpp",
"chars": 3322,
"preview": "#include \"mls/detail/persisted_key_pair.h\"\n\n#include <cassert>\n#include <filesystem>\n#include <fstream>\n#include <functi"
},
{
"path": "cpp/src/mls/persisted_key_pair.h",
"chars": 1128,
"preview": "#pragma once\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#ifdef __ANDROID__\n#include <jni.h>\n#endif\n\n#includ"
},
{
"path": "cpp/src/mls/persisted_key_pair_null.cpp",
"chars": 571,
"preview": "#include \"persisted_key_pair.h\"\n\nnamespace discord {\nnamespace dave {\nnamespace mls {\n\nstd::shared_ptr<::mlspp::Signatur"
},
{
"path": "cpp/src/mls/session.cpp",
"chars": 28078,
"preview": "#include \"session.h\"\n\n#include <cstring>\n#include <thread>\n#include <vector>\n\n#include <dave/logger.h>\n#include <hpke/ra"
},
{
"path": "cpp/src/mls/session.h",
"chars": 4440,
"preview": "#pragma once\n\n#include <functional>\n#include <list>\n#include <memory>\n#include <optional>\n#include <set>\n#include <strin"
},
{
"path": "cpp/src/mls/user_credential.cpp",
"chars": 949,
"preview": "#include \"user_credential.h\"\n\n#include <string>\n\n#include \"mls/util.h\"\n\nnamespace discord {\nnamespace dave {\nnamespace m"
},
{
"path": "cpp/src/mls/user_credential.h",
"chars": 394,
"preview": "#pragma once\n\n#include <string>\n\n#include <mls/credential.h>\n\n#include <dave/version.h>\n\nnamespace discord {\nnamespace d"
},
{
"path": "cpp/src/mls/util.cpp",
"chars": 687,
"preview": "#include \"util.h\"\n\nnamespace discord {\nnamespace dave {\nnamespace mls {\n\n::mlspp::bytes_ns::bytes BigEndianBytesFrom(uin"
},
{
"path": "cpp/src/mls/util.h",
"chars": 323,
"preview": "#pragma once\n\n#include <string>\n\n#include <bytes/bytes.h>\n\nnamespace discord {\nnamespace dave {\nnamespace mls {\n\n::mlspp"
},
{
"path": "cpp/src/mls_key_ratchet.cpp",
"chars": 1038,
"preview": "#include \"mls_key_ratchet.h\"\n\n#include <cassert>\n\n#include <dave/logger.h>\n\n#include \"common.h\"\n\nnamespace discord {\nnam"
},
{
"path": "cpp/src/mls_key_ratchet.h",
"chars": 629,
"preview": "#pragma once\n\n#include <bytes/bytes.h>\n#include <mls/key_schedule.h>\n\n#include <dave/dave_interfaces.h>\n\nnamespace disco"
},
{
"path": "cpp/src/openssl_cryptor.cpp",
"chars": 6715,
"preview": "#include \"openssl_cryptor.h\"\n\n#include <openssl/err.h>\n\n#include <bytes/bytes.h>\n#include <dave/logger.h>\n\n#include \"com"
},
{
"path": "cpp/src/openssl_cryptor.h",
"chars": 969,
"preview": "#pragma once\n\n#include <openssl/evp.h>\n\n#include \"cryptor.h\"\n\nnamespace discord {\nnamespace dave {\n\nclass OpenSSLCryptor"
},
{
"path": "cpp/src/utils/clock.h",
"chars": 456,
"preview": "#pragma once\n\n#include <chrono>\n\nnamespace discord {\nnamespace dave {\n\nclass IClock {\npublic:\n using BaseClock = std:"
},
{
"path": "cpp/src/utils/leb128.cpp",
"chars": 1452,
"preview": "\n#include \"leb128.h\"\n\n// The following code was copied from the webrtc source code:\n// https://webrtc.googlesource.com/s"
},
{
"path": "cpp/src/utils/leb128.h",
"chars": 642,
"preview": "#pragma once\n\n#include <cstddef>\n#include <cstdint>\n\nnamespace discord {\nnamespace dave {\n\nconstexpr size_t Leb128MaxSiz"
},
{
"path": "cpp/src/utils/scope_exit.h",
"chars": 888,
"preview": "#pragma once\n\n#include <algorithm>\n#include <functional>\n#include <utility>\n\nnamespace discord {\nnamespace dave {\n\nclass"
},
{
"path": "cpp/src/version.cpp",
"chars": 257,
"preview": "#include <dave/version.h>\n\nnamespace discord {\nnamespace dave {\n\nconstexpr ProtocolVersion CurrentDaveProtocolVersion = "
},
{
"path": "cpp/test/CMakeLists.txt",
"chars": 1391,
"preview": "enable_testing()\n\nfind_package(GTest CONFIG REQUIRED)\n\nSET(TEST_APP_NAME \"libdave_test\")\n\nfile(GLOB_RECURSE TEST_HEADERS"
},
{
"path": "cpp/test/capi/CMakeLists.txt",
"chars": 2287,
"preview": "set(CMAKE_C_STANDARD 11)\nset(CMAKE_C_STANDARD_REQUIRED ON)\n\n# Small wrapper library for the external sender\nSET(EXTERNAL"
},
{
"path": "cpp/test/capi/basic_tests.c",
"chars": 30655,
"preview": "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#ifdef _WIN32\n#define WIN32_LEAN_AND_MEAN\n#include <windows."
},
{
"path": "cpp/test/capi/external_sender_wrapper.cpp",
"chars": 3277,
"preview": "#include \"external_sender_wrapper.h\"\n\n#include <cstring>\n#include <vector>\n\n#include \"../external_sender.h\"\n\nnamespace {"
},
{
"path": "cpp/test/capi/external_sender_wrapper.h",
"chars": 1452,
"preview": "\n#include <dave/dave.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nDECLARE_OPAQUE_HANDLE(DAVEExternalSenderHandle);\n\nDAVE_"
},
{
"path": "cpp/test/capi/test_helpers.c",
"chars": 1103,
"preview": "#include \"test_helpers.h\"\n\n#include <stdlib.h>\n#include <string.h>\n\nstatic int HexDigitToValue(char c)\n{\n if (c >= '0"
},
{
"path": "cpp/test/capi/test_helpers.h",
"chars": 1636,
"preview": "#ifndef TEST_HELPERS_H\n#define TEST_HELPERS_H\n\n#include <stddef.h>\n#include <stdint.h>\n\n#define TEST_ASSERT(condition, m"
},
{
"path": "cpp/test/codec_utils_tests.cpp",
"chars": 14936,
"preview": "#include <vector>\n#include \"gtest/gtest.h\"\n\n#include <dave/array_view.h>\n\n#include \"codec_utils.h\"\n#include \"decryptor.h"
},
{
"path": "cpp/test/cryptor_manager_tests.cpp",
"chars": 8013,
"preview": "#include <gmock/gmock.h>\n#include <gtest/gtest.h>\n\n#include <bytes/bytes.h>\n#include <limits>\n\n#include \"common.h\"\n#incl"
},
{
"path": "cpp/test/cryptor_tests.cpp",
"chars": 5940,
"preview": "#include <gtest/gtest.h>\n\n#include \"decryptor.h\"\n#include \"encryptor.h\"\n#include \"frame_processors.h\"\n\n#include \"dave_te"
},
{
"path": "cpp/test/dave_test.cpp",
"chars": 569,
"preview": "#include \"dave_test.h\"\n\nnamespace discord {\nnamespace dave {\nnamespace test {\n\nstd::vector<uint8_t> GetBufferFromHex(con"
},
{
"path": "cpp/test/dave_test.h",
"chars": 352,
"preview": "\n#include \"gtest/gtest.h\"\n\n#include \"common.h\"\n\nnamespace discord {\nnamespace dave {\nnamespace test {\n\nstd::vector<uint8"
},
{
"path": "cpp/test/external_sender.cpp",
"chars": 2649,
"preview": "#include \"external_sender.h\"\n\n#include \"mls/parameters.h\"\n#include \"mls/util.h\"\n\nnamespace discord {\nnamespace dave {\nna"
},
{
"path": "cpp/test/external_sender.h",
"chars": 818,
"preview": "#pragma once\n\n#include <dave/version.h>\n#include <mls/messages.h>\n\nnamespace discord {\nnamespace dave {\nnamespace test {"
},
{
"path": "cpp/test/static_key_ratchet.cpp",
"chars": 1324,
"preview": "#include \"static_key_ratchet.h\"\n\n#include <algorithm>\n\n#include <bytes/bytes.h>\n#include <dave/logger.h>\n\n#include \"comm"
},
{
"path": "cpp/test/static_key_ratchet.h",
"chars": 643,
"preview": "#pragma once\n\n#include <string>\n\n#include <dave/dave_interfaces.h>\n\nnamespace discord {\nnamespace dave {\nnamespace test "
},
{
"path": "cpp/test/xssl_cryptor_tests.cpp",
"chars": 7901,
"preview": "#include \"gtest/gtest.h\"\n\n#include <bytes/bytes.h>\n\n#ifdef WITH_BORINGSSL\n#include \"boringssl_cryptor.h\"\n#else\n#include "
},
{
"path": "cpp/vcpkg-alts/boringssl/overlay-ports/mlspp/portfile.cmake",
"chars": 718,
"preview": "vcpkg_from_github(\n OUT_SOURCE_PATH SOURCE_PATH\n REPO cisco/mlspp\n REF \"${VERSION}\"\n SHA512 5d37631e2c47daae"
},
{
"path": "cpp/vcpkg-alts/boringssl/overlay-ports/mlspp/vcpkg.json",
"chars": 441,
"preview": "{\n \"name\": \"mlspp\",\n \"version-string\": \"1cc50a124a3bc4e143a787ec934280dc70c1034d\",\n \"description\": \"Cisco MLS C++ lib"
},
{
"path": "cpp/vcpkg-alts/boringssl/vcpkg.json",
"chars": 317,
"preview": "{\n \"name\": \"libdave\",\n \"license\": \"MIT\",\n \"dependencies\": [\n {\n \"name\": \"boringssl\",\n \"version>=\": \"2023"
},
{
"path": "cpp/vcpkg-alts/openssl_1.1/overlay-ports/mlspp/portfile.cmake",
"chars": 718,
"preview": "vcpkg_from_github(\n OUT_SOURCE_PATH SOURCE_PATH\n REPO cisco/mlspp\n REF \"${VERSION}\"\n SHA512 5d37631e2c47daae"
},
{
"path": "cpp/vcpkg-alts/openssl_1.1/overlay-ports/mlspp/vcpkg.json",
"chars": 539,
"preview": "{\n \"name\": \"mlspp\",\n \"version-string\": \"1cc50a124a3bc4e143a787ec934280dc70c1034d\",\n \"description\": \"Cisco MLS C++ lib"
},
{
"path": "cpp/vcpkg-alts/openssl_1.1/vcpkg.json",
"chars": 403,
"preview": "{\n \"name\": \"libdave\",\n \"license\": \"MIT\",\n \"dependencies\": [\n {\n \"name\": \"openssl\",\n \"version>=\": \"1.1.1n"
},
{
"path": "cpp/vcpkg-alts/openssl_3/overlay-ports/mlspp/portfile.cmake",
"chars": 717,
"preview": "vcpkg_from_github(\n OUT_SOURCE_PATH SOURCE_PATH\n REPO cisco/mlspp\n REF \"${VERSION}\"\n SHA512 5d37631e2c47daae"
},
{
"path": "cpp/vcpkg-alts/openssl_3/overlay-ports/mlspp/vcpkg.json",
"chars": 584,
"preview": "{\n \"name\": \"mlspp\",\n \"version-string\": \"1cc50a124a3bc4e143a787ec934280dc70c1034d\",\n \"description\": \"Cisco MLS C"
},
{
"path": "cpp/vcpkg-alts/openssl_3/vcpkg.json",
"chars": 394,
"preview": "{\n \"name\": \"libdave\",\n \"license\": \"MIT\",\n \"dependencies\": [\n {\n \"name\": \"openssl\",\n \"version>=\": \"3.0.7\""
},
{
"path": "cpp/vcpkg-alts/wasm/overlay-ports/mlspp/portfile.cmake",
"chars": 952,
"preview": "vcpkg_from_github(\n OUT_SOURCE_PATH SOURCE_PATH\n REPO cisco/mlspp\n REF \"${VERSION}\"\n SHA512 5d37631e2c47daae"
},
{
"path": "cpp/vcpkg-alts/wasm/overlay-ports/mlspp/vcpkg.json",
"chars": 433,
"preview": "{\n \"name\": \"mlspp\",\n \"version-string\": \"1cc50a124a3bc4e143a787ec934280dc70c1034d\",\n \"description\": \"Cisco MLS C++ lib"
},
{
"path": "cpp/vcpkg-alts/wasm/vcpkg.json",
"chars": 310,
"preview": "{\n \"name\": \"libdave\",\n \"license\": \"MIT\",\n \"dependencies\": [\n {\n \"name\": \"openssl\",\n \"version>=\": \"3.0.7\""
},
{
"path": "js/.gitignore",
"chars": 2073,
"preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Diagnostic reports"
},
{
"path": "js/.npmrc",
"chars": 22,
"preview": "node-linker = hoisted\n"
},
{
"path": "js/README.md",
"chars": 422,
"preview": "## libdave JS\n\nContains the package @discordapp/libdave. This is leveraged by Discord clients to enable out-of-band veri"
},
{
"path": "js/__tests__/DisplayableCode-test.ts",
"chars": 1126,
"preview": "import {describe, expect, test} from '@jest/globals';\nimport {generateDisplayableCode} from '../src/DisplayableCode';\n\nd"
},
{
"path": "js/__tests__/KeyFingerprint-test.ts",
"chars": 936,
"preview": "import {describe, expect, test} from '@jest/globals';\nimport {generateKeyFingerprint} from '../src/KeyFingerprint';\n\ndes"
},
{
"path": "js/__tests__/KeySerialization-test.ts",
"chars": 405,
"preview": "import {describe, expect, test} from '@jest/globals';\nimport {serializeKey} from '../src/KeySerialization';\n\ndescribe('K"
},
{
"path": "js/__tests__/PairwiseFingerprint-test.ts",
"chars": 1695,
"preview": "import {describe, expect, test} from '@jest/globals';\nimport {generatePairwiseFingerprint} from '../src/PairwiseFingerpr"
},
{
"path": "js/jest-setup.js",
"chars": 444,
"preview": "const crypto = require('crypto');\n\nfunction convertAlgorithm(name) {\n switch (name) {\n case 'SHA-512':\n return "
},
{
"path": "js/jest.config.js",
"chars": 437,
"preview": "/**\n * For a detailed explanation regarding each configuration property, visit:\n * https://jestjs.io/docs/configuration\n"
},
{
"path": "js/package.json",
"chars": 994,
"preview": "{\n \"name\": \"@discordapp/libdave\",\n \"description\": \"Discord's DAVE library for end-to-end encryption\",\n \"license\": \"MI"
},
{
"path": "js/src/DisplayableCode.ts",
"chars": 1052,
"preview": "const MAX_GROUP_SIZE = 8;\n\nexport function generateDisplayableCode(data: Uint8Array, desiredLength: number, groupSize: n"
},
{
"path": "js/src/KeyFingerprint.ts",
"chars": 787,
"preview": "const VERSION_LEN = 2;\nconst UID_LEN = 8;\n\nexport async function generateKeyFingerprint(version: number, key: Uint8Array"
},
{
"path": "js/src/KeySerialization.ts",
"chars": 129,
"preview": "import base64 from 'base64-js';\n\nexport function serializeKey(data: Uint8Array): string {\n return base64.fromByteArray("
},
{
"path": "js/src/PairwiseFingerprint.ts",
"chars": 1165,
"preview": "import {generateKeyFingerprint} from './KeyFingerprint';\nimport {scryptAsync} from '@noble/hashes/scrypt';\n\nconst salt ="
},
{
"path": "js/src/index.ts",
"chars": 232,
"preview": "export {generateDisplayableCode} from './DisplayableCode';\nexport {generateKeyFingerprint} from './KeyFingerprint';\nexpo"
},
{
"path": "js/src/wasm.ts",
"chars": 158,
"preview": "export {default as DaveModuleFactory} from '../wasm/libdave';\nexport type {MainModule as DaveModule} from '../wasm/libda"
},
{
"path": "js/tsconfig.json",
"chars": 601,
"preview": "{\n \"compilerOptions\": {\n \"esModuleInterop\": true,\n \"skipLibCheck\": true,\n \"target\": \"es2022\",\n \"allowJs\": t"
},
{
"path": "js/wasm/.gitignore",
"chars": 10,
"preview": "libdave.*\n"
},
{
"path": "samples/typescript/DaveSessionManager.ts",
"chars": 7839,
"preview": "import type {Session, TransientKeys, DaveModule, SignaturePrivateKey} from '@discordapp/libdave/wasm';\n\nconst MLS_NEW_GR"
},
{
"path": "samples/typescript/README.md",
"chars": 819,
"preview": "# TypeScript DAVE Session Manager\n\nThis directory contains an example implementation of a TypeScript class capable of ha"
}
]
About this extraction
This page contains the full source code of the discord/libdave GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 111 files (368.1 KB), approximately 93.4k tokens, and a symbol index with 411 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.