blob: 2b6ccf8a3319c4a07291f96669dd59b61e9c8f6c [file]
/*
* Copyright 2026 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 "modules/sframe/sframe_decryptor_handle.h"
#include <cstddef>
#include <cstdint>
#include <span>
#include <utility>
#include <vector>
#include "api/make_ref_counted.h"
#include "api/rtc_error.h"
#include "api/scoped_refptr.h"
#include "api/sframe/sframe_types.h"
#include "api/task_queue/task_queue_base.h"
#include "modules/sframe/mock_sframe_media_decryptor.h"
#include "rtc_base/checks.h"
#include "rtc_base/task_queue_for_test.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/run_loop.h"
namespace webrtc {
namespace {
using ::testing::_;
using ::testing::ElementsAreArray;
using ::testing::InSequence;
using ::testing::Return;
constexpr uint64_t kKeyId = 7;
// Returns a callback that stores the result and stops `run_loop`.
SframeKeyUpdateCallback StoreResult(test::RunLoop& run_loop, RTCError& result) {
return [&run_loop, &result](RTCError error) {
EXPECT_EQ(TaskQueueBase::Current(), run_loop.task_queue());
result = std::move(error);
run_loop.Quit();
};
}
TEST(SframeDecryptorHandleTest, AddsKeyOnDecryptorQueue) {
test::RunLoop run_loop;
TaskQueueForTest decryptor_queue;
scoped_refptr<MockSframeMediaDecryptor> decryptor =
make_ref_counted<MockSframeMediaDecryptor>();
scoped_refptr<SframeDecryptorHandle> handle = SframeDecryptorHandle::Create(
*run_loop.task_queue(), *decryptor_queue.Get(), decryptor);
const std::vector<uint8_t> key_material(16, 0xab);
EXPECT_CALL(*decryptor,
AddDecryptionKey(kKeyId, ElementsAreArray(key_material)))
.WillOnce([&] {
EXPECT_TRUE(decryptor_queue.IsCurrent());
return RTCError::OK();
});
RTCError result = RTCError::InternalError("Not called");
handle->AddDecryptionKey(kKeyId, key_material, StoreResult(run_loop, result));
run_loop.Run();
EXPECT_TRUE(result.ok());
}
TEST(SframeDecryptorHandleTest, RemovesKeyOnDecryptorQueue) {
test::RunLoop run_loop;
TaskQueueForTest decryptor_queue;
scoped_refptr<MockSframeMediaDecryptor> decryptor =
make_ref_counted<MockSframeMediaDecryptor>();
scoped_refptr<SframeDecryptorHandle> handle = SframeDecryptorHandle::Create(
*run_loop.task_queue(), *decryptor_queue.Get(), decryptor);
EXPECT_CALL(*decryptor, RemoveDecryptionKey(kKeyId)).WillOnce([&] {
EXPECT_TRUE(decryptor_queue.IsCurrent());
return RTCError::OK();
});
RTCError result = RTCError::InternalError("Not called");
handle->RemoveDecryptionKey(kKeyId, StoreResult(run_loop, result));
run_loop.Run();
EXPECT_TRUE(result.ok());
}
TEST(SframeDecryptorHandleTest, ReportsKeyUpdateError) {
test::RunLoop run_loop;
TaskQueueForTest decryptor_queue;
scoped_refptr<MockSframeMediaDecryptor> decryptor =
make_ref_counted<MockSframeMediaDecryptor>();
scoped_refptr<SframeDecryptorHandle> handle = SframeDecryptorHandle::Create(
*run_loop.task_queue(), *decryptor_queue.Get(), decryptor);
EXPECT_CALL(*decryptor, AddDecryptionKey)
.WillOnce(Return(RTCError::InternalError("Bad key")));
RTCError result = RTCError::OK();
handle->AddDecryptionKey(kKeyId, std::vector<uint8_t>(16, 0xab),
StoreResult(run_loop, result));
run_loop.Run();
EXPECT_EQ(result.type(), RTCErrorType::INTERNAL_ERROR);
}
TEST(SframeDecryptorHandleTest, KeepsCopyOfKeyMaterial) {
test::RunLoop run_loop;
TaskQueueForTest decryptor_queue;
scoped_refptr<MockSframeMediaDecryptor> decryptor =
make_ref_counted<MockSframeMediaDecryptor>();
scoped_refptr<SframeDecryptorHandle> handle = SframeDecryptorHandle::Create(
*run_loop.task_queue(), *decryptor_queue.Get(), decryptor);
const std::vector<uint8_t> expected_key(16, 0xab);
EXPECT_CALL(*decryptor,
AddDecryptionKey(kKeyId, ElementsAreArray(expected_key)))
.WillOnce(Return(RTCError::OK()));
RTCError result = RTCError::InternalError("Not called");
{
std::vector<uint8_t> key_material = expected_key;
handle->AddDecryptionKey(kKeyId, key_material,
StoreResult(run_loop, result));
key_material.assign(key_material.size(), 0);
}
run_loop.Run();
EXPECT_TRUE(result.ok());
}
TEST(SframeDecryptorHandleTest, AppliesKeyUpdatesInOrder) {
test::RunLoop run_loop;
TaskQueueForTest decryptor_queue;
scoped_refptr<MockSframeMediaDecryptor> decryptor =
make_ref_counted<MockSframeMediaDecryptor>();
scoped_refptr<SframeDecryptorHandle> handle = SframeDecryptorHandle::Create(
*run_loop.task_queue(), *decryptor_queue.Get(), decryptor);
InSequence s;
EXPECT_CALL(*decryptor, AddDecryptionKey(kKeyId, _))
.WillOnce(Return(RTCError::OK()));
EXPECT_CALL(*decryptor, RemoveDecryptionKey(kKeyId))
.WillOnce(Return(RTCError::OK()));
RTCError add_result = RTCError::InternalError("Not called");
handle->AddDecryptionKey(
kKeyId, std::vector<uint8_t>(16, 0xab),
[&add_result](RTCError error) { add_result = std::move(error); });
RTCError remove_result = RTCError::InternalError("Not called");
handle->RemoveDecryptionKey(kKeyId, StoreResult(run_loop, remove_result));
run_loop.Run();
EXPECT_TRUE(add_result.ok());
EXPECT_TRUE(remove_result.ok());
}
TEST(SframeDecryptorHandleTest,
AppliesKeyUpdateButDropsCallbackAfterHandleIsReleased) {
test::RunLoop run_loop;
TaskQueueForTest decryptor_queue;
scoped_refptr<MockSframeMediaDecryptor> decryptor =
make_ref_counted<MockSframeMediaDecryptor>();
scoped_refptr<SframeDecryptorHandle> handle = SframeDecryptorHandle::Create(
*run_loop.task_queue(), *decryptor_queue.Get(), decryptor);
EXPECT_CALL(*decryptor, RemoveDecryptionKey(kKeyId))
.WillOnce(Return(RTCError::OK()));
bool callback_called = false;
handle->RemoveDecryptionKey(
kKeyId, [&callback_called](RTCError) { callback_called = true; });
handle = nullptr;
// Wait for the key update, then run the task it posted to the callback queue.
decryptor_queue.WaitForPreviouslyPostedTasks();
run_loop.Flush();
EXPECT_FALSE(callback_called);
}
TEST(SframeDecryptorHandleTest, ReportsResultOnCallbackQueue) {
test::RunLoop run_loop;
TaskQueueForTest decryptor_queue;
scoped_refptr<MockSframeMediaDecryptor> decryptor =
make_ref_counted<MockSframeMediaDecryptor>();
scoped_refptr<SframeDecryptorHandle> handle = SframeDecryptorHandle::Create(
*run_loop.task_queue(), *decryptor_queue.Get(), decryptor);
EXPECT_CALL(*decryptor, RemoveDecryptionKey(kKeyId))
.WillOnce(Return(RTCError::OK()));
RTCError result = RTCError::InternalError("Not called");
TaskQueueForTest caller_queue("Caller");
caller_queue.SendTask([&] {
handle->RemoveDecryptionKey(kKeyId, StoreResult(run_loop, result));
});
run_loop.Run();
EXPECT_TRUE(result.ok());
}
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
TEST(SframeDecryptorHandleDeathTest, ReleasingOffCallbackQueueDies) {
// The task queues are created inside EXPECT_DEATH, so that they exist in the
// child process that runs the statement.
EXPECT_DEATH(
{
test::RunLoop run_loop;
TaskQueueForTest decryptor_queue;
TaskQueueForTest other_queue("Other");
scoped_refptr<SframeDecryptorHandle> handle =
SframeDecryptorHandle::Create(
*run_loop.task_queue(), *decryptor_queue.Get(),
make_ref_counted<MockSframeMediaDecryptor>());
other_queue.SendTask([&] { handle = nullptr; });
},
"");
}
#endif
} // namespace
} // namespace webrtc