### Initialize and Update vcpkg Submodule Source: https://github.com/discord/libdave/blob/main/cpp/README.md This snippet shows the commands to ensure the vcpkg submodule is up-to-date and initialized. It's a prerequisite for building the libdave library using vcpkg. ```bash git submodule update --recursive ./vcpkg/bootstrap-vcpkg.sh ``` -------------------------------- ### Encrypt and Decrypt Media Frames in C++ Source: https://context7.com/discord/libdave/llms.txt Demonstrates how to initialize libdave encryptor and decryptor instances, manage key ratchets, and perform secure media frame processing. It includes steps for assigning codecs, handling encryption/decryption buffers, and retrieving operational statistics. ```cpp #include #include #include void process_media_frames(discord::dave::mls::ISession* session) { auto encryptor = discord::dave::CreateEncryptor(); auto decryptor = discord::dave::CreateDecryptor(); std::string selfUserId = "111222333444555666"; auto selfKeyRatchet = session->GetKeyRatchet(selfUserId); encryptor->SetKeyRatchet(std::move(selfKeyRatchet)); std::string remoteUserId = "222333444555666777"; auto remoteKeyRatchet = session->GetKeyRatchet(remoteUserId); decryptor->TransitionToKeyRatchet(std::move(remoteKeyRatchet)); uint32_t audioSsrc = 12345; encryptor->AssignSsrcToCodec(audioSsrc, discord::dave::Codec::Opus); encryptor->SetProtocolVersionChangedCallback([]() { std::cout << "Protocol version changed" << std::endl; }); std::vector audioFrame(960); size_t maxCiphertext = encryptor->GetMaxCiphertextByteSize( discord::dave::MediaType::Audio, audioFrame.size() ); std::vector encryptedFrame(maxCiphertext); size_t bytesWritten = 0; auto encryptResult = encryptor->Encrypt( discord::dave::MediaType::Audio, audioSsrc, discord::dave::MakeArrayView(audioFrame.data(), audioFrame.size()), discord::dave::MakeArrayView(encryptedFrame.data(), encryptedFrame.size()), &bytesWritten ); if (encryptResult == discord::dave::IEncryptor::ResultCode::Success) { encryptedFrame.resize(bytesWritten); } std::vector incomingEncrypted = {/* ... from RTP ... */}; size_t maxPlaintext = decryptor->GetMaxPlaintextByteSize( discord::dave::MediaType::Audio, incomingEncrypted.size() ); std::vector decryptedFrame(maxPlaintext); bytesWritten = 0; auto decryptResult = decryptor->Decrypt( discord::dave::MediaType::Audio, discord::dave::MakeArrayView( incomingEncrypted.data(), incomingEncrypted.size()), discord::dave::MakeArrayView(decryptedFrame.data(), decryptedFrame.size()), &bytesWritten ); if (decryptResult == discord::dave::IDecryptor::ResultCode::Success) { decryptedFrame.resize(bytesWritten); } auto encStats = encryptor->GetStats(discord::dave::MediaType::Audio); auto decStats = decryptor->GetStats(discord::dave::MediaType::Audio); std::cout << "Encrypted: " << encStats.encryptSuccessCount << ", Decrypted: " << decStats.decryptSuccessCount << std::endl; } ``` -------------------------------- ### C++ Session Management with libdave Source: https://context7.com/discord/libdave/llms.txt Demonstrates RAII-based session management using the C++ libdave API. It covers creating a session with a failure callback, initializing it for a group, obtaining a key package, processing welcome messages to build a roster, handling commit messages for membership changes, and retrieving pairwise fingerprints for verification. Dependencies include `` and ``. ```cpp #include #include #include #include int main() { // Create MLS session with failure callback auto session = discord::dave::mls::CreateSession( nullptr, // KeyPairContextType (platform-specific) "auth-session-id", [](const std::string& source, const std::string& reason) { std::cerr << "MLS failure in " << source << ": " << reason << std::endl; } ); // Initialize for group discord::dave::ProtocolVersion version = discord::dave::MaxSupportedProtocolVersion(); uint64_t groupId = 123456789012345678ULL; std::string selfUserId = "111222333444555666"; std::shared_ptr transientKey; session->Init(version, groupId, selfUserId, transientKey); // Get key package for Voice Gateway auto keyPackage = session->GetMarshalledKeyPackage(); // Send to gateway... // Process welcome message std::vector welcomeData = {/* ... from gateway ... */}; std::set recognizedUsers = {"111222333444555666", "222333444555666777"}; auto roster = session->ProcessWelcome(welcomeData, recognizedUsers); if (roster) { for (const auto& [userId, signature] : *roster) { std::cout << "User " << userId << " joined with signature size: " << signature.size() << std::endl; } } // Process commit for membership changes std::vector commitData = {/* ... from gateway ... */}; auto commitResult = session->ProcessCommit(commitData); if (std::holds_alternative(commitResult)) { std::cerr << "Commit processing failed - need to reset" session->Reset(); } else if (std::holds_alternative(commitResult)) { std::cout << "Commit ignored (duplicate or out-of-order)" << std::endl; } else { auto& rosterUpdate = std::get(commitResult); for (const auto& [userId, signature] : rosterUpdate) { if (signature.empty()) { std::cout << "User " << userId << " left" << std::endl; } else { std::cout << "User " << userId << " key updated" << std::endl; } } } // Get pairwise fingerprint for verification session->GetPairwiseFingerprint(version, "222333444555666777", [](const std::vector& fingerprint) { std::cout << "Fingerprint: "; for (auto byte : fingerprint) { std::cout << std::hex << (int)byte; } std::cout << std::endl; } ); return 0; } ``` -------------------------------- ### Project and Language Configuration (CMake) Source: https://github.com/discord/libdave/blob/main/cpp/CMakeLists.txt Initializes the CMake build system, sets the project name, version, and supported languages (C and C++). It also defines various build options that can be toggled by the user. ```cmake 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) ``` -------------------------------- ### Manage MLS sessions with C API Source: https://context7.com/discord/libdave/llms.txt Illustrates the lifecycle of an MLS session, including creation, initialization, processing welcome messages, and generating security fingerprints. It requires manual memory management for buffers returned by the library. ```c #include #include #include void onMLSFailure(const char* source, const char* reason, void* userData) { printf("MLS failure in %s: %s\n", source, reason); } void onPairwiseFingerprint(const uint8_t* fingerprint, size_t length, void* userData) { printf("Fingerprint received, length: %zu\n", length); // Display fingerprint for user verification } int main() { // Create session DAVESessionHandle session = daveSessionCreate( NULL, // context "auth-session", // authSessionId for key persistence onMLSFailure, // failure callback NULL // userData ); if (!session) { printf("Failed to create session\n"); return 1; } // Initialize session for a group uint16_t protocolVersion = 1; uint64_t groupId = 123456789012345678ULL; const char* selfUserId = "111222333444555666"; daveSessionInit(session, protocolVersion, groupId, selfUserId); // Get key package to send to Voice Gateway uint8_t* keyPackage; size_t keyPackageLength; daveSessionGetMarshalledKeyPackage(session, &keyPackage, &keyPackageLength); // Send keyPackage to gateway... daveFree(keyPackage); // Process welcome message from gateway const char* recognizedUserIds[] = {"111222333444555666", "222333444555666777"}; uint8_t welcomeData[] = {/* ... from gateway ... */}; DAVEWelcomeResultHandle welcomeResult = daveSessionProcessWelcome( session, welcomeData, sizeof(welcomeData), recognizedUserIds, 2 // number of recognized users ); if (welcomeResult) { uint64_t* rosterIds; size_t rosterLength; daveWelcomeResultGetRosterMemberIds(welcomeResult, &rosterIds, &rosterLength); printf("Joined group with %zu members\n", rosterLength); daveFree(rosterIds); daveWelcomeResultDestroy(welcomeResult); } // Compute pairwise fingerprint for verification daveSessionGetPairwiseFingerprint( session, protocolVersion, "222333444555666777", // remote user ID onPairwiseFingerprint, NULL ); // Get epoch authenticator uint8_t* authenticator; size_t authLength; daveSessionGetLastEpochAuthenticator(session, &authenticator, &authLength); // Use authenticator for verification... daveFree(authenticator); // Cleanup daveSessionDestroy(session); return 0; } ``` -------------------------------- ### Compile libdave as a Static Library Source: https://github.com/discord/libdave/blob/main/cpp/README.md These commands compile the libdave C++ library as a static library. It first cleans previous builds and then proceeds with the compilation. ```bash make cclean make ``` -------------------------------- ### Decrypt media frames with Decryptor class Source: https://context7.com/discord/libdave/llms.txt Demonstrates how to initialize the DAVE module, transition between key ratchets, and perform in-place decryption of media frames. It handles memory allocation via WASM heap and supports passthrough mode. ```typescript import { DaveModuleFactory, type DaveModule } from '@discordapp/libdave/wasm'; const dave: DaveModule = await DaveModuleFactory(); // Create decryptor for incoming media const decryptor = new dave.Decryptor(); // Transition to key ratchet for a remote user const remoteUserId = '222333444555666777'; const keyRatchet = session.GetKeyRatchet(remoteUserId); decryptor.TransitionToKeyRatchet(keyRatchet); // Decrypt incoming frame (in-place decryption) const encryptedFrame = new Uint8Array([/* ... received RTP payload ... */]); const maxPlaintext = decryptor.GetMaxPlaintextByteSize( dave.MediaType.Audio, encryptedFrame.length ); const frameBuffer = dave._malloc(Math.max(encryptedFrame.length, maxPlaintext)); new Uint8Array(dave.HEAPU8.buffer, frameBuffer, encryptedFrame.length).set(encryptedFrame); const bytesWritten = decryptor.Decrypt( dave.MediaType.Audio, frameBuffer, encryptedFrame.length, maxPlaintext ); if (bytesWritten > 0) { const decryptedFrame = new Uint8Array( dave.HEAPU8.buffer.slice(frameBuffer, frameBuffer + bytesWritten) ); // Process decrypted audio frame } dave._free(frameBuffer); // Enable passthrough mode (no decryption expected) decryptor.TransitionToPassthroughMode(true); ``` -------------------------------- ### Configure and Build Test Executable with CMake Source: https://github.com/discord/libdave/blob/main/cpp/test/CMakeLists.txt This script sets up the GTest environment, compiles the test executable from source files, and links required dependencies including MLSPP and GTest. It also includes platform-specific logic to copy shared libraries and ASAN runtime DLLs on Windows. ```cmake enable_testing() find_package(GTest CONFIG REQUIRED) SET(TEST_APP_NAME "libdave_test") file(GLOB_RECURSE TEST_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.h") file(GLOB_RECURSE TEST_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") add_executable(${TEST_APP_NAME} ${TEST_HEADERS} ${TEST_SOURCES}) add_dependencies(${TEST_APP_NAME} ${LIB_NAME}) target_include_directories(${TEST_APP_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/src) target_link_libraries(libdave_test PRIVATE ${LIB_NAME} GTest::gtest_main GTest::gmock MLSPP::bytes MLSPP::mlspp) add_test(NAME ${TEST_APP_NAME} COMMAND ${TEST_APP_NAME}) if(WIN32 AND BUILD_SHARED_LIBS) add_custom_command(TARGET ${TEST_APP_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ COMMENT "Copying ${LIB_NAME}.dll to test directory" ) endif() if(WIN32 AND ENABLE_SANITIZERS) if (NOT EXISTS "${ASAN_RUNTIME_DLL}") message(FATAL_ERROR "ASAN runtime DLL not found at ${ASAN_RUNTIME_DLL}") endif() add_custom_command(TARGET ${TEST_APP_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${ASAN_RUNTIME_DLL}" $ COMMENT "Copying ASAN runtime DLL to test directory" ) endif() add_subdirectory(capi) ``` -------------------------------- ### Source and Header File Discovery (CMake) Source: https://github.com/discord/libdave/blob/main/cpp/CMakeLists.txt Defines the library name and uses `file(GLOB_RECURSE)` to find all header and source files within the `src/` and `includes/` directories. This is used to automatically include files in the build target. ```cmake 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") ``` -------------------------------- ### Compile libdave as a Shared Library Source: https://github.com/discord/libdave/blob/main/cpp/README.md These commands compile the libdave C++ library as a shared library. It first cleans previous builds and then proceeds with the compilation for a shared library. ```bash make cclean make shared ``` -------------------------------- ### Manage MLS Group State with Session Class (TypeScript) Source: https://context7.com/discord/libdave/llms.txt The Session class manages the MLS group state for DAVE encrypted calls. It handles joining groups via welcome messages, processing commits for membership changes, and deriving encryption keys for each participant. Initialization requires WASM module, context, authSessionId, and an MLS failure callback. It can process welcome messages and retrieve key ratchets. ```typescript import { DaveModuleFactory, type DaveModule } from '@discordapp/libdave/wasm'; // Initialize the WASM module const dave: DaveModule = await DaveModuleFactory(); // Create a new session with MLS failure callback const session = new dave.Session( '', // context (platform-specific, usually empty) '', // authSessionId (for persistent key storage, usually empty) (source: string, reason: string) => { console.error(`MLS failure in ${source}: ${reason}`); } ); // Initialize for a specific group const protocolVersion = 1; const groupId = BigInt('123456789012345678'); const selfUserId = '111222333444555666'; const transientKeys = new dave.TransientKeys(); const privateKey = transientKeys.GetTransientPrivateKey(protocolVersion); session.Init(protocolVersion, groupId, selfUserId, privateKey); // Set external sender from Voice Gateway (Opcode 25) const externalSenderPackage = new Uint8Array([/* ... from gateway ... */]); session.SetExternalSender(externalSenderPackage); // Get key package to send to gateway (Opcode 26) const keyPackage = session.GetMarshalledKeyPackage(); // Send keyPackage to Voice Gateway // Process welcome message when joining (Opcode 30) const welcomeMessage = new Uint8Array([/* ... from gateway ... */]); const recognizedUserIds = ['111222333444555666', '222333444555666777']; const roster = session.ProcessWelcome(welcomeMessage, recognizedUserIds); if (roster) { console.log('Joined group with roster:', roster); } // Get key ratchet for a user's media encryption const keyRatchet = session.GetKeyRatchet('222333444555666777'); // Get epoch authenticator for verification const authenticator = session.GetLastEpochAuthenticator(); ``` -------------------------------- ### Dependency Configuration (CMake) Source: https://github.com/discord/libdave/blob/main/cpp/CMakeLists.txt Finds and configures dependencies for OpenSSL, BoringSSL, and nlohmann_json. It checks for specific OpenSSL versions and defines preprocessor macros based on found libraries. ```cmake 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 "^#[ ]*define[ ]+OPENSSL_VERSION_TEXT[ ]+\"OpenSSL ([0-9])+\\.([0-9])+\\.([0-9])+ .+") string(REGEX REPLACE "^.*OPENSSL_VERSION_TEXT[ ]+\"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) ``` -------------------------------- ### Convert Binary Data to Displayable Code Source: https://context7.com/discord/libdave/llms.txt Converts raw binary fingerprint data into a formatted numeric string. This utility uses modular arithmetic to group digits, facilitating easier visual or verbal comparison. ```typescript import { generateDisplayableCode } from '@discordapp/libdave'; const fingerprintData = new Uint8Array([0xaa, 0xbb, 0xcc, 0xdd, 0xee]); const code = generateDisplayableCode(fingerprintData, 5, 5); console.log(code); ``` -------------------------------- ### Encrypt Media Frames with libdave C API Source: https://context7.com/discord/libdave/llms.txt Handles real-time encryption of audio and video frames using the libdave Encryptor API. It involves creating an encryptor, setting a key ratchet, assigning codecs to SSRCs, and encrypting frame data. The function returns the encrypted frame and provides statistics on the encryption process. Dependencies include the 'dave/dave.h' header. ```c #include #include int encrypt_media_frame() { // Create encryptor DAVEEncryptorHandle encryptor = daveEncryptorCreate(); // Get key ratchet from session for self DAVEKeyRatchetHandle keyRatchet = daveSessionGetKeyRatchet(session, selfUserId); daveEncryptorSetKeyRatchet(encryptor, keyRatchet); // Map SSRCs to codecs uint32_t audioSsrc = 12345; uint32_t videoSsrc = 67890; daveEncryptorAssignSsrcToCodec(encryptor, audioSsrc, DAVE_CODEC_OPUS); daveEncryptorAssignSsrcToCodec(encryptor, videoSsrc, DAVE_CODEC_VP8); // Encrypt an audio frame uint8_t audioFrame[960]; // Opus frame data size_t frameSize = sizeof(audioFrame); // Calculate required output buffer size size_t maxCiphertext = daveEncryptorGetMaxCiphertextByteSize( encryptor, DAVE_MEDIA_TYPE_AUDIO, frameSize ); uint8_t* encryptedFrame = malloc(maxCiphertext); size_t bytesWritten = 0; DAVEEncryptorResultCode result = daveEncryptorEncrypt( encryptor, DAVE_MEDIA_TYPE_AUDIO, audioSsrc, audioFrame, frameSize, encryptedFrame, maxCiphertext, &bytesWritten ); if (result == DAVE_ENCRYPTOR_RESULT_CODE_SUCCESS) { // Send encryptedFrame (bytesWritten bytes) over RTP printf("Encrypted %zu bytes to %zu bytes\n", frameSize, bytesWritten); } else { printf("Encryption failed with code: %d\n", result); } // Get encryption statistics DAVEEncryptorStats stats; daveEncryptorGetStats(encryptor, DAVE_MEDIA_TYPE_AUDIO, &stats); printf("Encrypted %llu frames, %llu failures\n", stats.encryptSuccessCount, stats.encryptFailureCount); // Cleanup free(encryptedFrame); daveKeyRatchetDestroy(keyRatchet); daveEncryptorDestroy(encryptor); return 0; } ``` -------------------------------- ### Decrypt Media Frames with libdave C API Source: https://context7.com/discord/libdave/llms.txt Handles real-time decryption of incoming encrypted audio and video frames using the libdave Decryptor API. It involves creating a decryptor, obtaining a key ratchet for the remote user, and decrypting the frame. The function handles various decryption outcomes, including success, decryption failure, missing key ratchets, and invalid nonces, and provides decryption statistics. Dependencies include the 'dave/dave.h' header. ```c #include #include int decrypt_media_frame() { // Create decryptor DAVEDecryptorHandle decryptor = daveDecryptorCreate(); // Get key ratchet for the remote user const char* remoteUserId = "222333444555666777"; DAVEKeyRatchetHandle keyRatchet = daveSessionGetKeyRatchet(session, remoteUserId); // Transition to new key ratchet (handles gradual key rotation) daveDecryptorTransitionToKeyRatchet(decryptor, keyRatchet); // Decrypt an incoming frame uint8_t encryptedFrame[1024]; // Received from RTP size_t encryptedSize = /* ... actual received size ... */ 100; // Calculate required output buffer size size_t maxPlaintext = daveDecryptorGetMaxPlaintextByteSize( decryptor, DAVE_MEDIA_TYPE_AUDIO, encryptedSize ); uint8_t* decryptedFrame = malloc(maxPlaintext); size_t bytesWritten = 0; DAVEDecryptorResultCode result = daveDecryptorDecrypt( decryptor, DAVE_MEDIA_TYPE_AUDIO, encryptedFrame, encryptedSize, decryptedFrame, maxPlaintext, &bytesWritten ); switch (result) { case DAVE_DECRYPTOR_RESULT_CODE_SUCCESS: printf("Decrypted %zu bytes\n", bytesWritten); // Process decryptedFrame with audio decoder break; case DAVE_DECRYPTOR_RESULT_CODE_DECRYPTION_FAILURE: printf("Decryption failed - invalid ciphertext\n"); break; case DAVE_DECRYPTOR_RESULT_CODE_MISSING_KEY_RATCHET: printf("No key ratchet available\n"); break; case DAVE_DECRYPTOR_RESULT_CODE_INVALID_NONCE: printf("Invalid nonce - possible replay attack\n"); break; default: printf("Decryption error: %d\n", result); } // Get decryption statistics DAVEDecryptorStats stats; daveDecryptorGetStats(decryptor, DAVE_MEDIA_TYPE_AUDIO, &stats); printf("Decrypted %llu frames, %llu failures, %llu invalid nonces\n", stats.decryptSuccessCount, stats.decryptFailureCount, stats.decryptInvalidNonceCount); // Cleanup free(decryptedFrame); daveKeyRatchetDestroy(keyRatchet); daveDecryptorDestroy(decryptor); return 0; } ``` -------------------------------- ### Generate Pairwise Fingerprint and Verification Code Source: https://context7.com/discord/libdave/llms.txt Generates a secure fingerprint for identity verification between two users using MLS public keys. The resulting binary fingerprint is converted into a human-readable numeric string for out-of-band verification. ```typescript import { generatePairwiseFingerprint, generateDisplayableCode } from '@discordapp/libdave'; const myPublicKey = new Uint8Array(33); const theirPublicKey = new Uint8Array(65); const myUserId = '123456789012345678'; const theirUserId = '987654321098765432'; const fingerprint = await generatePairwiseFingerprint( 0, myPublicKey, myUserId, theirPublicKey, theirUserId ); const verificationCode = generateDisplayableCode(fingerprint, 30, 5); console.log('Verification code:', verificationCode); ``` -------------------------------- ### Serialize Keys for Transmission Source: https://context7.com/discord/libdave/llms.txt Serializes binary key data into a base64-encoded string. This is essential for transmitting key packages over the Discord Voice Gateway or storing keys in a persistent format. ```typescript import { serializeKey } from '@discordapp/libdave'; const publicKey = new Uint8Array([0x04, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde]); const serialized = serializeKey(publicKey); console.log(serialized); ``` -------------------------------- ### Implement DAVE Protocol Session Management in TypeScript Source: https://context7.com/discord/libdave/llms.txt The DaveSessionManager class manages MLS sessions, processes Voice Gateway opcodes, and handles user key ratchets. It requires the @discordapp/libdave WASM module to perform cryptographic operations and session state transitions. ```typescript import type { Session, TransientKeys, DaveModule, SignaturePrivateKey } from '@discordapp/libdave/wasm'; const MLS_NEW_GROUP_EXPECTED_EPOCH = '1'; const DAVE_PROTOCOL_INIT_TRANSITION_ID = 0; export class DaveSessionManager { private readonly dave: DaveModule; private readonly transientKeys: TransientKeys | null; private readonly mlsSession: Session; private readonly selfUserId: string; private readonly groupId: string; private readonly recognizedUserIds: Set = new Set(); private readonly daveProtocolTransitions: Map = new Map(); private latestPreparedTransitionVersion: number = 0; constructor(dave: DaveModule, transientKeys: TransientKeys | null, selfUserId: string, groupId: string) { this.dave = dave; this.transientKeys = transientKeys; this.selfUserId = selfUserId; this.groupId = groupId; this.mlsSession = new dave.Session('', '', (source: string, reason: string) => { console.error(`MLS failure: ${source} ${reason}`); }); } public onSelectProtocolAck(protocolVersion: number) { if (protocolVersion > 0) { this._handleDaveProtocolPrepareEpoch(MLS_NEW_GROUP_EXPECTED_EPOCH, protocolVersion); this._sendMLSKeyPackage(); } else { this._prepareDaveProtocolRatchets(DAVE_PROTOCOL_INIT_TRANSITION_ID, protocolVersion); this._handleDaveProtocolExecuteTransition(DAVE_PROTOCOL_INIT_TRANSITION_ID); } } public onDaveProtocolPrepareTransition(transitionId: number, protocolVersion: number) { this._prepareDaveProtocolRatchets(transitionId, protocolVersion); } public onDaveProtocolMLSExternalSenderPackage(externalSenderPackage: ArrayBuffer) { this.mlsSession.SetExternalSender(externalSenderPackage); } public onMLSProposals(proposals: ArrayBuffer) { const commitWelcome = this.mlsSession.ProcessProposals(proposals, this._getRecognizedUserIDs()); } public onMLSWelcome(transitionId: number, welcome: ArrayBuffer) { const roster = this.mlsSession.ProcessWelcome(welcome, this._getRecognizedUserIDs()); if (roster) { this._prepareDaveProtocolRatchets(transitionId, this.mlsSession.GetProtocolVersion()); } else { this._sendMLSKeyPackage(); } } public createUser(userId: string) { this.recognizedUserIds.add(userId); const keyRatchet = this.mlsSession.GetKeyRatchet(userId); } public destroyUser(userId: string) { this.recognizedUserIds.delete(userId); } private _handleDaveProtocolPrepareEpoch(epoch: string, protocolVersion: number): void { if (epoch === MLS_NEW_GROUP_EXPECTED_EPOCH) { let privateKey: SignaturePrivateKey | null = null; if (this.transientKeys) { privateKey = this.transientKeys.GetTransientPrivateKey(protocolVersion); } this.mlsSession.Init(protocolVersion, BigInt(this.groupId), this.selfUserId, privateKey); } } private _prepareDaveProtocolRatchets(transitionId: number, protocolVersion: number): void { for (const userId of this._getRecognizedUserIDs()) { if (userId !== this.selfUserId) { const keyRatchet = this.mlsSession.GetKeyRatchet(userId); } } this.daveProtocolTransitions.set(transitionId, protocolVersion); this.latestPreparedTransitionVersion = protocolVersion; } private _getRecognizedUserIDs(): string[] { return Array.from(this.recognizedUserIds).concat([this.selfUserId]); } private _sendMLSKeyPackage() { const keyPackage = this.mlsSession.GetMarshalledKeyPackage(); } } ``` -------------------------------- ### Encrypt Outgoing Media Frames with Encryptor Class (TypeScript) Source: https://context7.com/discord/libdave/llms.txt The Encryptor class encrypts outgoing media frames using the DAVE protocol. It supports multiple codecs and handles key ratcheting automatically. It requires WASM module initialization and a key ratchet from the Session. The class can assign SSRCs to codecs, encrypt frames, and listen for protocol version changes. Passthrough mode is available for disabling encryption. ```typescript import { DaveModuleFactory, type DaveModule } from '@discordapp/libdave/wasm'; const dave: DaveModule = await DaveModuleFactory(); // Create encryptor for outgoing media const encryptor = new dave.Encryptor(); // Set key ratchet from session const keyRatchet = session.GetKeyRatchet(selfUserId); // Assuming 'session' and 'selfUserId' are defined from previous snippet encryptor.SetKeyRatchet(keyRatchet); // Configure SSRC to codec mapping const audioSsrc = 12345; const videoSsrc = 67890; encryptor.AssignSsrcToCodec(audioSsrc, dave.Codec.Opus); encryptor.AssignSsrcToCodec(videoSsrc, dave.Codec.VP8); // Encrypt audio frame (in-place encryption) const audioFrame = new Uint8Array(960); // Opus frame const frameCapacity = encryptor.GetMaxCiphertextByteSize(dave.MediaType.Audio, audioFrame.length); // Allocate buffer in WASM memory const frameBuffer = dave._malloc(frameCapacity); new Uint8Array(dave.HEAPU8.buffer, frameBuffer, audioFrame.length).set(audioFrame); const bytesWritten = encryptor.Encrypt( dave.MediaType.Audio, audioSsrc, frameBuffer, audioFrame.length, frameCapacity ); if (bytesWritten > 0) { const encryptedFrame = new Uint8Array(dave.HEAPU8.buffer, frameBuffer, bytesWritten); // Send encryptedFrame over RTP } dave._free(frameBuffer); // Listen for protocol version changesencryptor.SetProtocolVersionChangedCallback(() => { console.log('Protocol version changed to:', encryptor.GetProtocolVersion()); }); // Enable passthrough mode (no encryption)encryptor.SetPassthroughMode(true); ``` -------------------------------- ### Compiler Flag Configuration (CMake) Source: https://github.com/discord/libdave/blob/main/cpp/CMakeLists.txt Configures C++ standard to C++17 and sets compiler flags based on the detected compiler (Clang, GNU, or MSVC). It enables strict warning levels and error checking. ```cmake 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() ``` -------------------------------- ### Sanitizer Configuration (CMake) Source: https://github.com/discord/libdave/blob/main/cpp/CMakeLists.txt Enables address and undefined behavior sanitizers for Debug builds when the ENABLE_SANITIZERS option is set. It configures specific flags for Clang/GNU and MSVC compilers. ```cmake 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() ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.