Add E2EE diagnostic logging for debugging call encryption
All checks were successful
Android Kernel Build / build (push) Successful in 19m17s
All checks were successful
Android Kernel Build / build (push) Successful in 19m17s
- Enable diag file for all builds (was DEBUG-only) - Add native frame count + bad streak query methods (JNI) - Add periodic E2EE-HEALTH log with enc/dec frame counts - Reduce scan receivers spam (only log on state change) - Log E2EE state on call connected - Log when attachSender/attachReceiver skips due to missing key
This commit is contained in:
@@ -531,6 +531,7 @@ public:
|
||||
}
|
||||
|
||||
uint32_t KeyFingerprint() const { return key_fingerprint_; }
|
||||
int FrameCount() const { return diag_count_.load(std::memory_order_relaxed); }
|
||||
|
||||
/* ── RefCountInterface ─────────────────────────────────────── */
|
||||
void AddRef() const override {
|
||||
@@ -724,6 +725,8 @@ public:
|
||||
}
|
||||
|
||||
uint32_t KeyFingerprint() const { return key_fingerprint_; }
|
||||
int FrameCount() const { return diag_count_.load(std::memory_order_relaxed); }
|
||||
uint32_t BadStreak() const { return bad_audio_streak_.load(std::memory_order_relaxed); }
|
||||
|
||||
/* ── RefCountInterface ─────────────────────────────────────── */
|
||||
void AddRef() const override {
|
||||
@@ -1121,4 +1124,33 @@ Java_com_rosetta_messenger_network_XChaCha20E2EE_nativeCloseDiagFile(
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Query frame counts for health checks ────────────────────── */
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_rosetta_messenger_network_XChaCha20E2EE_nativeGetEncryptorFrameCount(
|
||||
JNIEnv *, jclass, jlong ptr)
|
||||
{
|
||||
if (ptr == 0) return -1;
|
||||
auto *enc = reinterpret_cast<XChaCha20Encryptor *>(ptr);
|
||||
return enc->FrameCount();
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_rosetta_messenger_network_XChaCha20E2EE_nativeGetDecryptorFrameCount(
|
||||
JNIEnv *, jclass, jlong ptr)
|
||||
{
|
||||
if (ptr == 0) return -1;
|
||||
auto *dec = reinterpret_cast<XChaCha20Decryptor *>(ptr);
|
||||
return dec->FrameCount();
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_rosetta_messenger_network_XChaCha20E2EE_nativeGetDecryptorBadStreak(
|
||||
JNIEnv *, jclass, jlong ptr)
|
||||
{
|
||||
if (ptr == 0) return -1;
|
||||
auto *dec = reinterpret_cast<XChaCha20Decryptor *>(ptr);
|
||||
return dec->BadStreak();
|
||||
}
|
||||
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -736,6 +736,9 @@ object CallManager {
|
||||
|
||||
private fun onCallConnected() {
|
||||
appContext?.let { CallSoundManager.play(it, CallSoundManager.CallSound.CONNECTED) }
|
||||
val keyFp = sharedKeyBytes?.fingerprintHex(8) ?: "null"
|
||||
breadcrumb("CONNECTED: e2eeAvail=$e2eeAvailable keyFp=$keyFp sEnc=${senderEncryptors.size} rDec=${receiverDecryptors.size} nativeLoaded=${XChaCha20E2EE.nativeLoaded}")
|
||||
breadcrumbState("onCallConnected")
|
||||
updateState { it.copy(phase = CallPhase.ACTIVE, statusText = "Call active") }
|
||||
durationJob?.cancel()
|
||||
durationJob =
|
||||
@@ -832,6 +835,9 @@ object CallManager {
|
||||
emitCallAttachmentIfNeeded(snapshot)
|
||||
resetRtcObjects()
|
||||
e2eeAvailable = true
|
||||
lastScanLog = ""
|
||||
lastHealthLog = ""
|
||||
healthLogCount = 0
|
||||
role = null
|
||||
roomId = ""
|
||||
offerSent = false
|
||||
@@ -893,14 +899,15 @@ object CallManager {
|
||||
}
|
||||
sharedKeyBytes = keyBytes.copyOf(32)
|
||||
breadcrumb("KE: shared key fp=${sharedKeyBytes?.fingerprintHex(8)}")
|
||||
// Frame-level diagnostics are enabled only for debug builds.
|
||||
if (BuildConfig.DEBUG) {
|
||||
// Enable frame-level diagnostics for all builds (needed for debugging E2EE issues).
|
||||
try {
|
||||
val dir = java.io.File(appContext!!.filesDir, "crash_reports")
|
||||
if (!dir.exists()) dir.mkdirs()
|
||||
val diagPath = java.io.File(dir, "e2ee_diag.txt").absolutePath
|
||||
XChaCha20E2EE.nativeOpenDiagFile(diagPath)
|
||||
} catch (_: Throwable) {}
|
||||
breadcrumb("E2EE: diag file opened at $diagPath")
|
||||
} catch (t: Throwable) {
|
||||
breadcrumb("E2EE: diag file open FAILED: ${t.message}")
|
||||
}
|
||||
// If sender track already exists, bind encryptor now.
|
||||
val existingSender =
|
||||
@@ -916,8 +923,13 @@ object CallManager {
|
||||
Log.i(TAG, "E2EE key ready (XChaCha20)")
|
||||
}
|
||||
|
||||
private var lastHealthLog = ""
|
||||
private var healthLogCount = 0
|
||||
|
||||
private fun startE2EERebindLoopIfNeeded() {
|
||||
if (e2eeRebindJob?.isActive == true) return
|
||||
healthLogCount = 0
|
||||
lastHealthLog = ""
|
||||
e2eeRebindJob =
|
||||
scope.launch {
|
||||
while (true) {
|
||||
@@ -934,9 +946,29 @@ object CallManager {
|
||||
attachSenderE2EE(sender)
|
||||
}
|
||||
attachReceiverE2EEFromPeerConnection()
|
||||
// Periodic health check: log frame counts from native
|
||||
healthLogCount++
|
||||
if (healthLogCount % 4 == 0) { // every ~6s
|
||||
logE2EEHealth()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun logE2EEHealth() {
|
||||
val encFrames = senderEncryptors.values.firstOrNull()?.frameCount() ?: -1
|
||||
val decFrames = receiverDecryptors.values.firstOrNull()?.frameCount() ?: -1
|
||||
val badStreak = receiverDecryptors.values.firstOrNull()?.badStreak() ?: -1
|
||||
val keyFp = sharedKeyBytes?.fingerprintHex(8) ?: "null"
|
||||
val health = "enc=$encFrames dec=$decFrames bad=$badStreak keyFp=$keyFp sEnc=${senderEncryptors.size} rDec=${receiverDecryptors.size}"
|
||||
// Only log if state changed or every 5th iteration to avoid spam
|
||||
if (health != lastHealthLog || healthLogCount % 20 == 0) {
|
||||
breadcrumb("E2EE-HEALTH: $health")
|
||||
lastHealthLog = health
|
||||
}
|
||||
}
|
||||
|
||||
private var lastScanLog = ""
|
||||
|
||||
private fun attachReceiverE2EEFromPeerConnection() {
|
||||
val pc = peerConnection ?: return
|
||||
@@ -956,7 +988,12 @@ object CallManager {
|
||||
fromTransceivers++
|
||||
}
|
||||
}
|
||||
breadcrumb("E2EE: scan receivers attached recv=$fromReceivers tx=$fromTransceivers totalMap=${receiverDecryptors.size}")
|
||||
val hasKey = sharedKeyBytes != null
|
||||
val scanLog = "recv=$fromReceivers tx=$fromTransceivers totalMap=${receiverDecryptors.size} hasKey=$hasKey e2eeAvail=$e2eeAvailable"
|
||||
if (scanLog != lastScanLog) {
|
||||
breadcrumb("E2EE: scan receivers attached $scanLog")
|
||||
lastScanLog = scanLog
|
||||
}
|
||||
}.onFailure {
|
||||
breadcrumb("E2EE: attachReceiverE2EEFromPeerConnection failed: ${it.message}")
|
||||
}
|
||||
@@ -1096,8 +1133,15 @@ object CallManager {
|
||||
}
|
||||
|
||||
private fun attachSenderE2EE(sender: RtpSender?) {
|
||||
if (!e2eeAvailable) return
|
||||
val key = sharedKeyBytes ?: return
|
||||
if (!e2eeAvailable) {
|
||||
breadcrumb("E2EE: attachSender SKIP — e2eeAvailable=false")
|
||||
return
|
||||
}
|
||||
val key = sharedKeyBytes
|
||||
if (key == null) {
|
||||
breadcrumb("E2EE: attachSender SKIP — sharedKeyBytes=null")
|
||||
return
|
||||
}
|
||||
if (sender == null) return
|
||||
val mapKey = senderMapKey(sender)
|
||||
val existing = senderEncryptors[mapKey]
|
||||
@@ -1134,8 +1178,15 @@ object CallManager {
|
||||
}
|
||||
|
||||
private fun attachReceiverE2EE(receiver: RtpReceiver?) {
|
||||
if (!e2eeAvailable) return
|
||||
val key = sharedKeyBytes ?: return
|
||||
if (!e2eeAvailable) {
|
||||
breadcrumb("E2EE: attachReceiver SKIP — e2eeAvailable=false")
|
||||
return
|
||||
}
|
||||
val key = sharedKeyBytes
|
||||
if (key == null) {
|
||||
breadcrumb("E2EE: attachReceiver SKIP — sharedKeyBytes=null")
|
||||
return
|
||||
}
|
||||
if (receiver == null) return
|
||||
val mapKey = receiverMapKey(receiver)
|
||||
val existing = receiverDecryptors[mapKey]
|
||||
|
||||
@@ -72,6 +72,8 @@ object XChaCha20E2EE {
|
||||
|
||||
override fun getNativeFrameEncryptor(): Long = nativePtr
|
||||
|
||||
fun frameCount(): Int = if (nativePtr != 0L) nativeGetEncryptorFrameCount(nativePtr) else -1
|
||||
|
||||
fun dispose() {
|
||||
if (nativePtr != 0L) nativeReleaseEncryptor(nativePtr)
|
||||
}
|
||||
@@ -89,6 +91,9 @@ object XChaCha20E2EE {
|
||||
|
||||
override fun getNativeFrameDecryptor(): Long = nativePtr
|
||||
|
||||
fun frameCount(): Int = if (nativePtr != 0L) nativeGetDecryptorFrameCount(nativePtr) else -1
|
||||
fun badStreak(): Int = if (nativePtr != 0L) nativeGetDecryptorBadStreak(nativePtr) else -1
|
||||
|
||||
fun dispose() {
|
||||
if (nativePtr != 0L) nativeReleaseDecryptor(nativePtr)
|
||||
}
|
||||
@@ -99,8 +104,11 @@ object XChaCha20E2EE {
|
||||
@JvmStatic private external fun nativeHSalsa20(rawDh: ByteArray): ByteArray
|
||||
@JvmStatic private external fun nativeCreateEncryptor(key: ByteArray): Long
|
||||
@JvmStatic private external fun nativeReleaseEncryptor(ptr: Long)
|
||||
@JvmStatic private external fun nativeGetEncryptorFrameCount(ptr: Long): Int
|
||||
@JvmStatic private external fun nativeCreateDecryptor(key: ByteArray): Long
|
||||
@JvmStatic private external fun nativeReleaseDecryptor(ptr: Long)
|
||||
@JvmStatic private external fun nativeGetDecryptorFrameCount(ptr: Long): Int
|
||||
@JvmStatic private external fun nativeGetDecryptorBadStreak(ptr: Long): Int
|
||||
@JvmStatic private external fun nativeInstallCrashHandler(path: String)
|
||||
@JvmStatic external fun nativeOpenDiagFile(path: String)
|
||||
@JvmStatic external fun nativeCloseDiagFile()
|
||||
|
||||
Reference in New Issue
Block a user