Enable End-to-End Encrypted Audio Payloads.
This change integrates the FrameDecryptorInterface and the FrameEncryptorInterface into
the audio media path. If a FrameEncryptorInterface is set on an outgoing audio RTPSender
then each outgoing audio payload will first pass through the provided FrameEncryptor which
will have a chance to modify the payload contents for the purposes of encryption.
If a FrameDecryptorInterface is set on an incoming audio RtpReceiver then each incoming
audio payload will first pass through the provided FrameDecryptor which have a chance to
modify the payload contents for the purpose of decryption.
While AEAD is supported by the FrameDecryptor/FrameEncryptor interfaces this CL does not
use it and so it is left as null.
Bug: webrtc:9681
Change-Id: Ic383a9dce280528739f9d271357c2220e0a0dccf
Reviewed-on: https://webrtc-review.googlesource.com/c/101702
Commit-Queue: Benjamin Wright <benwright@webrtc.org>
Reviewed-by: Fredrik Solenberg <solenberg@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Reviewed-by: Emad Omara <emadomara@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#25001}
diff --git a/api/BUILD.gn b/api/BUILD.gn
index 007879b..5b21334 100644
--- a/api/BUILD.gn
+++ b/api/BUILD.gn
@@ -446,6 +446,23 @@
]
}
+ rtc_source_set("fake_frame_crypto") {
+ testonly = true
+ sources = [
+ "test/fake_frame_decryptor.cc",
+ "test/fake_frame_decryptor.h",
+ "test/fake_frame_encryptor.cc",
+ "test/fake_frame_encryptor.h",
+ ]
+ deps = [
+ ":array_view",
+ ":libjingle_peerconnection_api",
+ "..:webrtc_common",
+ "../rtc_base:checks",
+ "../rtc_base:rtc_base_approved",
+ ]
+ }
+
rtc_source_set("mock_peerconnectioninterface") {
testonly = true
sources = [
diff --git a/api/test/fake_frame_decryptor.cc b/api/test/fake_frame_decryptor.cc
new file mode 100644
index 0000000..432664a
--- /dev/null
+++ b/api/test/fake_frame_decryptor.cc
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2018 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "api/test/fake_frame_decryptor.h"
+#include <vector>
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+
+FakeFrameDecryptor::FakeFrameDecryptor(uint8_t fake_key,
+ uint8_t expected_postfix_byte)
+ : fake_key_(fake_key), expected_postfix_byte_(expected_postfix_byte) {}
+
+int FakeFrameDecryptor::Decrypt(cricket::MediaType media_type,
+ const std::vector<uint32_t>& csrcs,
+ rtc::ArrayView<const uint8_t> additional_data,
+ rtc::ArrayView<const uint8_t> encrypted_frame,
+ rtc::ArrayView<uint8_t> frame,
+ size_t* bytes_written) {
+ if (fail_decryption_) {
+ return 1;
+ }
+
+ RTC_CHECK_EQ(frame.size() + 1, encrypted_frame.size());
+ for (size_t i = 0; i < frame.size(); i++) {
+ frame[i] ^= fake_key_;
+ }
+
+ if (encrypted_frame[frame.size()] != expected_postfix_byte_) {
+ return 1;
+ }
+
+ return 0;
+}
+
+size_t FakeFrameDecryptor::GetMaxPlaintextByteSize(
+ cricket::MediaType media_type,
+ size_t encrypted_frame_size) {
+ return encrypted_frame_size - 1;
+}
+
+void FakeFrameDecryptor::SetFakeKey(uint8_t fake_key) {
+ fake_key_ = fake_key;
+}
+
+uint8_t FakeFrameDecryptor::GetFakeKey() const {
+ return fake_key_;
+}
+
+void FakeFrameDecryptor::SetExpectedPostfixByte(uint8_t expected_postfix_byte) {
+ expected_postfix_byte_ = expected_postfix_byte;
+}
+
+uint8_t FakeFrameDecryptor::GetExpectedPostfixByte() const {
+ return expected_postfix_byte_;
+}
+
+void FakeFrameDecryptor::SetFailDecryption(bool fail_decryption) {
+ fail_decryption_ = fail_decryption;
+}
+
+} // namespace webrtc
diff --git a/api/test/fake_frame_decryptor.h b/api/test/fake_frame_decryptor.h
new file mode 100644
index 0000000..b945def
--- /dev/null
+++ b/api/test/fake_frame_decryptor.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2018 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef API_TEST_FAKE_FRAME_DECRYPTOR_H_
+#define API_TEST_FAKE_FRAME_DECRYPTOR_H_
+
+#include <vector>
+
+#include "api/crypto/framedecryptorinterface.h"
+#include "rtc_base/refcountedobject.h"
+
+namespace webrtc {
+
+// The FakeFrameDecryptor is a TEST ONLY fake implementation of the
+// FrameDecryptorInterface. It is constructed with a simple single digit key and
+// a fixed postfix byte. This is just to validate that the core code works
+// as expected.
+class FakeFrameDecryptor
+ : public rtc::RefCountedObject<FrameDecryptorInterface> {
+ public:
+ // Provide a key (0,255) and some postfix byte (0,255) this should match the
+ // byte you expect from the FakeFrameEncryptor.
+ explicit FakeFrameDecryptor(uint8_t fake_key = 1,
+ uint8_t expected_postfix_byte = 255);
+
+ // FrameDecryptorInterface implementation
+ int Decrypt(cricket::MediaType media_type,
+ const std::vector<uint32_t>& csrcs,
+ rtc::ArrayView<const uint8_t> additional_data,
+ rtc::ArrayView<const uint8_t> encrypted_frame,
+ rtc::ArrayView<uint8_t> frame,
+ size_t* bytes_written) override;
+
+ size_t GetMaxPlaintextByteSize(cricket::MediaType media_type,
+ size_t encrypted_frame_size) override;
+
+ void SetFakeKey(uint8_t fake_key);
+
+ uint8_t GetFakeKey() const;
+
+ void SetExpectedPostfixByte(uint8_t expected_postfix_byte);
+
+ uint8_t GetExpectedPostfixByte() const;
+
+ void SetFailDecryption(bool fail_decryption);
+
+ private:
+ uint8_t fake_key_ = 0;
+ uint8_t expected_postfix_byte_ = 0;
+ bool fail_decryption_ = false;
+};
+
+} // namespace webrtc
+
+#endif // API_TEST_FAKE_FRAME_DECRYPTOR_H_
diff --git a/api/test/fake_frame_encryptor.cc b/api/test/fake_frame_encryptor.cc
new file mode 100644
index 0000000..013058f
--- /dev/null
+++ b/api/test/fake_frame_encryptor.cc
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2018 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "api/test/fake_frame_encryptor.h"
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+FakeFrameEncryptor::FakeFrameEncryptor(uint8_t fake_key, uint8_t postfix_byte)
+ : fake_key_(fake_key), postfix_byte_(postfix_byte) {}
+
+// FrameEncryptorInterface implementation
+int FakeFrameEncryptor::Encrypt(cricket::MediaType media_type,
+ uint32_t ssrc,
+ rtc::ArrayView<const uint8_t> additional_data,
+ rtc::ArrayView<const uint8_t> frame,
+ rtc::ArrayView<uint8_t> encrypted_frame,
+ size_t* bytes_written) {
+ // Useful if you want to test failure cases.
+ if (fail_encryption_) {
+ return 1;
+ }
+
+ RTC_CHECK_EQ(frame.size() + 1, encrypted_frame.size());
+ for (size_t i = 0; i < frame.size(); i++) {
+ encrypted_frame[i] ^= fake_key_;
+ }
+ encrypted_frame[frame.size()] = postfix_byte_;
+ *bytes_written = encrypted_frame.size();
+ return 0;
+}
+
+size_t FakeFrameEncryptor::GetMaxCiphertextByteSize(
+ cricket::MediaType media_type,
+ size_t frame_size) {
+ return frame_size + 1;
+}
+
+void FakeFrameEncryptor::SetFakeKey(uint8_t fake_key) {
+ fake_key_ = fake_key;
+}
+
+uint8_t FakeFrameEncryptor::GetFakeKey() const {
+ return fake_key_;
+}
+
+void FakeFrameEncryptor::SetPostfixByte(uint8_t postfix_byte) {
+ postfix_byte_ = postfix_byte;
+}
+
+uint8_t FakeFrameEncryptor::GetPostfixByte() const {
+ return postfix_byte_;
+}
+
+void FakeFrameEncryptor::SetFailEncryption(bool fail_encryption) {
+ fail_encryption_ = fail_encryption;
+}
+
+} // namespace webrtc
diff --git a/api/test/fake_frame_encryptor.h b/api/test/fake_frame_encryptor.h
new file mode 100644
index 0000000..61ae938
--- /dev/null
+++ b/api/test/fake_frame_encryptor.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2018 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef API_TEST_FAKE_FRAME_ENCRYPTOR_H_
+#define API_TEST_FAKE_FRAME_ENCRYPTOR_H_
+
+#include "api/crypto/frameencryptorinterface.h"
+#include "rtc_base/refcountedobject.h"
+
+namespace webrtc {
+
+// The FakeFrameEncryptor is a TEST ONLY fake implementation of the
+// FrameEncryptorInterface. It is constructed with a simple single digit key and
+// a fixed postfix byte. This is just to validate that the core code works
+// as expected.
+class FakeFrameEncryptor
+ : public rtc::RefCountedObject<FrameEncryptorInterface> {
+ public:
+ // Provide a key (0,255) and some postfix byte (0,255).
+ explicit FakeFrameEncryptor(uint8_t fake_key = 1, uint8_t postfix_byte = 255);
+
+ // FrameEncryptorInterface implementation
+ int Encrypt(cricket::MediaType media_type,
+ uint32_t ssrc,
+ rtc::ArrayView<const uint8_t> additional_data,
+ rtc::ArrayView<const uint8_t> frame,
+ rtc::ArrayView<uint8_t> encrypted_frame,
+ size_t* bytes_written) override;
+
+ size_t GetMaxCiphertextByteSize(cricket::MediaType media_type,
+ size_t frame_size) override;
+
+ void SetFakeKey(uint8_t fake_key);
+
+ uint8_t GetFakeKey() const;
+
+ void SetPostfixByte(uint8_t expected_postfix_byte);
+
+ uint8_t GetPostfixByte() const;
+
+ void SetFailEncryption(bool fail_encryption);
+
+ private:
+ uint8_t fake_key_ = 0;
+ uint8_t postfix_byte_ = 0;
+ bool fail_encryption_ = false;
+};
+
+} // namespace webrtc
+
+#endif // API_TEST_FAKE_FRAME_ENCRYPTOR_H_
diff --git a/audio/audio_receive_stream.cc b/audio/audio_receive_stream.cc
index b9faea5..7e473ed 100644
--- a/audio/audio_receive_stream.cc
+++ b/audio/audio_receive_stream.cc
@@ -76,7 +76,7 @@
nullptr /* RtcpRttStats */, event_log, config.rtp.remote_ssrc,
config.jitter_buffer_max_packets,
config.jitter_buffer_fast_accelerate, config.decoder_factory,
- config.codec_pair_id));
+ config.codec_pair_id, config.frame_decryptor));
}
} // namespace
diff --git a/audio/audio_send_stream.cc b/audio/audio_send_stream.cc
index 06be261..e82264a 100644
--- a/audio/audio_send_stream.cc
+++ b/audio/audio_send_stream.cc
@@ -50,10 +50,12 @@
rtc::TaskQueue* worker_queue,
ProcessThread* module_process_thread,
RtcpRttStats* rtcp_rtt_stats,
- RtcEventLog* event_log) {
+ RtcEventLog* event_log,
+ FrameEncryptorInterface* frame_encryptor) {
return absl::make_unique<voe::ChannelSendProxy>(
absl::make_unique<voe::ChannelSend>(worker_queue, module_process_thread,
- rtcp_rtt_stats, event_log));
+ rtcp_rtt_stats, event_log,
+ frame_encryptor));
}
} // namespace
@@ -103,7 +105,8 @@
CreateChannelAndProxy(worker_queue,
module_process_thread,
rtcp_rtt_stats,
- event_log)) {}
+ event_log,
+ config.frame_encryptor)) {}
AudioSendStream::AudioSendStream(
const webrtc::AudioSendStream::Config& config,
@@ -227,6 +230,11 @@
stream->timed_send_transport_adapter_.get());
}
+ // Enable the frame encryptor if a new frame encryptor has been provided.
+ if (first_time || new_config.frame_encryptor != old_config.frame_encryptor) {
+ channel_proxy->SetFrameEncryptor(new_config.frame_encryptor);
+ }
+
const ExtensionIds old_ids = FindExtensionIds(old_config.rtp.extensions);
const ExtensionIds new_ids = FindExtensionIds(new_config.rtp.extensions);
// Audio level indication
diff --git a/audio/audio_send_stream_unittest.cc b/audio/audio_send_stream_unittest.cc
index 0ed1ac1..0a954f8 100644
--- a/audio/audio_send_stream_unittest.cc
+++ b/audio/audio_send_stream_unittest.cc
@@ -196,6 +196,7 @@
EXPECT_CALL(*channel_proxy_, SetLocalSSRC(kSsrc)).Times(1);
EXPECT_CALL(*channel_proxy_, SetRTCP_CNAME(StrEq(kCName))).Times(1);
EXPECT_CALL(*channel_proxy_, SetNACKStatus(true, 10)).Times(1);
+ EXPECT_CALL(*channel_proxy_, SetFrameEncryptor(nullptr)).Times(1);
EXPECT_CALL(*channel_proxy_,
SetSendAudioLevelIndicationStatus(true, kAudioLevelId))
.Times(1);
diff --git a/audio/channel_receive.cc b/audio/channel_receive.cc
index 784e4a7..26213cc 100644
--- a/audio/channel_receive.cc
+++ b/audio/channel_receive.cc
@@ -229,7 +229,8 @@
size_t jitter_buffer_max_packets,
bool jitter_buffer_fast_playout,
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory,
- absl::optional<AudioCodecPairId> codec_pair_id)
+ absl::optional<AudioCodecPairId> codec_pair_id,
+ FrameDecryptorInterface* frame_decryptor)
: event_log_(rtc_event_log),
rtp_receive_statistics_(
ReceiveStatistics::Create(Clock::GetRealTimeClock())),
@@ -245,7 +246,8 @@
_audioDeviceModulePtr(audio_device_module),
_transportPtr(NULL),
_outputGain(1.0f),
- associated_send_channel_(nullptr) {
+ associated_send_channel_(nullptr),
+ frame_decryptor_(frame_decryptor) {
RTC_DCHECK(module_process_thread);
RTC_DCHECK(audio_device_module);
AudioCodingModule::Config acm_config;
@@ -425,7 +427,38 @@
WebRtcRTPHeader webrtc_rtp_header = {};
webrtc_rtp_header.header = header;
- const size_t payload_data_length = payload_length - header.paddingLength;
+ size_t payload_data_length = payload_length - header.paddingLength;
+
+ // E2EE Custom Audio Frame Decryption (This is optional).
+ // Keep this buffer around for the lifetime of the OnReceivedPayloadData call.
+ rtc::Buffer decrypted_audio_payload;
+ if (frame_decryptor_ != nullptr) {
+ size_t max_plaintext_size = frame_decryptor_->GetMaxPlaintextByteSize(
+ cricket::MEDIA_TYPE_AUDIO, payload_length);
+ decrypted_audio_payload.SetSize(max_plaintext_size);
+
+ size_t bytes_written = 0;
+ std::vector<uint32_t> csrcs(header.arrOfCSRCs,
+ header.arrOfCSRCs + header.numCSRCs);
+ int decrypt_status = frame_decryptor_->Decrypt(
+ cricket::MEDIA_TYPE_AUDIO, csrcs,
+ /*additional_data=*/nullptr,
+ rtc::ArrayView<const uint8_t>(payload, payload_data_length),
+ decrypted_audio_payload, &bytes_written);
+
+ // In this case just interpret the failure as a silent frame.
+ if (decrypt_status != 0) {
+ bytes_written = 0;
+ }
+
+ // Resize the decrypted audio payload to the number of bytes actually
+ // written.
+ decrypted_audio_payload.SetSize(bytes_written);
+ // Update the final payload.
+ payload = decrypted_audio_payload.data();
+ payload_data_length = decrypted_audio_payload.size();
+ }
+
if (payload_data_length == 0) {
webrtc_rtp_header.frameType = kEmptyFrame;
return OnReceivedPayloadData(nullptr, 0, &webrtc_rtp_header);
diff --git a/audio/channel_receive.h b/audio/channel_receive.h
index 20198c4..2e089b7 100644
--- a/audio/channel_receive.h
+++ b/audio/channel_receive.h
@@ -42,6 +42,7 @@
namespace webrtc {
class AudioDeviceModule;
+class FrameDecryptorInterface;
class PacketRouter;
class ProcessThread;
class RateLimiter;
@@ -112,7 +113,8 @@
size_t jitter_buffer_max_packets,
bool jitter_buffer_fast_playout,
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory,
- absl::optional<AudioCodecPairId> codec_pair_id);
+ absl::optional<AudioCodecPairId> codec_pair_id,
+ FrameDecryptorInterface* frame_decryptor);
virtual ~ChannelReceive();
void SetSink(AudioSinkInterface* sink);
@@ -263,6 +265,9 @@
PacketRouter* packet_router_ = nullptr;
rtc::ThreadChecker construction_thread_;
+
+ // E2EE Audio Frame Decryption
+ FrameDecryptorInterface* frame_decryptor_ = nullptr;
};
} // namespace voe
diff --git a/audio/channel_send.cc b/audio/channel_send.cc
index 0c9328f..d3748b3 100644
--- a/audio/channel_send.cc
+++ b/audio/channel_send.cc
@@ -19,6 +19,7 @@
#include "absl/memory/memory.h"
#include "api/array_view.h"
+#include "api/crypto/frameencryptorinterface.h"
#include "audio/utility/audio_frame_operations.h"
#include "call/rtp_transport_controller_send_interface.h"
#include "logging/rtc_event_log/events/rtc_event_audio_playout.h"
@@ -260,6 +261,35 @@
_rtpRtcpModule->SetAudioLevel(rms_level_.Average());
}
+ // E2EE Custom Audio Frame Encryption (This is optional).
+ // Keep this buffer around for the lifetime of the send call.
+ rtc::Buffer encrypted_audio_payload;
+ if (frame_encryptor_ != nullptr) {
+ // TODO(benwright@webrtc.org) - Allocate enough to always encrypt inline.
+ // Allocate a buffer to hold the maximum possible encrypted payload.
+ size_t max_ciphertext_size = frame_encryptor_->GetMaxCiphertextByteSize(
+ cricket::MEDIA_TYPE_AUDIO, payloadSize);
+ encrypted_audio_payload.SetSize(max_ciphertext_size);
+
+ // Encrypt the audio payload into the buffer.
+ size_t bytes_written = 0;
+ int encrypt_status = frame_encryptor_->Encrypt(
+ cricket::MEDIA_TYPE_AUDIO, _rtpRtcpModule->SSRC(),
+ /*additional_data=*/nullptr,
+ rtc::ArrayView<const uint8_t>(payloadData, payloadSize),
+ encrypted_audio_payload, &bytes_written);
+ if (encrypt_status != 0) {
+ RTC_DLOG(LS_ERROR) << "Channel::SendData() failed encrypt audio payload: "
+ << encrypt_status;
+ return -1;
+ }
+ // Resize the buffer to the exact number of bytes actually used.
+ encrypted_audio_payload.SetSize(bytes_written);
+ // Rewrite the payloadData and size to the new encrypted payload.
+ payloadData = encrypted_audio_payload.data();
+ payloadSize = encrypted_audio_payload.size();
+ }
+
// Push data from ACM to RTP/RTCP-module to deliver audio frame for
// packetization.
// This call will trigger Transport::SendPacket() from the RTP/RTCP module.
@@ -322,7 +352,8 @@
ChannelSend::ChannelSend(rtc::TaskQueue* encoder_queue,
ProcessThread* module_process_thread,
RtcpRttStats* rtcp_rtt_stats,
- RtcEventLog* rtc_event_log)
+ RtcEventLog* rtc_event_log,
+ FrameEncryptorInterface* frame_encryptor)
: event_log_(rtc_event_log),
_timeStamp(0), // This is just an offset, RTP module will add it's own
// random offset
@@ -342,7 +373,8 @@
kMaxRetransmissionWindowMs)),
use_twcc_plr_for_ana_(
webrtc::field_trial::FindFullName("UseTwccPlrForAna") == "Enabled"),
- encoder_queue_(encoder_queue) {
+ encoder_queue_(encoder_queue),
+ frame_encryptor_(frame_encryptor) {
RTC_DCHECK(module_process_thread);
RTC_DCHECK(encoder_queue);
audio_coding_.reset(AudioCodingModule::Create(AudioCodingModule::Config()));
@@ -949,5 +981,16 @@
return rtt;
}
+void ChannelSend::SetFrameEncryptor(FrameEncryptorInterface* frame_encryptor) {
+ rtc::CritScope cs(&encoder_queue_lock_);
+ if (encoder_queue_is_active_) {
+ encoder_queue_->PostTask([this, frame_encryptor]() {
+ this->frame_encryptor_ = frame_encryptor;
+ });
+ } else {
+ frame_encryptor_ = frame_encryptor;
+ }
+}
+
} // namespace voe
} // namespace webrtc
diff --git a/audio/channel_send.h b/audio/channel_send.h
index 4569201..ef92f8e 100644
--- a/audio/channel_send.h
+++ b/audio/channel_send.h
@@ -37,6 +37,7 @@
namespace webrtc {
+class FrameEncryptorInterface;
class PacketRouter;
class ProcessThread;
class RateLimiter;
@@ -118,7 +119,8 @@
ChannelSend(rtc::TaskQueue* encoder_queue,
ProcessThread* module_process_thread,
RtcpRttStats* rtcp_rtt_stats,
- RtcEventLog* rtc_event_log);
+ RtcEventLog* rtc_event_log,
+ FrameEncryptorInterface* frame_encryptor);
virtual ~ChannelSend();
@@ -222,6 +224,9 @@
int64_t GetRTT() const;
+ // E2EE Custom Audio Frame Encryption
+ void SetFrameEncryptor(FrameEncryptorInterface* frame_encryptor);
+
private:
class ProcessAndEncodeAudioTask;
@@ -290,6 +295,9 @@
rtc::CriticalSection encoder_queue_lock_;
bool encoder_queue_is_active_ RTC_GUARDED_BY(encoder_queue_lock_) = false;
rtc::TaskQueue* encoder_queue_ = nullptr;
+
+ // E2EE Audio Frame Encryption
+ FrameEncryptorInterface* frame_encryptor_ = nullptr;
};
} // namespace voe
diff --git a/audio/channel_send_proxy.cc b/audio/channel_send_proxy.cc
index a4d8b69..8091bdc 100644
--- a/audio/channel_send_proxy.cc
+++ b/audio/channel_send_proxy.cc
@@ -197,5 +197,11 @@
return channel_.get();
}
+void ChannelSendProxy::SetFrameEncryptor(
+ FrameEncryptorInterface* frame_encryptor) {
+ RTC_DCHECK(worker_thread_checker_.CalledOnValidThread());
+ channel_->SetFrameEncryptor(frame_encryptor);
+}
+
} // namespace voe
} // namespace webrtc
diff --git a/audio/channel_send_proxy.h b/audio/channel_send_proxy.h
index 754f9f6..1b8b4a0 100644
--- a/audio/channel_send_proxy.h
+++ b/audio/channel_send_proxy.h
@@ -23,6 +23,7 @@
namespace webrtc {
+class FrameEncryptorInterface;
class RtcpBandwidthObserver;
class RtpRtcp;
class RtpTransportControllerSendInterface;
@@ -84,6 +85,9 @@
// Needed by ChannelReceiveProxy::AssociateSendChannel.
virtual ChannelSend* GetChannel() const;
+ // E2EE Custom Audio Frame Encryption (Optional)
+ virtual void SetFrameEncryptor(FrameEncryptorInterface* frame_encryptor);
+
private:
// Thread checkers document and lock usage of some methods on voe::Channel to
// specific threads we know about. The goal is to eventually split up
diff --git a/audio/mock_voe_channel_proxy.h b/audio/mock_voe_channel_proxy.h
index 0ae3cdc..88a50ea 100644
--- a/audio/mock_voe_channel_proxy.h
+++ b/audio/mock_voe_channel_proxy.h
@@ -105,6 +105,8 @@
void(float recoverable_packet_loss_rate));
MOCK_METHOD0(StartSend, void());
MOCK_METHOD0(StopSend, void());
+ MOCK_METHOD1(SetFrameEncryptor,
+ void(FrameEncryptorInterface* frame_encryptor));
};
} // namespace test
} // namespace webrtc
diff --git a/call/audio_receive_stream.h b/call/audio_receive_stream.h
index 9595a29..9c890b1 100644
--- a/call/audio_receive_stream.h
+++ b/call/audio_receive_stream.h
@@ -27,6 +27,7 @@
namespace webrtc {
class AudioSinkInterface;
+class FrameDecryptorInterface;
class AudioReceiveStream {
public:
@@ -120,6 +121,11 @@
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory;
absl::optional<AudioCodecPairId> codec_pair_id;
+
+ // An optional custom frame decryptor that allows the entire frame to be
+ // decrypted in whatever way the caller choses. This is not required by
+ // default.
+ rtc::scoped_refptr<webrtc::FrameDecryptorInterface> frame_decryptor;
};
// Reconfigure the stream according to the Configuration.
diff --git a/call/audio_send_stream.h b/call/audio_send_stream.h
index 60909ae..61e2531 100644
--- a/call/audio_send_stream.h
+++ b/call/audio_send_stream.h
@@ -21,6 +21,7 @@
#include "api/audio_codecs/audio_encoder_factory.h"
#include "api/audio_codecs/audio_format.h"
#include "api/call/transport.h"
+#include "api/crypto/frameencryptorinterface.h"
#include "api/rtpparameters.h"
#include "call/rtp_config.h"
#include "modules/audio_processing/include/audio_processing_statistics.h"
@@ -128,6 +129,11 @@
// Track ID as specified during track creation.
std::string track_id;
+
+ // An optional custom frame encryptor that allows the entire frame to be
+ // encryptor in whatever way the caller choses. This is not required by
+ // default.
+ rtc::scoped_refptr<webrtc::FrameEncryptorInterface> frame_encryptor;
};
virtual ~AudioSendStream() = default;
diff --git a/media/base/mediachannel.cc b/media/base/mediachannel.cc
index 304d7f8..cba3be3 100644
--- a/media/base/mediachannel.cc
+++ b/media/base/mediachannel.cc
@@ -15,6 +15,13 @@
VideoOptions::VideoOptions() = default;
VideoOptions::~VideoOptions() = default;
+MediaChannel::MediaChannel(const MediaConfig& config)
+ : enable_dscp_(config.enable_dscp), network_interface_(NULL) {}
+
+MediaChannel::MediaChannel() : enable_dscp_(false), network_interface_(NULL) {}
+
+MediaChannel::~MediaChannel() {}
+
void MediaChannel::SetInterface(NetworkInterface* iface) {
rtc::CritScope cs(&network_interface_crit_);
network_interface_ = iface;
@@ -30,13 +37,15 @@
}
void MediaChannel::SetFrameEncryptor(
- webrtc::FrameEncryptorInterface* frame_encryptor) {
- frame_encryptor_ = frame_encryptor;
+ uint32_t ssrc,
+ rtc::scoped_refptr<webrtc::FrameEncryptorInterface> frame_encryptor) {
+ // Placeholder should be pure virtual once internal supports it.
}
void MediaChannel::SetFrameDecryptor(
- webrtc::FrameDecryptorInterface* frame_decryptor) {
- frame_decryptor_ = frame_decryptor;
+ uint32_t ssrc,
+ rtc::scoped_refptr<webrtc::FrameDecryptorInterface> frame_decryptor) {
+ // Placeholder should be pure virtual once internal supports it.
}
MediaSenderInfo::MediaSenderInfo() = default;
diff --git a/media/base/mediachannel.h b/media/base/mediachannel.h
index 98b36ac..ff3368c 100644
--- a/media/base/mediachannel.h
+++ b/media/base/mediachannel.h
@@ -179,10 +179,9 @@
virtual ~NetworkInterface() {}
};
- explicit MediaChannel(const MediaConfig& config)
- : enable_dscp_(config.enable_dscp), network_interface_(NULL) {}
- MediaChannel() : enable_dscp_(false), network_interface_(NULL) {}
- ~MediaChannel() override {}
+ explicit MediaChannel(const MediaConfig& config);
+ MediaChannel();
+ ~MediaChannel() override;
// Sets the abstract interface class for sending RTP/RTCP data.
virtual void SetInterface(NetworkInterface* iface);
@@ -219,13 +218,17 @@
// Set the frame encryptor to use on all outgoing frames. This is optional.
// This pointers lifetime is managed by the set of RtpSender it is attached
// to.
+ // TODO(benwright) make pure virtual once internal supports it.
virtual void SetFrameEncryptor(
- webrtc::FrameEncryptorInterface* frame_encryptor);
+ uint32_t ssrc,
+ rtc::scoped_refptr<webrtc::FrameEncryptorInterface> frame_encryptor);
// Set the frame decryptor to use on all incoming frames. This is optional.
// This pointers lifetimes is managed by the set of RtpReceivers it is
// attached to.
+ // TODO(benwright) make pure virtual once internal supports it.
virtual void SetFrameDecryptor(
- webrtc::FrameDecryptorInterface* frame_decryptor);
+ uint32_t ssrc,
+ rtc::scoped_refptr<webrtc::FrameDecryptorInterface> frame_decryptor);
// Base method to send packet using NetworkInterface.
bool SendPacket(rtc::CopyOnWriteBuffer* packet,
@@ -281,10 +284,6 @@
// of network_interface_ object.
rtc::CriticalSection network_interface_crit_;
NetworkInterface* network_interface_;
-
- protected:
- webrtc::FrameEncryptorInterface* frame_encryptor_ = nullptr;
- webrtc::FrameDecryptorInterface* frame_decryptor_ = nullptr;
};
// The stats information is structured as follows:
diff --git a/media/engine/webrtcvoiceengine.cc b/media/engine/webrtcvoiceengine.cc
index bbe48a2..6b14c23 100644
--- a/media/engine/webrtcvoiceengine.cc
+++ b/media/engine/webrtcvoiceengine.cc
@@ -719,7 +719,8 @@
webrtc::Call* call,
webrtc::Transport* send_transport,
const rtc::scoped_refptr<webrtc::AudioEncoderFactory>& encoder_factory,
- const absl::optional<webrtc::AudioCodecPairId> codec_pair_id)
+ const absl::optional<webrtc::AudioCodecPairId> codec_pair_id,
+ rtc::scoped_refptr<webrtc::FrameEncryptorInterface> frame_encryptor)
: call_(call),
config_(send_transport),
send_side_bwe_with_overhead_(
@@ -736,6 +737,7 @@
config_.encoder_factory = encoder_factory;
config_.codec_pair_id = codec_pair_id;
config_.track_id = track_id;
+ config_.frame_encryptor = frame_encryptor;
rtp_parameters_.encodings[0].ssrc = ssrc;
rtp_parameters_.rtcp.cname = c_name;
rtp_parameters_.header_extensions = extensions;
@@ -775,6 +777,13 @@
ReconfigureAudioSendStream();
}
+ void SetFrameEncryptor(
+ rtc::scoped_refptr<webrtc::FrameEncryptorInterface> frame_encryptor) {
+ RTC_DCHECK(worker_thread_checker_.CalledOnValidThread());
+ config_.frame_encryptor = frame_encryptor;
+ ReconfigureAudioSendStream();
+ }
+
void SetAudioNetworkAdaptorConfig(
const absl::optional<std::string>& audio_network_adaptor_config) {
RTC_DCHECK(worker_thread_checker_.CalledOnValidThread());
@@ -1072,7 +1081,8 @@
const std::map<int, webrtc::SdpAudioFormat>& decoder_map,
absl::optional<webrtc::AudioCodecPairId> codec_pair_id,
size_t jitter_buffer_max_packets,
- bool jitter_buffer_fast_accelerate)
+ bool jitter_buffer_fast_accelerate,
+ rtc::scoped_refptr<webrtc::FrameDecryptorInterface> frame_decryptor)
: call_(call), config_() {
RTC_DCHECK(call);
config_.rtp.remote_ssrc = remote_ssrc;
@@ -1089,6 +1099,7 @@
config_.decoder_factory = decoder_factory;
config_.decoder_map = decoder_map;
config_.codec_pair_id = codec_pair_id;
+ config_.frame_decryptor = frame_decryptor;
RecreateAudioReceiveStream();
}
@@ -1097,6 +1108,13 @@
call_->DestroyAudioReceiveStream(stream_);
}
+ void SetFrameDecryptor(
+ rtc::scoped_refptr<webrtc::FrameDecryptorInterface> frame_decryptor) {
+ RTC_DCHECK(worker_thread_checker_.CalledOnValidThread());
+ config_.frame_decryptor = frame_decryptor;
+ RecreateAudioReceiveStream();
+ }
+
void SetLocalSsrc(uint32_t local_ssrc) {
RTC_DCHECK(worker_thread_checker_.CalledOnValidThread());
config_.rtp.local_ssrc = local_ssrc;
@@ -1745,7 +1763,7 @@
WebRtcAudioSendStream* stream = new WebRtcAudioSendStream(
ssrc, mid_, sp.cname, sp.id, send_codec_spec_, send_rtp_extensions_,
max_send_bitrate_bps_, audio_network_adaptor_config, call_, this,
- engine()->encoder_factory_, codec_pair_id_);
+ engine()->encoder_factory_, codec_pair_id_, nullptr);
send_streams_.insert(std::make_pair(ssrc, stream));
// At this point the stream's local SSRC has been updated. If it is the first
@@ -1831,7 +1849,8 @@
recv_nack_enabled_, sp.stream_ids(), recv_rtp_extensions_,
call_, this, engine()->decoder_factory_, decoder_map_,
codec_pair_id_, engine()->audio_jitter_buffer_max_packets_,
- engine()->audio_jitter_buffer_fast_accelerate_)));
+ engine()->audio_jitter_buffer_fast_accelerate_,
+ unsignaled_frame_decryptor_)));
recv_streams_[ssrc]->SetPlayout(playout_);
return true;
@@ -1912,6 +1931,30 @@
return dtmf_payload_type_.has_value() && send_;
}
+void WebRtcVoiceMediaChannel::SetFrameDecryptor(
+ uint32_t ssrc,
+ rtc::scoped_refptr<webrtc::FrameDecryptorInterface> frame_decryptor) {
+ RTC_DCHECK(worker_thread_checker_.CalledOnValidThread());
+ auto matching_stream = recv_streams_.find(ssrc);
+ if (matching_stream != recv_streams_.end()) {
+ matching_stream->second->SetFrameDecryptor(frame_decryptor);
+ }
+ // Handle unsignaled frame decryptors.
+ if (ssrc == 0) {
+ unsignaled_frame_decryptor_ = frame_decryptor;
+ }
+}
+
+void WebRtcVoiceMediaChannel::SetFrameEncryptor(
+ uint32_t ssrc,
+ rtc::scoped_refptr<webrtc::FrameEncryptorInterface> frame_encryptor) {
+ RTC_DCHECK(worker_thread_checker_.CalledOnValidThread());
+ auto matching_stream = send_streams_.find(ssrc);
+ if (matching_stream != send_streams_.end()) {
+ matching_stream->second->SetFrameEncryptor(frame_encryptor);
+ }
+}
+
bool WebRtcVoiceMediaChannel::InsertDtmf(uint32_t ssrc,
int event,
int duration) {
diff --git a/media/engine/webrtcvoiceengine.h b/media/engine/webrtcvoiceengine.h
index db3f7c2..bedede1 100644
--- a/media/engine/webrtcvoiceengine.h
+++ b/media/engine/webrtcvoiceengine.h
@@ -170,6 +170,21 @@
bool RemoveSendStream(uint32_t ssrc) override;
bool AddRecvStream(const StreamParams& sp) override;
bool RemoveRecvStream(uint32_t ssrc) override;
+
+ // E2EE Frame API
+ // Set a frame decryptor to a particular ssrc that will intercept all
+ // incoming audio payloads and attempt to decrypt them before forwarding the
+ // result.
+ void SetFrameDecryptor(uint32_t ssrc,
+ rtc::scoped_refptr<webrtc::FrameDecryptorInterface>
+ frame_decryptor) override;
+ // Set a frame encryptor to a particular ssrc that will intercept all
+ // outgoing audio payloads frames and attempt to encrypt them and forward the
+ // result to the packetizer.
+ void SetFrameEncryptor(uint32_t ssrc,
+ rtc::scoped_refptr<webrtc::FrameEncryptorInterface>
+ frame_encryptor) override;
+
// SSRC=0 will apply the new volume to current and future unsignaled streams.
bool SetOutputVolume(uint32_t ssrc, double volume) override;
@@ -287,6 +302,10 @@
const webrtc::AudioCodecPairId codec_pair_id_ =
webrtc::AudioCodecPairId::Create();
+ // Unsignaled streams have an option to have a frame decryptor set on them.
+ rtc::scoped_refptr<webrtc::FrameDecryptorInterface>
+ unsignaled_frame_decryptor_;
+
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(WebRtcVoiceMediaChannel);
};
} // namespace cricket
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index 3ad3340..4e6e558 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -492,6 +492,7 @@
deps = [
":peerconnection",
":rtc_pc_base",
+ "../api:fake_frame_crypto",
"../api:libjingle_peerconnection_api",
"../api:mock_rtp",
"../api/units:time_delta",
diff --git a/pc/rtpreceiver.cc b/pc/rtpreceiver.cc
index 8ab08bf..d4a1f62 100644
--- a/pc/rtpreceiver.cc
+++ b/pc/rtpreceiver.cc
@@ -43,13 +43,17 @@
return streams;
}
-void AttachFrameDecryptorToMediaChannel(
+// Attempt to attach the frame decryptor to the current media channel on the
+// correct worker thread only if both the media channel exists and a ssrc has
+// been allocated to the stream.
+void MaybeAttachFrameDecryptorToMediaChannel(
+ const absl::optional<uint32_t>& ssrc,
rtc::Thread* worker_thread,
- webrtc::FrameDecryptorInterface* frame_decryptor,
+ rtc::scoped_refptr<webrtc::FrameDecryptorInterface> frame_decryptor,
cricket::MediaChannel* media_channel) {
- if (media_channel) {
+ if (media_channel && ssrc.has_value()) {
return worker_thread->Invoke<void>(RTC_FROM_HERE, [&] {
- media_channel->SetFrameDecryptor(frame_decryptor);
+ media_channel->SetFrameDecryptor(*ssrc, frame_decryptor);
});
}
}
@@ -152,8 +156,9 @@
void AudioRtpReceiver::SetFrameDecryptor(
rtc::scoped_refptr<FrameDecryptorInterface> frame_decryptor) {
frame_decryptor_ = std::move(frame_decryptor);
- AttachFrameDecryptorToMediaChannel(worker_thread_, frame_decryptor_.get(),
- media_channel_);
+ // Attach the frame decryptor to the media channel if it exists.
+ MaybeAttachFrameDecryptorToMediaChannel(ssrc_, worker_thread_,
+ frame_decryptor_, media_channel_);
}
rtc::scoped_refptr<FrameDecryptorInterface>
@@ -246,6 +251,9 @@
if (!SetOutputVolume(track_->enabled() ? cached_volume_ : 0)) {
RTC_NOTREACHED();
}
+ // Reattach the frame decryptor if we were reconfigured.
+ MaybeAttachFrameDecryptorToMediaChannel(ssrc_, worker_thread_,
+ frame_decryptor_, media_channel_);
}
void AudioRtpReceiver::SetObserver(RtpReceiverObserverInterface* observer) {
@@ -259,8 +267,6 @@
void AudioRtpReceiver::SetVoiceMediaChannel(
cricket::VoiceMediaChannel* voice_media_channel) {
media_channel_ = voice_media_channel;
- AttachFrameDecryptorToMediaChannel(worker_thread_, frame_decryptor_.get(),
- media_channel_);
}
void AudioRtpReceiver::NotifyFirstPacketReceived() {
@@ -341,8 +347,9 @@
void VideoRtpReceiver::SetFrameDecryptor(
rtc::scoped_refptr<FrameDecryptorInterface> frame_decryptor) {
frame_decryptor_ = std::move(frame_decryptor);
- AttachFrameDecryptorToMediaChannel(worker_thread_, frame_decryptor_.get(),
- media_channel_);
+ // Attach the new frame decryptor the media channel if it exists yet.
+ MaybeAttachFrameDecryptorToMediaChannel(ssrc_, worker_thread_,
+ frame_decryptor_, media_channel_);
}
rtc::scoped_refptr<FrameDecryptorInterface>
@@ -379,6 +386,9 @@
}
ssrc_ = ssrc;
SetSink(source_->sink());
+ // Attach any existing frame decryptor to the media channel.
+ MaybeAttachFrameDecryptorToMediaChannel(ssrc_, worker_thread_,
+ frame_decryptor_, media_channel_);
}
void VideoRtpReceiver::set_stream_ids(std::vector<std::string> stream_ids) {
@@ -429,8 +439,6 @@
void VideoRtpReceiver::SetVideoMediaChannel(
cricket::VideoMediaChannel* video_media_channel) {
media_channel_ = video_media_channel;
- AttachFrameDecryptorToMediaChannel(worker_thread_, frame_decryptor_.get(),
- media_channel_);
}
void VideoRtpReceiver::NotifyFirstPacketReceived() {
diff --git a/pc/rtpsender.cc b/pc/rtpsender.cc
index 20130a1..3bb90f2 100644
--- a/pc/rtpsender.cc
+++ b/pc/rtpsender.cc
@@ -31,20 +31,6 @@
return ++g_unique_id;
}
-// Attaches the frame encryptor to the media channel through an invoke on a
-// worker thread. This set must be done on the corresponding worker thread that
-// the media channel was created on.
-void AttachFrameEncryptorToMediaChannel(
- rtc::Thread* worker_thread,
- webrtc::FrameEncryptorInterface* frame_encryptor,
- cricket::MediaChannel* media_channel) {
- if (media_channel) {
- return worker_thread->Invoke<void>(RTC_FROM_HERE, [&] {
- media_channel->SetFrameEncryptor(frame_encryptor);
- });
- }
-}
-
// Returns an true if any RtpEncodingParameters member that isn't implemented
// contains a value.
bool UnimplementedRtpEncodingParameterHasValue(
@@ -78,6 +64,21 @@
return false;
}
+// Attempt to attach the frame decryptor to the current media channel on the
+// correct worker thread only if both the media channel exists and a ssrc has
+// been allocated to the stream.
+void MaybeAttachFrameEncryptorToMediaChannel(
+ const absl::optional<uint32_t> ssrc,
+ rtc::Thread* worker_thread,
+ rtc::scoped_refptr<webrtc::FrameEncryptorInterface> frame_encryptor,
+ cricket::MediaChannel* media_channel) {
+ if (media_channel && ssrc.has_value()) {
+ return worker_thread->Invoke<void>(RTC_FROM_HERE, [&] {
+ media_channel->SetFrameEncryptor(*ssrc, frame_encryptor);
+ });
+ }
+}
+
} // namespace
// Returns true if any RtpParameters member that isn't implemented contains a
@@ -304,8 +305,8 @@
void AudioRtpSender::SetFrameEncryptor(
rtc::scoped_refptr<FrameEncryptorInterface> frame_encryptor) {
frame_encryptor_ = std::move(frame_encryptor);
- AttachFrameEncryptorToMediaChannel(worker_thread_, frame_encryptor_.get(),
- media_channel_);
+ MaybeAttachFrameEncryptorToMediaChannel(ssrc_, worker_thread_,
+ frame_encryptor_, media_channel_);
}
rtc::scoped_refptr<FrameEncryptorInterface> AudioRtpSender::GetFrameEncryptor()
@@ -354,6 +355,9 @@
init_parameters_.encodings.clear();
});
}
+ // Each time there is an ssrc update.
+ MaybeAttachFrameEncryptorToMediaChannel(ssrc_, worker_thread_,
+ frame_encryptor_, media_channel_);
}
void AudioRtpSender::Stop() {
@@ -379,8 +383,6 @@
void AudioRtpSender::SetVoiceMediaChannel(
cricket::VoiceMediaChannel* voice_media_channel) {
media_channel_ = voice_media_channel;
- AttachFrameEncryptorToMediaChannel(worker_thread_, frame_encryptor_.get(),
- media_channel_);
}
void AudioRtpSender::SetAudioSend() {
@@ -555,8 +557,8 @@
void VideoRtpSender::SetFrameEncryptor(
rtc::scoped_refptr<FrameEncryptorInterface> frame_encryptor) {
frame_encryptor_ = std::move(frame_encryptor);
- AttachFrameEncryptorToMediaChannel(worker_thread_, frame_encryptor_.get(),
- media_channel_);
+ MaybeAttachFrameEncryptorToMediaChannel(ssrc_, worker_thread_,
+ frame_encryptor_, media_channel_);
}
rtc::scoped_refptr<FrameEncryptorInterface> VideoRtpSender::GetFrameEncryptor()
@@ -599,6 +601,8 @@
init_parameters_.encodings.clear();
});
}
+ MaybeAttachFrameEncryptorToMediaChannel(ssrc_, worker_thread_,
+ frame_encryptor_, media_channel_);
}
void VideoRtpSender::Stop() {
@@ -620,8 +624,6 @@
void VideoRtpSender::SetVideoMediaChannel(
cricket::VideoMediaChannel* video_media_channel) {
media_channel_ = video_media_channel;
- AttachFrameEncryptorToMediaChannel(worker_thread_, frame_encryptor_.get(),
- media_channel_);
}
void VideoRtpSender::SetVideoSend() {
diff --git a/pc/rtpsenderreceiver_unittest.cc b/pc/rtpsenderreceiver_unittest.cc
index 2af2ebf..07ff6a3 100644
--- a/pc/rtpsenderreceiver_unittest.cc
+++ b/pc/rtpsenderreceiver_unittest.cc
@@ -13,6 +13,8 @@
#include <utility>
#include "api/rtpparameters.h"
+#include "api/test/fake_frame_decryptor.h"
+#include "api/test/fake_frame_encryptor.h"
#include "media/base/fakemediaengine.h"
#include "media/base/rtpdataengine.h"
#include "media/base/testutils.h"
@@ -1411,4 +1413,26 @@
EXPECT_TRUE(audio_sender_destroyed_signal_fired_);
}
+// Validate that the default FrameEncryptor setting is nullptr.
+TEST_F(RtpSenderReceiverTest, AudioSenderCanSetFrameEncryptor) {
+ CreateAudioRtpSender();
+ rtc::scoped_refptr<FrameEncryptorInterface> fake_frame_encryptor(
+ new FakeFrameEncryptor());
+ EXPECT_EQ(nullptr, audio_rtp_sender_->GetFrameEncryptor());
+ audio_rtp_sender_->SetFrameEncryptor(fake_frame_encryptor);
+ EXPECT_EQ(fake_frame_encryptor.get(),
+ audio_rtp_sender_->GetFrameEncryptor().get());
+}
+
+// Validate that the default FrameEncryptor setting is nullptr.
+TEST_F(RtpSenderReceiverTest, AudioReceiverCanSetFrameDecryptor) {
+ CreateAudioRtpReceiver();
+ rtc::scoped_refptr<FrameDecryptorInterface> fake_frame_decryptor(
+ new FakeFrameDecryptor());
+ EXPECT_EQ(nullptr, audio_rtp_receiver_->GetFrameDecryptor());
+ audio_rtp_receiver_->SetFrameDecryptor(fake_frame_decryptor);
+ EXPECT_EQ(fake_frame_decryptor.get(),
+ audio_rtp_receiver_->GetFrameDecryptor().get());
+}
+
} // namespace webrtc