///|
fn media_type_code(media_type : MediaType) -> Int {
match media_type {
Audio => 0
Video => 1
}
}
///|
fn codec_code(codec : Codec) -> Int {
match codec {
Opus => 1
Vp8 => 2
Vp9 => 3
H264 => 4
H265 => 5
Av1 => 6
}
}
///|
fn Codec::media_type(self : Codec) -> MediaType {
match self {
Opus => Audio
Vp8 | Vp9 | H264 | H265 | Av1 => Video
}
}
///|
fn encrypt_failure(code : Int) -> EncryptFailure {
match code {
1 => EncryptionFailure
2 => MissingKeyRatchet
3 => MissingCryptor
4 => TooManyAttempts
_ => EncryptFailure::Unknown(code~)
}
}
///|
fn decrypt_failure(code : Int) -> DecryptFailure {
match code {
1 => DecryptionFailure
2 => MissingKeyRatchet
3 => InvalidNonce
4 => MissingCryptor
_ => DecryptFailure::Unknown(code~)
}
}
///|
fn check_media_configuration_status(
operation : String,
status : Int,
) -> Unit raise DaveError {
if status != 0 {
raise NativeFailure(operation~, reason="native status \{status}")
}
}
///|
/// A stateful media-frame encryptor.
///
/// Assign each SSRC to its codec before encrypting frames for that SSRC. The
/// native handle is released by a finalizer; copied `Encryptor` values alias
/// the same mutable state and must not be used concurrently.
pub struct Encryptor {
priv raw : @ffi.RawEncryptor
priv codecs : Map[UInt, Codec]
}
///|
/// Create a media-frame encryptor.
pub fn Encryptor::new() -> Encryptor raise DaveError {
let operation = "Encryptor::new"
require_available(operation)
let raw = @ffi.RawEncryptor::new()
if !raw.is_valid() {
raise NativeFailure(operation~, reason="failed to create a DAVE encryptor")
}
Encryptor::{ raw, codecs: Map([]), }
}
///|
/// Install a copy of `key_ratchet` for outbound media.
pub fn Encryptor::set_key_ratchet(
self : Encryptor,
key_ratchet : KeyRatchet,
) -> Unit raise DaveError {
self.raw.set_key_ratchet(key_ratchet.raw)
check_media_configuration_status(
"Encryptor::set_key_ratchet",
self.raw.last_status(),
)
}
///|
/// Enable or disable unencrypted passthrough.
pub fn Encryptor::set_passthrough_mode(
self : Encryptor,
enabled~ : Bool,
) -> Unit raise DaveError {
self.raw.set_passthrough(enabled~)
check_media_configuration_status(
"Encryptor::set_passthrough_mode",
self.raw.last_status(),
)
}
///|
/// Associate an RTP SSRC with its media codec.
pub fn Encryptor::assign_ssrc_to_codec(
self : Encryptor,
ssrc~ : UInt,
codec~ : Codec,
) -> Unit raise DaveError {
self.raw.assign_ssrc(ssrc~, codec=codec_code(codec))
check_media_configuration_status(
"Encryptor::assign_ssrc_to_codec",
self.raw.last_status(),
)
self.codecs[ssrc] = codec
}
///|
/// Return the protocol version selected by the active outbound ratchet.
pub fn Encryptor::protocol_version(self : Encryptor) -> UInt16 {
self.raw.protocol_version()
}
///|
/// Whether an outbound key ratchet has been installed.
pub fn Encryptor::has_key_ratchet(self : Encryptor) -> Bool {
self.raw.has_key()
}
///|
/// Whether outbound media is currently passed through unencrypted.
pub fn Encryptor::is_passthrough_mode(self : Encryptor) -> Bool {
self.raw.is_passthrough()
}
///|
/// Encrypt one media frame and return a new MoonBit-owned byte string.
///
/// SSRC/codec assignment is required while encryption is active. Native
/// passthrough mode accepts frames without an assignment, matching libdave.
pub fn Encryptor::encrypt(
self : Encryptor,
media_type~ : MediaType,
ssrc~ : UInt,
frame : Bytes,
) -> Bytes raise DaveError {
if !self.raw.is_passthrough() {
guard self.codecs.get(ssrc) is Some(codec) else {
raise InvalidState(
operation="Encryptor::encrypt",
reason="SSRC \{ssrc} has no assigned codec",
)
}
if codec.media_type() != media_type {
raise InvalidArgument(
operation="Encryptor::encrypt",
reason="the assigned codec does not match the requested media type",
)
}
}
let result = self.raw.encrypt(
media_type=media_type_code(media_type),
ssrc~,
frame,
)
let status = self.raw.last_status()
if status < 0 {
raise NativeFailure(
operation="Encryptor::encrypt",
reason="native status \{status}",
)
}
if status > 0 {
raise EncryptFailed(reason=encrypt_failure(status))
}
result
}
///|
/// A stateful media-frame decryptor.
///
/// Applications normally keep one decryptor per remote participant. The
/// native handle is released by a finalizer; copied `Decryptor` values alias
/// the same mutable state and must not be used concurrently.
pub struct Decryptor {
priv raw : @ffi.RawDecryptor
}
///|
/// Create a media-frame decryptor.
pub fn Decryptor::new() -> Decryptor raise DaveError {
let operation = "Decryptor::new"
require_available(operation)
let raw = @ffi.RawDecryptor::new()
if !raw.is_valid() {
raise NativeFailure(operation~, reason="failed to create a DAVE decryptor")
}
Decryptor::{ raw, }
}
///|
/// Begin libdave's transition to a copy of `key_ratchet`.
///
/// libdave retains old decryption keys for its built-in transition window.
pub fn Decryptor::transition_to_key_ratchet(
self : Decryptor,
key_ratchet : KeyRatchet,
) -> Unit raise DaveError {
self.raw.transition_to_key_ratchet(key_ratchet.raw)
check_media_configuration_status(
"Decryptor::transition_to_key_ratchet",
self.raw.last_status(),
)
}
///|
/// Transition to or from unencrypted passthrough.
pub fn Decryptor::transition_to_passthrough_mode(
self : Decryptor,
enabled~ : Bool,
) -> Unit raise DaveError {
self.raw.transition_to_passthrough(enabled~)
check_media_configuration_status(
"Decryptor::transition_to_passthrough_mode",
self.raw.last_status(),
)
}
///|
/// Decrypt one media frame and return a new MoonBit-owned byte string.
pub fn Decryptor::decrypt(
self : Decryptor,
media_type~ : MediaType,
encrypted_frame : Bytes,
) -> Bytes raise DaveError {
let result = self.raw.decrypt(
media_type=media_type_code(media_type),
encrypted_frame,
)
let status = self.raw.last_status()
if status < 0 {
raise NativeFailure(
operation="Decryptor::decrypt",
reason="native status \{status}",
)
}
if status > 0 {
raise DecryptFailed(reason=decrypt_failure(status))
}
result
}