NIF bindings for the DAVE (Discord Audio Video E2EE) MLS session.
Wraps the davey Rust crate which implements the MLS (RFC 9420) key
exchange protocol used by Discord's DAVE protocol.
The NIF is downloaded precompiled when EDA compiles, from the GitHub release of EDA's version, and verified against the checksums shipped in the package. Binaries exist for Linux (x86-64, ARM64, ARMv7, RISC-V; glibc and musl), macOS, Windows and FreeBSD, so no Rust toolchain is needed.
Building from source
On another platform, or without network access at compile time, build it from source. That needs Rust and Rustler:
{:rustler, "~> 0.35"}
config :rustler_precompiled, :force_build, eda: trueWithout the NIF
If the NIF can be neither downloaded nor built, EDA still compiles, with a warning, and this
module keeps its stubs: every function raises :nif_not_loaded and available?/0 returns false.
Voice is then connected without offering DAVE, which Discord refuses with close code 4017 —
it has required DAVE since March 2026 for DMs, group DMs, voice channels and Go Live; only Stage
channels are exempt.
Summary
Functions
Returns true if the NIF is loaded and available.
Returns true if the user's decryptor has an active passthrough (epoch transition grace period).
Creates and returns the client's MLS key package as {:ok, binary}.
Decrypts a DAVE-encrypted audio packet. Returns {:ok, decrypted_binary}.
Encrypts an Opus audio packet using DAVE E2EE.
Returns the current MLS epoch number.
Returns the maximum DAVE protocol version supported by the davey crate.
Creates a new MLS session for the given protocol version, user ID, and channel ID.
Processes an MLS commit from the gateway.
Processes MLS proposals from the gateway.
Processes an MLS welcome message from the gateway.
Returns the DAVE protocol version of this session.
Returns true if the MLS session is ready (group established).
Re-initializes the session with new parameters without creating a new resource.
Leaves the MLS group and clears the session's key storage, keeping the external sender.
Sets the external sender credential (from the voice gateway).
Sets passthrough mode on every decryptor: while it is on, unencrypted frames are accepted.
Returns the session status: :inactive, :pending, :awaiting_response, or :active.
Functions
@spec available?() :: boolean()
Returns true if the NIF is loaded and available.
@spec can_passthrough?(reference(), non_neg_integer()) :: {:ok, boolean()} | {:error, atom()} | boolean()
Returns true if the user's decryptor has an active passthrough (epoch transition grace period).
@spec create_key_package(reference()) :: {:ok, {:ok, binary()}} | {:ok, binary()} | {:error, atom()}
Creates and returns the client's MLS key package as {:ok, binary}.
@spec decrypt_audio(reference(), non_neg_integer(), binary()) :: {:ok, {:ok, binary()}} | {:ok, binary()} | {:error, atom()}
Decrypts a DAVE-encrypted audio packet. Returns {:ok, decrypted_binary}.
@spec encrypt_opus(reference(), binary()) :: {:ok, {:ok, binary()}} | {:ok, binary()} | {:error, :not_ready | :encryption_failed | :error}
Encrypts an Opus audio packet using DAVE E2EE.
Returns {:ok, encrypted_binary} or {:error, :not_ready | :encryption_failed | :error}.
@spec get_epoch(reference()) :: {:ok, non_neg_integer()} | {:error, atom()} | non_neg_integer()
Returns the current MLS epoch number.
@spec max_protocol_version() :: pos_integer()
Returns the maximum DAVE protocol version supported by the davey crate.
@spec new_session(pos_integer(), non_neg_integer(), non_neg_integer()) :: {:ok, reference()} | {:error, atom()} | reference() | no_return()
Creates a new MLS session for the given protocol version, user ID, and channel ID.
@spec process_commit(reference(), binary()) :: :ok | :error | {:error, :no_group | :pending_group | :invalid}
Processes an MLS commit from the gateway.
A failure names its cause: :no_group or :pending_group when the session has not joined a group
the commit could apply to, :invalid otherwise.
@spec process_proposals(reference(), :append | :revoke, binary(), [non_neg_integer()]) :: {:ok, {:ok, binary(), binary() | nil}} | {:ok, binary(), binary() | nil} | {:error, atom()}
Processes MLS proposals from the gateway.
operation_type is :append or :revoke.
user_ids is a list of connected client user IDs (integers).
Returns {:ok, commit_binary, welcome_binary_or_nil}.
@spec process_welcome(reference(), binary()) :: :ok | :error | {:error, :already_in_group | :no_external_sender | :invalid}
Processes an MLS welcome message from the gateway.
A failure names its cause: :already_in_group when the session had already joined through a
commit of its own — the expected outcome of a commit race — :no_external_sender, or :invalid.
@spec protocol_version(reference()) :: {:ok, pos_integer()} | {:error, atom()} | pos_integer()
Returns the DAVE protocol version of this session.
Returns true if the MLS session is ready (group established).
@spec reinit(reference(), pos_integer(), non_neg_integer(), non_neg_integer()) :: :ok | :error
Re-initializes the session with new parameters without creating a new resource.
@spec reset(reference()) :: :ok | :error
Leaves the MLS group and clears the session's key storage, keeping the external sender.
It does not prepare a new pending group, so a session that must commit again needs reinit/4.
Sets the external sender credential (from the voice gateway).
@spec set_passthrough_mode(reference(), boolean(), non_neg_integer()) :: :ok | :error
Sets passthrough mode on every decryptor: while it is on, unencrypted frames are accepted.
Turning it on is immediate; transition_expiry is how many seconds decryptors keep accepting
unencrypted frames once it is turned off again.
@spec status(reference()) :: {:ok, :inactive | :pending | :awaiting_response | :active} | {:error, atom()} | :inactive | :pending | :awaiting_response | :active
Returns the session status: :inactive, :pending, :awaiting_response, or :active.