pub fn derive_wasm_participant_ssrc(call_id: &str, lid: &str, slot_word: u32) -> u32 {
let mut okm = [0u8; 4];
crate::crypto::hkdf_sha256_into(
call_id.as_bytes(),
Some(&slot_word.to_le_bytes()),
lid.as_bytes(),
&mut okm,
)
.expect("4 bytes within HKDF limit");
u32::from_le_bytes(okm)
}
pub const VIDEO_SSRC_SLOT_WORD: u32 = 2;
pub const APP_DATA_SSRC_SLOT_WORD: u32 = 6;
pub const HBH_FEC_TX_SSRC_SLOT_WORD: u32 = 7;
pub const HBH_FEC_RX_SSRC_SLOT_WORD: u32 = 8;
pub const WASM_RELAY_STREAM_SLOT_WORDS: [u32; 9] = [0, 1, 4, 2, 3, 5, 7, 8, 6];
pub fn derive_video_participant_ssrc(call_id: &str, lid: &str) -> u32 {
derive_wasm_participant_ssrc(call_id, lid, VIDEO_SSRC_SLOT_WORD)
}
pub fn derive_wasm_relay_stream_ssrcs(call_id: &str, lid: &str) -> [u32; 9] {
WASM_RELAY_STREAM_SLOT_WORDS.map(|slot| derive_wasm_participant_ssrc(call_id, lid, slot))
}
pub fn format_e2e_srtp_participant_id(jid: &str) -> String {
crate::voip::format_participant_id(jid)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::voip::testkat::kats;
#[test]
fn ssrc_matches_kat() {
let k = kats();
let call_id = k["inputs"]["callId"].as_str().unwrap();
let lid = k["inputs"]["peerLid"].as_str().unwrap();
assert_eq!(
derive_wasm_participant_ssrc(call_id, lid, 0) as u64,
k["voip_crypto"]["ssrc_slot0"].as_u64().unwrap()
);
assert_eq!(
derive_wasm_participant_ssrc(call_id, lid, 1) as u64,
k["voip_crypto"]["ssrc_slot1"].as_u64().unwrap()
);
}
#[test]
fn video_ssrc_differs_from_audio_ssrc() {
let audio = derive_wasm_participant_ssrc("CALL-ID-0001", "12345:0@lid", 0);
let video = derive_video_participant_ssrc("CALL-ID-0001", "12345:0@lid");
assert_ne!(audio, video);
assert_eq!(video, 0xf8d7_0484, "WhatsApp WASM video-slot KAT");
}
#[test]
fn relay_stream_bundle_uses_the_protocol_slot_order() {
assert_eq!(
WASM_RELAY_STREAM_SLOT_WORDS,
crate::voip::stun::wasm_stream_slot_words(),
"the descriptor builder zips these positionally; they must not drift"
);
let call_id = "CALL-ID-0001";
let participant = "12345:0@lid";
let bundle = derive_wasm_relay_stream_ssrcs(call_id, participant);
for (index, slot) in WASM_RELAY_STREAM_SLOT_WORDS.iter().copied().enumerate() {
assert_eq!(
bundle[index],
derive_wasm_participant_ssrc(call_id, participant, slot)
);
}
assert_eq!(
bundle[0],
derive_wasm_participant_ssrc(call_id, participant, 0)
);
assert_eq!(
bundle[3],
derive_video_participant_ssrc(call_id, participant)
);
}
#[test]
fn format_participant_id_rules() {
assert_eq!(format_e2e_srtp_participant_id("12345@lid"), "12345:0@lid");
assert_eq!(format_e2e_srtp_participant_id("12345:6@lid"), "12345:6@lid");
assert_eq!(
format_e2e_srtp_participant_id("12345@s.whatsapp.net"),
"12345@s.whatsapp.net"
);
}
}