diff --git a/lib/src/e2ee.worker/e2ee.cryptor.dart b/lib/src/e2ee.worker/e2ee.cryptor.dart index 6b61dc6..c3e6a15 100644 --- a/lib/src/e2ee.worker/e2ee.cryptor.dart +++ b/lib/src/e2ee.worker/e2ee.cryptor.dart @@ -120,13 +120,11 @@ enum CryptorError { class FrameInfo { FrameInfo({ - required this.mimeType, required this.ssrc, required this.timestamp, required this.buffer, required this.frameType, }); - String? mimeType; String frameType; int ssrc; int timestamp; @@ -144,7 +142,6 @@ class FrameCryptor { String? participantIdentity; String? trackId; String? codec; - String? mineType; ParticipantKeyHandler keyHandler; KeyOptions get keyOptions => keyHandler.keyOptions; late String kind; @@ -282,8 +279,7 @@ class FrameCryptor { data = obj.data.toDart.asUint8List(); if (obj.hasProperty('type'.toJS).toDart) { frameType = obj.type; - logger.finer( - 'frameType: $frameType, mineTye ${obj.getMetadata().mimeType}'); + logger.finer('frameType: $frameType'); } } @@ -318,7 +314,7 @@ class FrameCryptor { } FrameInfo readFrameInfo(JSObject frameObj) { - Uint8List buffer; + var buffer = Uint8List(0); var synchronizationSource = 0; var timestamp = 0; var frameType = ''; @@ -326,17 +322,25 @@ class FrameCryptor { buffer = frameObj.data.toDart.asUint8List(); if (frameObj.hasProperty('type'.toJS).toDart) { frameType = frameObj.type; - logger.finer( - 'frameType: $frameType, mineTye ${frameObj.getMetadata().mimeType}'); + logger.finer('frameType: $frameType'); } synchronizationSource = frameObj.getMetadata().synchronizationSource; - timestamp = frameObj.getMetadata().rtpTimestamp.toInt(); - mineType ??= frameObj.getMetadata().mimeType; + if (frameObj.getMetadata().hasProperty('rtpTimestamp'.toJS).toDart) { + timestamp = frameObj.getMetadata().rtpTimestamp.toInt(); + } else if (frameObj.hasProperty('timestamp'.toJS).toDart) { + timestamp = + (frameObj.getProperty('timestamp'.toJS) as JSNumber).toDartInt; + } } else if (frameObj is web.RTCEncodedAudioFrame) { buffer = frameObj.data.toDart.asUint8List(); synchronizationSource = frameObj.getMetadata().synchronizationSource; - timestamp = frameObj.getMetadata().rtpTimestamp.toInt(); - mineType ??= frameObj.getMetadata().mimeType; + + if (frameObj.getMetadata().hasProperty('rtpTimestamp'.toJS).toDart) { + timestamp = frameObj.getMetadata().rtpTimestamp.toInt(); + } else if (frameObj.hasProperty('timestamp'.toJS).toDart) { + timestamp = + (frameObj.getProperty('timestamp'.toJS) as JSNumber).toDartInt; + } frameType = 'audio'; } else { throw Exception( @@ -344,7 +348,6 @@ class FrameCryptor { } return FrameInfo( - mimeType: mineType, ssrc: synchronizationSource, timestamp: timestamp, buffer: buffer, @@ -366,43 +369,44 @@ class FrameCryptor { JSObject frameObj, web.TransformStreamDefaultController controller, ) async { - var srcFrame = readFrameInfo(frameObj); - - mineType ??= srcFrame.mimeType; - - logger.finer( - 'encodeFunction: frame ${srcFrame.buffer.length}, synchronizationSource ${srcFrame.ssrc} mineType $mineType frameType ${srcFrame.frameType}'); - - if (!enabled || - // skip for encryption for empty dtx frames - srcFrame.buffer.isEmpty) { - if (keyOptions.discardFrameWhenCryptorNotReady) { + try { + if (!enabled || + // skip for encryption for empty dtx frames + ((frameObj is web.RTCEncodedVideoFrame && + frameObj.data.toDart.lengthInBytes == 0) || + (frameObj is web.RTCEncodedAudioFrame && + frameObj.data.toDart.lengthInBytes == 0))) { + if (keyOptions.discardFrameWhenCryptorNotReady) { + return; + } + controller.enqueue(frameObj); return; } - controller.enqueue(frameObj); - return; - } - var secretKey = keyHandler.getKeySet(currentKeyIndex)?.encryptionKey; - var keyIndex = currentKeyIndex; + var srcFrame = readFrameInfo(frameObj); - if (secretKey == null) { - if (lastError != CryptorError.kMissingKey) { - lastError = CryptorError.kMissingKey; - postMessage({ - 'type': 'cryptorState', - 'msgType': 'event', - 'participantId': participantIdentity, - 'trackId': trackId, - 'kind': kind, - 'state': 'missingKey', - 'error': 'Missing key for track $trackId', - }); + logger.fine( + 'encodeFunction: buffer ${srcFrame.buffer.length}, synchronizationSource ${srcFrame.ssrc} frameType ${srcFrame.frameType}'); + + var secretKey = keyHandler.getKeySet(currentKeyIndex)?.encryptionKey; + var keyIndex = currentKeyIndex; + + if (secretKey == null) { + if (lastError != CryptorError.kMissingKey) { + lastError = CryptorError.kMissingKey; + postMessage({ + 'type': 'cryptorState', + 'msgType': 'event', + 'participantId': participantIdentity, + 'trackId': trackId, + 'kind': kind, + 'state': 'missingKey', + 'error': 'Missing key for track $trackId', + }); + } + return; } - return; - } - try { var headerLength = kind == 'video' ? getUnencryptedBytes(frameObj, codec) : 1; @@ -426,7 +430,7 @@ class FrameCryptor { .toDart as JSArrayBuffer; logger.finer( - 'encodeFunction, buffer: ${srcFrame.buffer.length}, cipherText: ${cipherText.toDart.asUint8List().length}'); + 'encodeFunction: encrypted buffer: ${srcFrame.buffer.length}, cipherText: ${cipherText.toDart.asUint8List().length}'); var finalBuffer = BytesBuilder(); finalBuffer @@ -436,8 +440,6 @@ class FrameCryptor { finalBuffer.add(frameTrailer.buffer.asUint8List()); enqueueFrame(frameObj, controller, finalBuffer); - logger.finer( - 'encodeFunction: ssrc ${srcFrame.ssrc} buffer length ${srcFrame.buffer.length}, encrypted: ${finalBuffer.length}, headerLength $headerLength cipherText ${cipherText.toDart.asUint8List().length} iv ${iv.length} frameTrailer ${frameTrailer.buffer.asUint8List().length}'); if (lastError != CryptorError.kOk) { lastError = CryptorError.kOk; @@ -453,7 +455,7 @@ class FrameCryptor { } logger.finer( - 'encrypto kind $kind,codec $codec headerLength: $headerLength, timestamp: ${srcFrame.timestamp}, ssrc: ${srcFrame.ssrc}, data length: ${srcFrame.buffer.length}, encrypted length: ${finalBuffer.toBytes().length}, iv $iv'); + 'encodeFunction[CryptorError.kOk]: frame enqueued kind $kind,codec $codec headerLength: $headerLength, timestamp: ${srcFrame.timestamp}, ssrc: ${srcFrame.ssrc}, data length: ${srcFrame.buffer.length}, encrypted length: ${finalBuffer.toBytes().length}, iv $iv'); } catch (e) { logger.warning('encodeFunction encrypt: e ${e.toString()}'); if (lastError != CryptorError.kEncryptError) { @@ -476,11 +478,9 @@ class FrameCryptor { web.TransformStreamDefaultController controller, ) async { var srcFrame = readFrameInfo(frameObj); - mineType ??= srcFrame.mimeType; var ratchetCount = 0; - logger.finer( - 'decodeFunction: frame ${srcFrame.buffer.length} mineType $mineType'); + logger.fine('decodeFunction: frame lenght ${srcFrame.buffer.length}'); ByteBuffer? decrypted; KeySet? initialKeySet; @@ -510,17 +510,18 @@ class FrameCryptor { if (sifGuard.isSifAllowed()) { var frameType = srcFrame.buffer.sublist(srcFrame.buffer.length - 1)[0]; - logger.finer('skip uncrypted frame, type $frameType'); + logger + .finer('ecodeFunction: skip uncrypted frame, type $frameType'); var finalBuffer = BytesBuilder(); finalBuffer.add(Uint8List.fromList(srcFrame.buffer .sublist(0, srcFrame.buffer.length - (magicBytes.length + 1)))); enqueueFrame(frameObj, controller, finalBuffer); - logger.fine('enqueing silent frame'); + logger.fine('ecodeFunction: enqueing silent frame'); controller.enqueue(frameObj); } else { - logger.finer('SIF limit reached, dropping frame'); + logger.finer('ecodeFunction: SIF limit reached, dropping frame'); } - logger.finer('enqueing silent frame'); + logger.finer('ecodeFunction: enqueing silent frame'); controller.enqueue(frameObj); return; } else { @@ -545,9 +546,6 @@ class FrameCryptor { logger.finer( 'decodeFunction: start decrypting frame headerLength $headerLength ${srcFrame.buffer.length} frameTrailer $frameTrailer, ivLength $ivLength, keyIndex $keyIndex, iv $iv'); - logger.finer( - 'decodeFunction: ssrc ${srcFrame.ssrc} buffer length ${srcFrame.buffer.length}, encrypted: ${srcFrame.buffer.length}, headerLength $headerLength cipherText ${srcFrame.buffer.sublist(headerLength, srcFrame.buffer.length - ivLength - 2).length} iv ${iv.length} frameTrailer ${frameTrailer.buffer.asUint8List().length}'); - /// missingKey flow: /// tries to decrypt once, fails, tries to ratchet once and decrypt again, /// fails (does not save ratcheted key), bumps _decryptionFailureCount, @@ -589,15 +587,16 @@ class FrameCryptor { .toDart) as JSArrayBuffer) .toDart; logger.finer( - 'decodeFunction: decryptFrameInternal: decrypted: ${decrypted!.asUint8List().length}'); + 'decodeFunction::decryptFrameInternal: decrypted: ${decrypted!.asUint8List().length}'); if (decrypted == null) { throw Exception('[decryptFrameInternal] could not decrypt'); } logger.finer( - 'decryptFrameInternal: decrypted: ${decrypted!.asUint8List().length}'); + 'decodeFunction::decryptFrameInternal: decrypted: ${decrypted!.asUint8List().length}'); if (currentkeySet != initialKeySet) { - logger.fine('ratchetKey: decryption ok, newState: kKeyRatcheted'); + logger.fine( + 'decodeFunction::decryptFrameInternal: ratchetKey: decryption ok, newState: kKeyRatcheted'); await keyHandler.setKeySetFromMaterial( currentkeySet, initialKeyIndex); } @@ -606,9 +605,9 @@ class FrameCryptor { lastError != CryptorError.kKeyRatcheted && ratchetCount > 0) { logger.finer( - 'KeyRatcheted: ssrc ${srcFrame.ssrc} timestamp ${srcFrame.timestamp} ratchetCount $ratchetCount participantId: $participantIdentity'); + 'decodeFunction::decryptFrameInternal: KeyRatcheted: ssrc ${srcFrame.ssrc} timestamp ${srcFrame.timestamp} ratchetCount $ratchetCount participantId: $participantIdentity'); logger.finer( - 'ratchetKey: lastError != CryptorError.kKeyRatcheted, reset state to kKeyRatcheted'); + 'decodeFunction::decryptFrameInternal: ratchetKey: lastError != CryptorError.kKeyRatcheted, reset state to kKeyRatcheted'); lastError = CryptorError.kKeyRatcheted; postMessage({ @@ -659,7 +658,7 @@ class FrameCryptor { keyHandler.decryptionSuccess(); logger.finer( - 'decodeFunction: buffer length ${srcFrame.buffer.length}, decrypted: ${decrypted!.asUint8List().length}'); + 'decodeFunction: decryption success, buffer length ${srcFrame.buffer.length}, decrypted: ${decrypted!.asUint8List().length}'); var finalBuffer = BytesBuilder(); @@ -681,8 +680,8 @@ class FrameCryptor { }); } - logger.finer( - 'decrypto kind $kind, codec $mineType headerLength: $headerLength, timestamp: ${srcFrame.timestamp}, ssrc: ${srcFrame.ssrc}, data length: ${srcFrame.buffer.length}, decrypted length: ${finalBuffer.toBytes().length}, keyindex $keyIndex iv $iv'); + logger.fine( + 'decodeFunction[CryptorError.kOk]: decryption success kind $kind, headerLength: $headerLength, timestamp: ${srcFrame.timestamp}, ssrc: ${srcFrame.ssrc}, data length: ${srcFrame.buffer.length}, decrypted length: ${finalBuffer.toBytes().length}, keyindex $keyIndex iv $iv'); } catch (e) { if (lastError != CryptorError.kDecryptError) { lastError = CryptorError.kDecryptError; diff --git a/lib/src/e2ee.worker/e2ee.worker.dart b/lib/src/e2ee.worker/e2ee.worker.dart index 987703a..729390f 100644 --- a/lib/src/e2ee.worker/e2ee.worker.dart +++ b/lib/src/e2ee.worker/e2ee.worker.dart @@ -68,7 +68,7 @@ void main() async { var kind = options.getProperty('kind'.toJS) as JSString; var participantId = options.getProperty('participantId'.toJS) as JSString; var trackId = options.getProperty('trackId'.toJS) as JSString; - var codec = options.getProperty('codec'.toJS) as JSString; + var codec = options.getProperty('codec'.toJS) as JSString?; var msgType = options.getProperty('msgType'.toJS) as JSString; var keyProviderId = options.getProperty('keyProviderId'.toJS) as JSString; @@ -88,7 +88,7 @@ void main() async { writable: transformer.writable, trackId: trackId.toDart, kind: kind.toDart, - codec: codec.toDart); + codec: codec?.toDart); }.toJS; }