blob: 1d328a26558a5f1ef529c63b6e709e0535fb53d4 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cast/streaming/public/receiver_session.h"
#include <functional>
#include <memory>
#include <utility>
#include "cast/streaming/input.pb.h"
#include "cast/streaming/public/receiver.h"
#include "cast/streaming/testing/mock_environment.h"
#include "cast/streaming/testing/simple_message_port.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "platform/base/ip_address.h"
#include "platform/test/fake_clock.h"
#include "platform/test/fake_task_runner.h"
#include "util/chrono_helpers.h"
#include "util/json/json_serialization.h"
#include "util/std_util.h"
using ::testing::_;
using ::testing::InSequence;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::StrictMock;
namespace openscreen::cast {
namespace {
constexpr char kValidOfferMessage[] = R"({
"type": "OFFER",
"seqNum": 1337,
"offer": {
"castMode": "mirroring",
"supportedStreams": [
{
"index": 31337,
"type": "video_source",
"codecName": "vp9",
"rtpProfile": "cast",
"rtpPayloadType": 127,
"ssrc": 19088743,
"maxFrameRate": "60000/1000",
"timeBase": "1/90000",
"maxBitRate": 5000000,
"profile": "main",
"level": "4",
"aesKey": "bbf109bf84513b456b13a184453b66ce",
"aesIvMask": "edaf9e4536e2b66191f560d9c04b2a69",
"receiverRtcpDscp": 46,
"resolutions": [
{
"width": 1280,
"height": 720
}
]
},
{
"index": 31338,
"type": "video_source",
"codecName": "vp8",
"rtpProfile": "cast",
"rtpPayloadType": 127,
"ssrc": 19088745,
"maxFrameRate": "60000/1000",
"timeBase": "1/90000",
"maxBitRate": 5000000,
"profile": "main",
"level": "4",
"aesKey": "040d756791711fd3adb939066e6d8690",
"aesIvMask": "9ff0f022a959150e70a2d05a6c184aed",
"receiverRtcpDscp": 46,
"resolutions": [
{
"width": 1280,
"height": 720
}
]
},
{
"index": 31339,
"type": "video_source",
"codecName": "hevc",
"codecParameter": "hev1.1.6.L150.B0",
"rtpProfile": "cast",
"rtpPayloadType": 127,
"ssrc": 19088746,
"maxFrameRate": "120",
"timeBase": "1/90000",
"maxBitRate": 5000000,
"aesKey": "040d756791711fd3adb939066e6d8690",
"aesIvMask": "9ff0f022a959150e70a2d05a6c184aed",
"receiverRtcpDscp": 46,
"resolutions": [
{
"width": 1920,
"height": 1080
}
]
},
{
"index": 1337,
"type": "audio_source",
"codecName": "opus",
"rtpProfile": "cast",
"rtpPayloadType": 97,
"ssrc": 19088747,
"bitRate": 124000,
"timeBase": "1/48000",
"channels": 2,
"aesKey": "51027e4e2347cbcb49d57ef10177aebc",
"aesIvMask": "7f12a19be62a36c04ae4116caaeff6d1",
"receiverRtcpDscp": 46
}
]
}
})";
constexpr char kValidRemotingOfferMessage[] = R"({
"type": "OFFER",
"seqNum": 419,
"offer": {
"castMode": "remoting",
"supportedStreams": [
{
"index": 31339,
"type": "video_source",
"codecName": "REMOTE_VIDEO",
"rtpProfile": "cast",
"rtpPayloadType": 127,
"ssrc": 19088745,
"maxFrameRate": "60000/1000",
"timeBase": "1/90000",
"maxBitRate": 5432101,
"aesKey": "040d756791711fd3adb939066e6d8690",
"aesIvMask": "9ff0f022a959150e70a2d05a6c184aed",
"resolutions": [
{
"width": 1920,
"height":1080
}
]
},
{
"index": 31340,
"type": "audio_source",
"codecName": "REMOTE_AUDIO",
"rtpProfile": "cast",
"rtpPayloadType": 97,
"ssrc": 19088747,
"bitRate": 125000,
"timeBase": "1/48000",
"channels": 2,
"aesKey": "51027e4e2347cbcb49d57ef10177aebc",
"aesIvMask": "7f12a19be62a36c04ae4116caaeff6d1"
}
]
}
})";
constexpr char kNoAudioOfferMessage[] = R"({
"type": "OFFER",
"seqNum": 1337,
"offer": {
"castMode": "mirroring",
"supportedStreams": [
{
"index": 31338,
"type": "video_source",
"codecName": "vp8",
"rtpProfile": "cast",
"rtpPayloadType": 127,
"ssrc": 19088745,
"maxFrameRate": "60000/1000",
"timeBase": "1/90000",
"maxBitRate": 5000000,
"profile": "main",
"level": "4",
"aesKey": "040d756791711fd3adb939066e6d8690",
"aesIvMask": "9ff0f022a959150e70a2d05a6c184aed",
"resolutions": [
{
"width": 1280,
"height": 720
}
]
}
]
}
})";
constexpr char kInvalidCodecOfferMessage[] = R"({
"type": "OFFER",
"seqNum": 1337,
"offer": {
"castMode": "mirroring",
"supportedStreams": [
{
"index": 31338,
"type": "video_source",
"codecName": "vp12",
"rtpProfile": "cast",
"rtpPayloadType": 127,
"ssrc": 19088745,
"maxFrameRate": "60000/1000",
"timeBase": "1/90000",
"maxBitRate": 5000000,
"profile": "main",
"level": "4",
"aesKey": "040d756791711fd3adb939066e6d8690",
"aesIvMask": "9ff0f022a959150e70a2d05a6c184aed",
"resolutions": [
{
"width": 1280,
"height": 720
}
]
}
]
}
})";
constexpr char kNoVideoOfferMessage[] = R"({
"type": "OFFER",
"seqNum": 1337,
"offer": {
"castMode": "mirroring",
"supportedStreams": [
{
"index": 1337,
"type": "audio_source",
"codecName": "opus",
"rtpProfile": "cast",
"rtpPayloadType": 97,
"ssrc": 19088747,
"bitRate": 124000,
"timeBase": "1/48000",
"channels": 2,
"aesKey": "51027e4e2347cbcb49d57ef10177aebc",
"aesIvMask": "7f12a19be62a36c04ae4116caaeff6d1"
}
]
}
})";
constexpr char kNoAudioOrVideoOfferMessage[] = R"({
"type": "OFFER",
"seqNum": 1337,
"offer": {
"castMode": "mirroring",
"supportedStreams": []
}
})";
constexpr char kInvalidJsonOfferMessage[] = R"({
"type": "OFFER",
"seqNum": 1337,
"offer": {
"castMode": "mirroring",
"supportedStreams": [
}
})";
constexpr char kMissingMandatoryFieldOfferMessage[] = R"({
"type": "OFFER",
"seqNum": 1337
})";
constexpr char kMissingSeqNumOfferMessage[] = R"({
"type": "OFFER",
"offer": {
"castMode": "mirroring",
"supportedStreams": []
}
})";
constexpr char kValidJsonInvalidFormatOfferMessage[] = R"({
"type": "OFFER",
"seqNum": 1337,
"offer": {
"castMode": "mirroring",
"supportedStreams": "anything"
}
})";
constexpr char kNullJsonOfferMessage[] = R"({
"type": "OFFER",
"seqNum": 1337
})";
constexpr char kInvalidSequenceNumberMessage[] = R"({
"type": "OFFER",
"seqNum": "not actually a number"
})";
constexpr char kUnknownTypeMessage[] = R"({
"type": "OFFER_VERSION_2",
"seqNum": 1337
})";
constexpr char kInvalidTypeMessage[] = R"({
"type": 39,
"seqNum": 1337
})";
constexpr char kGetCapabilitiesMessage[] = R"({
"seqNum": 820263770,
"type": "GET_CAPABILITIES"
})";
constexpr char kRpcMessage[] = R"({
"rpc" : "CGQQnBiCGQgSAggMGgIIBg==",
"seqNum" : 2,
"type" : "RPC"
})";
constexpr char kValidOfferMessageWithInput[] = R"({
"type": "OFFER",
"seqNum": 1337,
"offer": {
"castMode": "mirroring",
"supportedStreams": [
{
"index": 31338,
"type": "video_source",
"codecName": "vp8",
"rtpProfile": "cast",
"rtpPayloadType": 127,
"ssrc": 19088745,
"maxFrameRate": "60000/1000",
"timeBase": "1/90000",
"maxBitRate": 5000000,
"profile": "main",
"level": "4",
"aesKey": "040d756791711fd3adb939066e6d8690",
"aesIvMask": "9ff0f022a959150e70a2d05a6c184aed",
"rtpExtensions": ["input_events"],
"resolutions": [
{
"width": 1280,
"height": 720
}
]
}
]
}
})";
constexpr char kInputMessage[] = R"({
"input" : "CGQQnBiCGQgSAggMGgIIBg==",
"seqNum" : 3,
"type" : "INPUT"
})";
class FakeClient : public ReceiverSession::Client {
public:
MOCK_METHOD(void,
OnNegotiated,
(const ReceiverSession*, ReceiverSession::ConfiguredReceivers),
(override));
MOCK_METHOD(void,
OnRemotingNegotiated,
(const ReceiverSession*, ReceiverSession::RemotingNegotiation),
(override));
MOCK_METHOD(void,
OnReceiversDestroying,
(const ReceiverSession*, ReceiversDestroyingReason),
(override));
MOCK_METHOD(void,
OnError,
(const ReceiverSession*, const Error& error),
(override));
MOCK_METHOD(bool,
SupportsCodecParameter,
(const std::string& parameter),
(override));
};
void ExpectIsErrorAnswer(const std::string& raw_message) {
ErrorOr<Json::Value> message_or_error = json::Parse(raw_message);
ASSERT_TRUE(message_or_error.is_value());
const Json::Value message = std::move(message_or_error.value());
EXPECT_TRUE(message["answer"].isNull());
EXPECT_EQ("error", message["result"].asString());
EXPECT_EQ(1337, message["seqNum"].asInt());
EXPECT_EQ("ANSWER", message["type"].asString());
const Json::Value& error = message["error"];
EXPECT_TRUE(error.isObject());
EXPECT_GT(error["code"].asInt(), 0);
}
// Returns the parsed and validated ANSWER message for additional field
// validation.
Json::Value ExpectIsValidAnswer(const std::string& raw_message) {
ErrorOr<Json::Value> message_or_error = json::Parse(raw_message);
EXPECT_TRUE(message_or_error.is_value());
Json::Value message = std::move(message_or_error.value());
EXPECT_EQ("ANSWER", message["type"].asString());
EXPECT_EQ("ok", message["result"].asString());
EXPECT_FALSE(message["answer"].isNull());
EXPECT_TRUE(message["answer"].isObject());
return message;
}
MATCHER_P(
NegotiatedWithSender,
sender_id,
"Checks that a set of ConfiguredReceivers are from a specific sender") {
return sender_id == arg.sender_id;
}
} // namespace
class ReceiverSessionTest : public ::testing::Test {
public:
ReceiverSessionTest() : clock_(Clock::time_point{}), task_runner_(clock_) {}
std::unique_ptr<MockEnvironment> MakeEnvironment() {
auto environment = std::make_unique<NiceMock<MockEnvironment>>(
&FakeClock::now, task_runner_);
ON_CALL(*environment, GetBoundLocalEndpoint())
.WillByDefault(Return(IPEndpoint{{127, 0, 0, 1}, 12345}));
environment->SetSocketStateForTesting(Environment::SocketState::kReady);
return environment;
}
void SetUp() { SetUpWithConstraints(ReceiverConstraints{}); }
// Since constraints are constant throughout the life of a session,
// changing them requires configuring a new session.
void SetUpWithConstraints(ReceiverConstraints constraints) {
session_.reset();
message_port_ = std::make_unique<SimpleMessagePort>("sender-12345");
environment_ = MakeEnvironment();
session_ = std::make_unique<ReceiverSession>(
client_, *environment_, *message_port_, std::move(constraints));
}
protected:
void AssertGotAnErrorAnswerResponse() {
const std::vector<std::string>& messages = message_port_->posted_messages();
ASSERT_EQ(1u, messages.size());
ExpectIsErrorAnswer(messages[0]);
}
StrictMock<FakeClient> client_;
FakeClock clock_;
std::unique_ptr<MockEnvironment> environment_;
std::unique_ptr<SimpleMessagePort> message_port_;
std::unique_ptr<ReceiverSession> session_;
FakeTaskRunner task_runner_;
};
TEST_F(ReceiverSessionTest, CanNegotiateWithDefaultConstraints) {
InSequence s;
EXPECT_CALL(client_, OnNegotiated(session_.get(), _))
.WillOnce([](const ReceiverSession* session_,
ReceiverSession::ConfiguredReceivers cr) {
EXPECT_TRUE(cr.audio_receiver);
EXPECT_EQ(cr.audio_receiver->config().sender_ssrc, 19088747u);
EXPECT_EQ(cr.audio_receiver->config().receiver_ssrc, 19088748u);
EXPECT_EQ(cr.audio_receiver->config().channels, 2);
EXPECT_EQ(cr.audio_receiver->config().rtp_timebase, 48000);
// We should have chosen opus
EXPECT_EQ(cr.audio_config.codec, AudioCodec::kOpus);
EXPECT_TRUE(cr.video_receiver);
EXPECT_EQ(cr.video_receiver->config().sender_ssrc, 19088745u);
EXPECT_EQ(cr.video_receiver->config().receiver_ssrc, 19088746u);
EXPECT_EQ(cr.video_receiver->config().channels, 1);
EXPECT_EQ(cr.video_receiver->config().rtp_timebase, 90000);
// We should have chosen vp8
EXPECT_EQ(cr.video_config.codec, VideoCodec::kVp8);
// This should be from the default test sender ID.
EXPECT_EQ("sender-12345", cr.sender_id);
});
EXPECT_CALL(client_,
OnReceiversDestroying(session_.get(),
ReceiverSession::Client::kEndOfSession));
message_port_->ReceiveMessage(kValidOfferMessage);
const std::vector<std::string>& messages = message_port_->posted_messages();
ASSERT_EQ(1u, messages.size());
Json::Value message = ExpectIsValidAnswer(messages[0]);
EXPECT_EQ("ANSWER", message["type"].asString());
EXPECT_EQ(1337, message["seqNum"].asInt());
EXPECT_EQ("ok", message["result"].asString());
const Json::Value& answer = message["answer"];
EXPECT_TRUE(answer.isObject());
// Spot check the answer body fields. We have more in depth testing
// of answer behavior in answer_messages_unittest, but here we can
// ensure that the ReceiverSession properly configured the answer.
EXPECT_EQ(1337, answer["sendIndexes"][0].asInt());
EXPECT_EQ(31338, answer["sendIndexes"][1].asInt());
EXPECT_LT(0, answer["udpPort"].asInt());
EXPECT_GT(65535, answer["udpPort"].asInt());
// Constraints and display should not be present with no constraints.
EXPECT_TRUE(answer["constraints"].isNull());
EXPECT_TRUE(answer["display"].isNull());
}
TEST_F(ReceiverSessionTest, CanNegotiateWithCustomCodecConstraints) {
ReceiverSession session(
client_, *environment_, *message_port_,
ReceiverConstraints{{VideoCodec::kVp9}, {AudioCodec::kOpus}});
InSequence s;
EXPECT_CALL(client_, OnNegotiated(&session, _))
.WillOnce([](const ReceiverSession* session_,
ReceiverSession::ConfiguredReceivers cr) {
EXPECT_TRUE(cr.audio_receiver);
EXPECT_EQ(cr.audio_receiver->config().sender_ssrc, 19088747u);
EXPECT_EQ(cr.audio_receiver->config().receiver_ssrc, 19088748u);
EXPECT_EQ(cr.audio_receiver->config().channels, 2);
EXPECT_EQ(cr.audio_receiver->config().rtp_timebase, 48000);
EXPECT_EQ(cr.audio_config.codec, AudioCodec::kOpus);
EXPECT_TRUE(cr.video_receiver);
EXPECT_EQ(cr.video_receiver->config().sender_ssrc, 19088743u);
EXPECT_EQ(cr.video_receiver->config().receiver_ssrc, 19088744u);
EXPECT_EQ(cr.video_receiver->config().channels, 1);
EXPECT_EQ(cr.video_receiver->config().rtp_timebase, 90000);
EXPECT_EQ(cr.video_config.codec, VideoCodec::kVp9);
});
EXPECT_CALL(client_, OnReceiversDestroying(
&session, ReceiverSession::Client::kEndOfSession));
message_port_->ReceiveMessage(kValidOfferMessage);
}
TEST_F(ReceiverSessionTest, RejectsStreamWithUnsupportedCodecParameter) {
ReceiverConstraints constraints({VideoCodec::kHevc}, {AudioCodec::kOpus});
EXPECT_CALL(client_, SupportsCodecParameter(_)).WillRepeatedly(Return(false));
ReceiverSession session(client_, *environment_, *message_port_, constraints);
InSequence s;
EXPECT_CALL(client_, OnNegotiated(&session, _))
.WillOnce([](const ReceiverSession* session_,
ReceiverSession::ConfiguredReceivers cr) {
EXPECT_FALSE(cr.video_receiver);
});
EXPECT_CALL(client_, OnReceiversDestroying(
&session, ReceiverSession::Client::kEndOfSession));
message_port_->ReceiveMessage(kValidOfferMessage);
}
TEST_F(ReceiverSessionTest, AcceptsStreamWithNoCodecParameter) {
ReceiverConstraints constraints({VideoCodec::kHevc, VideoCodec::kVp9},
{AudioCodec::kOpus});
EXPECT_CALL(client_, SupportsCodecParameter(_)).WillRepeatedly(Return(false));
ReceiverSession session(client_, *environment_, *message_port_,
std::move(constraints));
InSequence s;
EXPECT_CALL(client_, OnNegotiated(&session, _))
.WillOnce([](const ReceiverSession* session_,
ReceiverSession::ConfiguredReceivers cr) {
EXPECT_TRUE(cr.video_receiver);
EXPECT_EQ(cr.video_config.codec, VideoCodec::kVp9);
});
EXPECT_CALL(client_, OnReceiversDestroying(
&session, ReceiverSession::Client::kEndOfSession));
message_port_->ReceiveMessage(kValidOfferMessage);
}
TEST_F(ReceiverSessionTest, AcceptsStreamWithMatchingParameter) {
ReceiverConstraints constraints({VideoCodec::kHevc}, {AudioCodec::kOpus});
EXPECT_CALL(client_, SupportsCodecParameter(_))
.WillRepeatedly(
[](const std::string& param) { return param == "hev1.1.6.L150.B0"; });
ReceiverSession session(client_, *environment_, *message_port_,
std::move(constraints));
InSequence s;
EXPECT_CALL(client_, OnNegotiated(&session, _))
.WillOnce([](const ReceiverSession* session_,
ReceiverSession::ConfiguredReceivers cr) {
EXPECT_TRUE(cr.video_receiver);
EXPECT_EQ(cr.video_config.codec, VideoCodec::kHevc);
});
EXPECT_CALL(client_, OnReceiversDestroying(
&session, ReceiverSession::Client::kEndOfSession));
message_port_->ReceiveMessage(kValidOfferMessage);
}
TEST_F(ReceiverSessionTest, CanNegotiateWithLimits) {
std::vector<AudioLimits> audio_limits = {
{false, AudioCodec::kOpus, 48001, 2, 32001, 32002, milliseconds(3001)}};
std::vector<VideoLimits> video_limits = {{true,
VideoCodec::kVp9,
62208000,
{1920, 1080, {144, 1}},
300000,
90000000,
milliseconds(1000)}};
auto display = std::make_unique<Display>(
Display{{640, 480, {60, 1}}, false /* can scale content */});
ReceiverSession session(client_, *environment_, *message_port_,
ReceiverConstraints{{VideoCodec::kVp9},
{AudioCodec::kOpus},
std::move(audio_limits),
std::move(video_limits),
std::move(display)});
InSequence s;
EXPECT_CALL(client_, OnNegotiated(&session, _));
EXPECT_CALL(client_, OnReceiversDestroying(
&session, ReceiverSession::Client::kEndOfSession));
message_port_->ReceiveMessage(kValidOfferMessage);
const std::vector<std::string>& messages = message_port_->posted_messages();
ASSERT_EQ(1u, messages.size());
Json::Value message_body = ExpectIsValidAnswer(messages[0]);
const Json::Value& answer_json = message_body["answer"];
ASSERT_TRUE(answer_json.isObject()) << messages[0];
// Constraints and display should be valid with valid constraints.
ASSERT_FALSE(answer_json["constraints"].isNull());
ASSERT_FALSE(answer_json["display"].isNull());
const Json::Value& display_json = answer_json["display"];
EXPECT_EQ("60", display_json["dimensions"]["frameRate"].asString());
EXPECT_EQ(640, display_json["dimensions"]["width"].asInt());
EXPECT_EQ(480, display_json["dimensions"]["height"].asInt());
EXPECT_EQ("sender", display_json["scaling"].asString());
const Json::Value& constraints_json = answer_json["constraints"];
ASSERT_TRUE(constraints_json.isObject());
const Json::Value& audio = constraints_json["audio"];
ASSERT_TRUE(audio.isObject());
EXPECT_EQ(32002, audio["maxBitRate"].asInt());
EXPECT_EQ(2, audio["maxChannels"].asInt());
EXPECT_EQ(3001, audio["maxDelay"].asInt());
EXPECT_EQ(48001, audio["maxSampleRate"].asInt());
EXPECT_EQ(32001, audio["minBitRate"].asInt());
const Json::Value& video = constraints_json["video"];
ASSERT_TRUE(video.isObject());
EXPECT_EQ(90000000, video["maxBitRate"].asInt());
EXPECT_EQ(1000, video["maxDelay"].asInt());
EXPECT_EQ("144", video["maxDimensions"]["frameRate"].asString());
EXPECT_EQ(1920, video["maxDimensions"]["width"].asInt());
EXPECT_EQ(1080, video["maxDimensions"]["height"].asInt());
EXPECT_EQ(300000, video["minBitRate"].asInt());
}
TEST_F(ReceiverSessionTest, HandlesNoValidAudioStream) {
InSequence s;
EXPECT_CALL(client_, OnNegotiated(session_.get(), _));
EXPECT_CALL(client_,
OnReceiversDestroying(session_.get(),
ReceiverSession::Client::kEndOfSession));
message_port_->ReceiveMessage(kNoAudioOfferMessage);
const std::vector<std::string>& messages = message_port_->posted_messages();
EXPECT_EQ(1u, messages.size());
Json::Value answer = ExpectIsValidAnswer(messages[0])["answer"];
// Should still select video stream.
EXPECT_EQ(1u, answer["sendIndexes"].size());
EXPECT_EQ(31338, answer["sendIndexes"][0].asInt());
EXPECT_EQ(1u, answer["ssrcs"].size());
EXPECT_EQ(19088746, answer["ssrcs"][0].asInt());
}
TEST_F(ReceiverSessionTest, HandlesInvalidCodec) {
// We didn't select any streams, but didn't have any errors either.
message_port_->ReceiveMessage(kInvalidCodecOfferMessage);
const std::vector<std::string>& messages = message_port_->posted_messages();
EXPECT_EQ(1u, messages.size());
// We should have failed to produce a valid answer message due to not
// selecting any stream.
AssertGotAnErrorAnswerResponse();
}
TEST_F(ReceiverSessionTest, HandlesNoValidVideoStream) {
InSequence s;
EXPECT_CALL(client_, OnNegotiated(session_.get(), _));
EXPECT_CALL(client_,
OnReceiversDestroying(session_.get(),
ReceiverSession::Client::kEndOfSession));
message_port_->ReceiveMessage(kNoVideoOfferMessage);
const std::vector<std::string>& messages = message_port_->posted_messages();
EXPECT_EQ(1u, messages.size());
const Json::Value answer = ExpectIsValidAnswer(messages[0])["answer"];
// Should still select audio stream.
EXPECT_EQ(1u, answer["sendIndexes"].size());
EXPECT_EQ(1337, answer["sendIndexes"][0].asInt());
EXPECT_EQ(1u, answer["ssrcs"].size());
EXPECT_EQ(19088748, answer["ssrcs"][0].asInt());
}
TEST_F(ReceiverSessionTest, RejectsOfferIfNewOneComesBeforeNegotiationIsDone) {
InSequence s;
EXPECT_CALL(client_, OnNegotiated(session_.get(),
NegotiatedWithSender("first-sender")));
EXPECT_CALL(client_,
OnReceiversDestroying(session_.get(),
ReceiverSession::Client::kEndOfSession));
// If the socket state is pending we don't process OFFERs yet.
environment_->SetSocketStateForTesting(Environment::SocketState::kStarting);
message_port_->ReceiveMessage("first-sender", kCastWebrtcNamespace,
kValidOfferMessage);
EXPECT_EQ(0u, message_port_->posted_messages().size());
// We should process the first OFFER now that the second one has come in.
message_port_->ReceiveMessage("first-sender", kCastWebrtcNamespace,
kValidOfferMessage);
EXPECT_EQ(1u, message_port_->posted_messages().size());
ExpectIsErrorAnswer(message_port_->posted_messages()[0]);
environment_->SetSocketStateForTesting(Environment::SocketState::kReady);
EXPECT_EQ(2u, message_port_->posted_messages().size());
ExpectIsValidAnswer(message_port_->posted_messages()[1]);
}
TEST_F(ReceiverSessionTest, HandlesRenegotiationFromSameSender) {
InSequence s;
EXPECT_CALL(client_, OnNegotiated(session_.get(),
NegotiatedWithSender("first-sender")));
EXPECT_CALL(client_,
OnReceiversDestroying(session_.get(),
ReceiverSession::Client::kRenegotiated));
EXPECT_CALL(client_, OnNegotiated(session_.get(),
NegotiatedWithSender("first-sender")));
EXPECT_CALL(client_,
OnReceiversDestroying(session_.get(),
ReceiverSession::Client::kEndOfSession));
message_port_->ReceiveMessage("first-sender", kCastWebrtcNamespace,
kValidOfferMessage);
EXPECT_EQ(1u, message_port_->posted_messages().size());
ExpectIsValidAnswer(message_port_->posted_messages()[0]);
message_port_->ReceiveMessage("first-sender", kCastWebrtcNamespace,
kValidOfferMessage);
EXPECT_EQ(2u, message_port_->posted_messages().size());
ExpectIsValidAnswer(message_port_->posted_messages()[1]);
}
TEST_F(ReceiverSessionTest, HandlesRenegotiationFromAnotherSender) {
InSequence s;
EXPECT_CALL(client_, OnNegotiated(session_.get(),
NegotiatedWithSender("first-sender")));
EXPECT_CALL(client_,
OnReceiversDestroying(session_.get(),
ReceiverSession::Client::kRenegotiated));
EXPECT_CALL(client_, OnNegotiated(session_.get(),
NegotiatedWithSender("some-other-sender")));
EXPECT_CALL(client_,
OnReceiversDestroying(session_.get(),
ReceiverSession::Client::kEndOfSession));
message_port_->ReceiveMessage("first-sender", kCastWebrtcNamespace,
kValidOfferMessage);
EXPECT_EQ(1u, message_port_->posted_messages().size());
ExpectIsValidAnswer(message_port_->posted_messages()[0]);
message_port_->ReceiveMessage("some-other-sender", kCastWebrtcNamespace,
kValidOfferMessage);
EXPECT_EQ(2u, message_port_->posted_messages().size());
ExpectIsValidAnswer(message_port_->posted_messages()[1]);
}
TEST_F(ReceiverSessionTest, HandlesErrorOfferFromAnotherSender) {
InSequence s;
EXPECT_CALL(client_, OnNegotiated(session_.get(),
NegotiatedWithSender("first-sender")));
EXPECT_CALL(client_,
OnReceiversDestroying(session_.get(),
ReceiverSession::Client::kEndOfSession));
message_port_->ReceiveMessage("first-sender", kCastWebrtcNamespace,
kValidOfferMessage);
EXPECT_EQ(1u, message_port_->posted_messages().size());
ExpectIsValidAnswer(message_port_->posted_messages()[0]);
message_port_->ReceiveMessage("some-other-sender", kCastWebrtcNamespace,
kInvalidCodecOfferMessage);
EXPECT_EQ(2u, message_port_->posted_messages().size());
ExpectIsErrorAnswer(message_port_->posted_messages()[1]);
}
TEST_F(ReceiverSessionTest, HandlesNoValidStreams) {
// We shouldn't call OnNegotiated if we failed to negotiate any streams.
message_port_->ReceiveMessage(kNoAudioOrVideoOfferMessage);
AssertGotAnErrorAnswerResponse();
}
TEST_F(ReceiverSessionTest, HandlesMalformedOffer) {
// Note that unlike when we simply don't select any streams, when the offer
// is not valid JSON we actually have no way of knowing it's an offer at all,
// so we call OnError and do not reply with an Answer.
EXPECT_CALL(client_, OnError(session_.get(), _));
message_port_->ReceiveMessage(kInvalidJsonOfferMessage);
}
TEST_F(ReceiverSessionTest, HandlesMissingSeqNumInOffer) {
// If the OFFER is missing a sequence number it gets rejected before being
// parsed as an OFFER, since the sender expects all messages to come back
// with a sequence number.
message_port_->ReceiveMessage(kMissingSeqNumOfferMessage);
}
TEST_F(ReceiverSessionTest, HandlesOfferMissingMandatoryFields) {
// If the OFFER is missing mandatory fields, we notify the client as well as
// reply with an error-case Answer.
EXPECT_CALL(client_, OnError(session_.get(), _));
message_port_->ReceiveMessage(kMissingMandatoryFieldOfferMessage);
AssertGotAnErrorAnswerResponse();
}
TEST_F(ReceiverSessionTest, HandlesImproperlyFormattedOffer) {
EXPECT_CALL(client_, OnError(session_.get(), _));
message_port_->ReceiveMessage(kValidJsonInvalidFormatOfferMessage);
AssertGotAnErrorAnswerResponse();
}
TEST_F(ReceiverSessionTest, HandlesNullOffer) {
EXPECT_CALL(client_, OnError(session_.get(), _));
message_port_->ReceiveMessage(kNullJsonOfferMessage);
AssertGotAnErrorAnswerResponse();
}
TEST_F(ReceiverSessionTest, HandlesInvalidSequenceNumber) {
// We should just discard messages with an invalid sequence number.
message_port_->ReceiveMessage(kInvalidSequenceNumberMessage);
}
TEST_F(ReceiverSessionTest, HandlesUnknownTypeMessage) {
// We should just discard messages with an unknown message type.
message_port_->ReceiveMessage(kUnknownTypeMessage);
}
TEST_F(ReceiverSessionTest, HandlesInvalidTypeMessage) {
// We should just discard messages with an invalid message type.
message_port_->ReceiveMessage(kInvalidTypeMessage);
}
TEST_F(ReceiverSessionTest, DoesNotCrashOnMessagePortError) {
// We should report message port errors.
EXPECT_CALL(client_, OnError(session_.get(), _));
message_port_->ReceiveError(Error(Error::Code::kUnknownError));
}
TEST_F(ReceiverSessionTest, NotifiesReceiverDestruction) {
InSequence s;
EXPECT_CALL(client_, OnNegotiated(session_.get(), _));
EXPECT_CALL(client_,
OnReceiversDestroying(session_.get(),
ReceiverSession::Client::kRenegotiated));
EXPECT_CALL(client_, OnNegotiated(session_.get(), _));
EXPECT_CALL(client_,
OnReceiversDestroying(session_.get(),
ReceiverSession::Client::kEndOfSession));
message_port_->ReceiveMessage(kNoAudioOfferMessage);
message_port_->ReceiveMessage(kValidOfferMessage);
}
TEST_F(ReceiverSessionTest, HandlesInvalidAnswer) {
// Simulate an unbound local endpoint.
EXPECT_CALL(*environment_, GetBoundLocalEndpoint)
.WillOnce(Return(IPEndpoint{}));
message_port_->ReceiveMessage(kValidOfferMessage);
AssertGotAnErrorAnswerResponse();
}
TEST_F(ReceiverSessionTest, DelaysAnswerUntilEnvironmentIsReady) {
environment_->SetSocketStateForTesting(Environment::SocketState::kStarting);
// We should not have sent an answer yet--the UDP socket is not ready.
message_port_->ReceiveMessage(kValidOfferMessage);
ASSERT_TRUE(message_port_->posted_messages().empty());
// Simulate the environment calling back into us with the socket being ready.
// state() will not be called again--we just need to get the bind event.
EXPECT_CALL(*environment_, GetBoundLocalEndpoint())
.WillOnce(Return(IPEndpoint{{10, 0, 0, 2}, 4567}));
EXPECT_CALL(client_, OnNegotiated(session_.get(), _));
EXPECT_CALL(client_,
OnReceiversDestroying(session_.get(),
ReceiverSession::Client::kEndOfSession));
session_->OnSocketReady();
const std::vector<std::string>& messages = message_port_->posted_messages();
ASSERT_EQ(1u, messages.size());
// We should have set the UDP port based on the ready socket value.
const Json::Value& message_body = ExpectIsValidAnswer(messages[0]);
EXPECT_EQ(4567, message_body["answer"]["udpPort"].asInt());
}
TEST_F(ReceiverSessionTest,
ReturnsErrorAnswerIfEnvironmentIsAlreadyInvalidated) {
EXPECT_CALL(client_, OnError(session_.get(), _));
environment_->SetSocketStateForTesting(Environment::SocketState::kInvalid);
// If the environment is already in a bad state, we can respond immediately.
message_port_->ReceiveMessage(kValidOfferMessage);
AssertGotAnErrorAnswerResponse();
}
TEST_F(ReceiverSessionTest, ReturnsErrorAnswerIfEnvironmentIsInvalidated) {
EXPECT_CALL(client_, OnError(session_.get(), _));
environment_->SetSocketStateForTesting(Environment::SocketState::kStarting);
// We should not have sent an answer yet--the environment is not ready.
message_port_->ReceiveMessage(kValidOfferMessage);
ASSERT_TRUE(message_port_->posted_messages().empty());
// Simulate the environment calling back into us with invalidation.
environment_->SetSocketStateForTesting(Environment::SocketState::kInvalid);
AssertGotAnErrorAnswerResponse();
}
TEST_F(ReceiverSessionTest, ReturnsErrorCapabilitiesIfRemotingDisabled) {
message_port_->ReceiveMessage(kGetCapabilitiesMessage);
const std::vector<std::string>& messages = message_port_->posted_messages();
ASSERT_EQ(1u, messages.size());
// We should have an error response.
ErrorOr<Json::Value> message_body = json::Parse(messages[0]);
EXPECT_TRUE(message_body.is_value());
EXPECT_EQ("CAPABILITIES_RESPONSE", message_body.value()["type"].asString());
EXPECT_EQ("error", message_body.value()["result"].asString());
}
TEST_F(ReceiverSessionTest, ReturnsCapabilitiesWithRemotingDefaults) {
ReceiverConstraints constraints;
constraints.remoting = std::make_unique<RemotingConstraints>();
SetUpWithConstraints(std::move(constraints));
message_port_->ReceiveMessage(kGetCapabilitiesMessage);
const std::vector<std::string>& messages = message_port_->posted_messages();
ASSERT_EQ(1u, messages.size());
const ErrorOr<Json::Value> message_body = json::Parse(messages[0]);
EXPECT_TRUE(message_body.is_value());
EXPECT_EQ("CAPABILITIES_RESPONSE", message_body.value()["type"].asString());
EXPECT_EQ("ok", message_body.value()["result"].asString());
const ReceiverCapability response =
ReceiverCapability::Parse(message_body.value()["capabilities"]).value();
EXPECT_THAT(
response.media_capabilities,
testing::ElementsAre(MediaCapability::kOpus, MediaCapability::kAac,
MediaCapability::kVp8, MediaCapability::kH264));
}
TEST_F(ReceiverSessionTest, ReturnsCapabilitiesWithRemotingConstraints) {
ReceiverConstraints constraints;
constraints.video_codecs = {VideoCodec::kH264};
constraints.remoting = std::make_unique<RemotingConstraints>();
constraints.remoting->supports_chrome_audio_codecs = true;
constraints.remoting->supports_4k = true;
SetUpWithConstraints(std::move(constraints));
message_port_->ReceiveMessage(kGetCapabilitiesMessage);
const std::vector<std::string>& messages = message_port_->posted_messages();
ASSERT_EQ(1u, messages.size());
const ErrorOr<Json::Value> message_body = json::Parse(messages[0]);
EXPECT_TRUE(message_body.is_value());
EXPECT_EQ("CAPABILITIES_RESPONSE", message_body.value()["type"].asString());
EXPECT_EQ("ok", message_body.value()["result"].asString());
const ReceiverCapability response =
ReceiverCapability::Parse(message_body.value()["capabilities"]).value();
EXPECT_THAT(
response.media_capabilities,
testing::ElementsAre(MediaCapability::kOpus, MediaCapability::kAac,
MediaCapability::kH264, MediaCapability::kAudio,
MediaCapability::k4k));
}
TEST_F(ReceiverSessionTest, CanNegotiateRemoting) {
ReceiverConstraints constraints;
constraints.remoting = std::make_unique<RemotingConstraints>();
constraints.remoting->supports_chrome_audio_codecs = true;
constraints.remoting->supports_4k = true;
SetUpWithConstraints(std::move(constraints));
InSequence s;
EXPECT_CALL(client_, OnRemotingNegotiated(session_.get(), _))
.WillOnce([](const ReceiverSession* session_,
ReceiverSession::RemotingNegotiation negotiation) {
const ReceiverSession::ConfiguredReceivers& cr = negotiation.receivers;
EXPECT_TRUE(cr.audio_receiver);
EXPECT_EQ(cr.audio_receiver->config().sender_ssrc, 19088747u);
EXPECT_EQ(cr.audio_receiver->config().receiver_ssrc, 19088748u);
EXPECT_EQ(cr.audio_receiver->config().channels, 2);
EXPECT_EQ(cr.audio_receiver->config().rtp_timebase, 48000);
EXPECT_EQ(cr.audio_config.codec, AudioCodec::kNotSpecified);
EXPECT_TRUE(cr.video_receiver);
EXPECT_EQ(cr.video_receiver->config().sender_ssrc, 19088745u);
EXPECT_EQ(cr.video_receiver->config().receiver_ssrc, 19088746u);
EXPECT_EQ(cr.video_receiver->config().channels, 1);
EXPECT_EQ(cr.video_receiver->config().rtp_timebase, 90000);
EXPECT_EQ(cr.video_config.codec, VideoCodec::kNotSpecified);
});
EXPECT_CALL(client_,
OnReceiversDestroying(session_.get(),
ReceiverSession::Client::kEndOfSession));
message_port_->ReceiveMessage(kValidRemotingOfferMessage);
}
TEST_F(ReceiverSessionTest, HandlesRpcMessage) {
ReceiverConstraints constraints;
constraints.remoting = std::make_unique<RemotingConstraints>();
constraints.remoting->supports_chrome_audio_codecs = true;
constraints.remoting->supports_4k = true;
SetUpWithConstraints(std::move(constraints));
message_port_->ReceiveMessage(kRpcMessage);
const std::vector<std::string>& messages = message_port_->posted_messages();
// Nothing should happen yet, the session doesn't have a messenger.
ASSERT_EQ(0u, messages.size());
// We don't need to fully test that the subscription model on the RpcMessenger
// works, but we do want to test that the ReceiverSession has properly wired
// the RpcMessenger up to the backing SessionMessenger and can properly
// handle received RPC messages.
InSequence s;
bool received_initialize_message = false;
EXPECT_CALL(client_, OnRemotingNegotiated(session_.get(), _))
.WillOnce([this, &received_initialize_message](
const ReceiverSession* session_,
ReceiverSession::RemotingNegotiation negotiation) mutable {
negotiation.messenger->RegisterMessageReceiverCallback(
100, [&received_initialize_message](
std::unique_ptr<RpcMessage> message) mutable {
ASSERT_EQ(100, message->handle());
ASSERT_EQ(RpcMessage::RPC_DS_INITIALIZE_CALLBACK,
message->proc());
ASSERT_EQ(0, message->integer_value());
received_initialize_message = true;
});
message_port_->ReceiveMessage(kRpcMessage);
});
EXPECT_CALL(client_,
OnReceiversDestroying(session_.get(),
ReceiverSession::Client::kEndOfSession));
message_port_->ReceiveMessage(kValidRemotingOfferMessage);
ASSERT_TRUE(received_initialize_message);
}
TEST_F(ReceiverSessionTest, EnablesDscpInAnswer) {
ReceiverConstraints constraints;
constraints.enable_dscp = true;
SetUpWithConstraints(std::move(constraints));
EXPECT_CALL(client_, OnNegotiated(session_.get(), _));
EXPECT_CALL(client_,
OnReceiversDestroying(session_.get(),
ReceiverSession::Client::kEndOfSession));
message_port_->ReceiveMessage(kValidOfferMessage);
const std::vector<std::string>& messages = message_port_->posted_messages();
ASSERT_EQ(1u, messages.size());
Json::Value message = ExpectIsValidAnswer(messages[0]);
const Json::Value& answer = message["answer"];
ASSERT_TRUE(answer.isObject());
const Json::Value& dscp = answer["receiverRtcpDscp"];
ASSERT_FALSE(dscp.empty());
EXPECT_EQ(dscp[0], 1337);
EXPECT_EQ(dscp[1], 31338);
message_port_->clear();
ReceiverConstraints constraints2;
constraints2.enable_dscp = false;
SetUpWithConstraints(std::move(constraints2));
EXPECT_CALL(client_, OnNegotiated(session_.get(), _));
EXPECT_CALL(client_,
OnReceiversDestroying(session_.get(),
ReceiverSession::Client::kEndOfSession));
message_port_->ReceiveMessage(kValidOfferMessage); // Re-send offer message
const std::vector<std::string>& messages2 = message_port_->posted_messages();
ASSERT_EQ(1u, messages2.size());
message = ExpectIsValidAnswer(messages2[0]);
const Json::Value& answer2 = message["answer"];
ASSERT_TRUE(answer2.isObject());
const Json::Value& dscp2 = answer2["receiverRtcpDscp"];
ASSERT_TRUE(dscp2.empty());
}
TEST_F(ReceiverSessionTest, InputEventsOptIn) {
ReceiverConstraints constraints;
constraints.supports_input_events = true;
SetUpWithConstraints(std::move(constraints));
EXPECT_CALL(client_, OnNegotiated(session_.get(), _))
.WillOnce([](const ReceiverSession* session,
ReceiverSession::ConfiguredReceivers cr) {
EXPECT_TRUE(cr.input_enabled);
});
EXPECT_CALL(client_,
OnReceiversDestroying(session_.get(),
ReceiverSession::Client::kEndOfSession));
message_port_->ReceiveMessage(kValidOfferMessageWithInput);
const std::vector<std::string>& messages = message_port_->posted_messages();
ASSERT_EQ(1u, messages.size());
Json::Value message = ExpectIsValidAnswer(messages[0]);
const Json::Value& answer = message["answer"];
bool found_extension = false;
for (const auto& ext : answer["rtpExtensions"]) {
if (ext.asString() == "input_events") {
found_extension = true;
break;
}
}
EXPECT_TRUE(found_extension);
}
TEST_F(ReceiverSessionTest, HandlesInputMessage) {
ReceiverConstraints constraints;
constraints.supports_input_events = true;
SetUpWithConstraints(std::move(constraints));
message_port_->ReceiveMessage(kInputMessage);
// Nothing should happen yet, the session doesn't have a messenger.
ASSERT_EQ(0u, message_port_->posted_messages().size());
InSequence s;
bool received_negotiation = false;
EXPECT_CALL(client_, OnNegotiated(session_.get(), _))
.WillOnce([&received_negotiation](
const ReceiverSession* session,
ReceiverSession::ConfiguredReceivers receivers) mutable {
ASSERT_TRUE(receivers.input_enabled);
received_negotiation = true;
});
EXPECT_CALL(client_,
OnReceiversDestroying(session_.get(),
ReceiverSession::Client::kEndOfSession));
message_port_->ReceiveMessage(kValidOfferMessageWithInput);
ASSERT_TRUE(received_negotiation);
}
TEST_F(ReceiverSessionTest, HandlesInputMessengerNotNegotiated) {
ReceiverConstraints constraints;
constraints.supports_input_events = false;
SetUpWithConstraints(std::move(constraints));
EXPECT_CALL(client_, OnNegotiated(session_.get(), _))
.WillOnce([](const ReceiverSession* session,
ReceiverSession::ConfiguredReceivers cr) {
EXPECT_FALSE(cr.input_enabled);
});
EXPECT_CALL(client_,
OnReceiversDestroying(session_.get(),
ReceiverSession::Client::kEndOfSession));
message_port_->ReceiveMessage(kValidOfferMessageWithInput);
}
TEST_F(ReceiverSessionTest, HandlesInputMessengerSendsMessage) {
ReceiverConstraints constraints;
constraints.supports_input_events = true;
SetUpWithConstraints(std::move(constraints));
EXPECT_CALL(client_, OnNegotiated(session_.get(), _))
.WillOnce([this](const ReceiverSession* session,
ReceiverSession::ConfiguredReceivers cr) {
ASSERT_TRUE(cr.input_enabled);
InputMessage message;
auto* event = message.add_events();
event->set_type(InputMessage::INPUT_TYPE_KEY_DOWN);
this->session_->SendInputMessage(message);
});
EXPECT_CALL(client_,
OnReceiversDestroying(session_.get(),
ReceiverSession::Client::kEndOfSession));
message_port_->ReceiveMessage(kValidOfferMessageWithInput);
// Verify message was sent through message port.
const auto& messages = message_port_->posted_messages();
// We expect at least 2 messages: ANSWER and INPUT.
ASSERT_GE(messages.size(), 2u);
// Index 0 is ANSWER.
ExpectIsValidAnswer(messages[0]);
// Index 1 should be INPUT.
ErrorOr<Json::Value> input_json = json::Parse(messages[1]);
ASSERT_TRUE(input_json.is_value());
EXPECT_EQ("INPUT", input_json.value()["type"].asString());
EXPECT_FALSE(input_json.value()["input"].asString().empty());
}
TEST_F(ReceiverSessionTest, HandlesInputMessengerReceivesMessage) {
ReceiverConstraints constraints;
constraints.supports_input_events = true;
SetUpWithConstraints(std::move(constraints));
bool received_input = false;
session_->SetInputCallback(
[&received_input](InputMessage message) { received_input = true; });
EXPECT_CALL(client_, OnNegotiated(session_.get(), _))
.WillOnce([](const ReceiverSession* session,
ReceiverSession::ConfiguredReceivers cr) {
ASSERT_TRUE(cr.input_enabled);
});
message_port_->ReceiveMessage(kValidOfferMessageWithInput);
message_port_->ReceiveMessage(kInputMessage);
ASSERT_TRUE(received_input);
EXPECT_CALL(client_,
OnReceiversDestroying(session_.get(),
ReceiverSession::Client::kEndOfSession));
}
} // namespace openscreen::cast