| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/mirroring/service/message_dispatcher.h" |
| |
| #include "base/base64.h" |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/macros.h" |
| #include "base/run_loop.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::InvokeWithoutArgs; |
| using ::testing::_; |
| using mirroring::mojom::CastMessage; |
| using mirroring::mojom::CastMessagePtr; |
| |
| namespace mirroring { |
| |
| namespace { |
| |
| bool IsEqual(const CastMessage& message1, const CastMessage& message2) { |
| return message1.message_namespace == message2.message_namespace && |
| message1.json_format_data == message2.json_format_data; |
| } |
| |
| void CloneResponse(const ReceiverResponse& response, |
| ReceiverResponse* cloned_response) { |
| cloned_response->type = response.type; |
| cloned_response->session_id = response.session_id; |
| cloned_response->sequence_number = response.sequence_number; |
| cloned_response->result = response.result; |
| if (response.answer) |
| cloned_response->answer = std::make_unique<Answer>(*response.answer); |
| if (response.status) |
| cloned_response->status = |
| std::make_unique<ReceiverStatus>(*response.status); |
| if (response.capabilities) { |
| cloned_response->capabilities = |
| std::make_unique<ReceiverCapability>(*response.capabilities); |
| } |
| cloned_response->rpc = response.rpc; |
| if (response.error) { |
| cloned_response->error = std::make_unique<ReceiverError>(); |
| cloned_response->error->code = response.error->code; |
| cloned_response->error->description = response.error->description; |
| cloned_response->error->details = response.error->details; |
| } |
| } |
| |
| } // namespace |
| |
| class MessageDispatcherTest : public mojom::CastMessageChannel, |
| public ::testing::Test { |
| public: |
| MessageDispatcherTest() : binding_(this) { |
| mojom::CastMessageChannelPtr outbound_channel; |
| binding_.Bind(mojo::MakeRequest(&outbound_channel)); |
| message_dispatcher_ = std::make_unique<MessageDispatcher>( |
| std::move(outbound_channel), mojo::MakeRequest(&inbound_channel_), |
| base::BindRepeating(&MessageDispatcherTest::OnParsingError, |
| base::Unretained(this))); |
| message_dispatcher_->Subscribe( |
| ResponseType::ANSWER, |
| base::BindRepeating(&MessageDispatcherTest::OnAnswerResponse, |
| base::Unretained(this))); |
| message_dispatcher_->Subscribe( |
| ResponseType::STATUS_RESPONSE, |
| base::BindRepeating(&MessageDispatcherTest::OnStatusResponse, |
| base::Unretained(this))); |
| } |
| ~MessageDispatcherTest() override { scoped_task_environment_.RunUntilIdle(); } |
| |
| void OnParsingError(const std::string& error_message) { |
| last_error_message_ = error_message; |
| } |
| |
| void OnAnswerResponse(const ReceiverResponse& response) { |
| if (!last_answer_response_) |
| last_answer_response_ = std::make_unique<ReceiverResponse>(); |
| CloneResponse(response, last_answer_response_.get()); |
| } |
| |
| void OnStatusResponse(const ReceiverResponse& response) { |
| if (!last_status_response_) |
| last_status_response_ = std::make_unique<ReceiverResponse>(); |
| CloneResponse(response, last_status_response_.get()); |
| } |
| |
| protected: |
| // mojom::CastMessageChannel implementation. Handles outbound message. |
| void Send(mojom::CastMessagePtr message) override { |
| last_outbound_message_.message_namespace = message->message_namespace; |
| last_outbound_message_.json_format_data = message->json_format_data; |
| } |
| |
| // Simulates receiving an inbound message from receiver. |
| void SendInboundMessage(const mojom::CastMessage& message) { |
| inbound_channel_->Send(message.Clone()); |
| } |
| |
| base::test::ScopedTaskEnvironment scoped_task_environment_; |
| std::unique_ptr<MessageDispatcher> message_dispatcher_; |
| CastMessage last_outbound_message_; |
| std::string last_error_message_; |
| std::unique_ptr<ReceiverResponse> last_answer_response_; |
| std::unique_ptr<ReceiverResponse> last_status_response_; |
| |
| private: |
| mojo::Binding<mojom::CastMessageChannel> binding_; |
| mojom::CastMessageChannelPtr inbound_channel_; |
| DISALLOW_COPY_AND_ASSIGN(MessageDispatcherTest); |
| }; |
| |
| TEST_F(MessageDispatcherTest, SendsOutboundMessage) { |
| const std::string test1 = "{\"a\": 1, \"b\": 2}"; |
| const CastMessage message1 = CastMessage{mojom::kWebRtcNamespace, test1}; |
| message_dispatcher_->SendOutboundMessage(message1.Clone()); |
| scoped_task_environment_.RunUntilIdle(); |
| EXPECT_TRUE(IsEqual(message1, last_outbound_message_)); |
| EXPECT_TRUE(last_error_message_.empty()); |
| |
| const std::string test2 = "{\"m\": 99, \"i\": 98, \"u\": 97}"; |
| const CastMessage message2 = CastMessage{mojom::kWebRtcNamespace, test2}; |
| message_dispatcher_->SendOutboundMessage(message2.Clone()); |
| scoped_task_environment_.RunUntilIdle(); |
| EXPECT_TRUE(IsEqual(message2, last_outbound_message_)); |
| EXPECT_TRUE(last_error_message_.empty()); |
| } |
| |
| TEST_F(MessageDispatcherTest, DispatchMessageToSubscriber) { |
| // Simulate a receiver ANSWER response and expect that just the ANSWER |
| // subscriber processes the message. |
| const std::string answer_response = |
| "{\"type\":\"ANSWER\",\"seqNum\":12345,\"result\":\"ok\"," |
| "\"answer\":{\"udpPort\":50691}}"; |
| const CastMessage answer_message = |
| CastMessage{mojom::kWebRtcNamespace, answer_response}; |
| SendInboundMessage(answer_message); |
| scoped_task_environment_.RunUntilIdle(); |
| ASSERT_TRUE(last_answer_response_); |
| EXPECT_FALSE(last_status_response_); |
| EXPECT_EQ(12345, last_answer_response_->sequence_number); |
| EXPECT_EQ(ResponseType::ANSWER, last_answer_response_->type); |
| EXPECT_EQ("ok", last_answer_response_->result); |
| EXPECT_EQ(50691, last_answer_response_->answer->udp_port); |
| EXPECT_FALSE(last_answer_response_->status); |
| last_answer_response_.reset(); |
| EXPECT_TRUE(last_error_message_.empty()); |
| |
| // Simulate a receiver STATUS_RESPONSE and expect that just the |
| // STATUS_RESPONSE subscriber processes the message. |
| const std::string status_response = |
| "{\"type\":\"STATUS_RESPONSE\",\"seqNum\":12345,\"result\":\"ok\"," |
| "\"status\":{\"wifiSnr\":42}}"; |
| const CastMessage status_message = |
| CastMessage{mojom::kWebRtcNamespace, status_response}; |
| SendInboundMessage(status_message); |
| scoped_task_environment_.RunUntilIdle(); |
| EXPECT_FALSE(last_answer_response_); |
| ASSERT_TRUE(last_status_response_); |
| EXPECT_EQ(12345, last_status_response_->sequence_number); |
| EXPECT_EQ(ResponseType::STATUS_RESPONSE, last_status_response_->type); |
| EXPECT_EQ("ok", last_status_response_->result); |
| EXPECT_EQ(42, last_status_response_->status->wifi_snr); |
| last_status_response_.reset(); |
| EXPECT_TRUE(last_error_message_.empty()); |
| |
| // Unsubscribe from ANSWER messages, and when feeding-in an ANSWER message, |
| // nothing should happen. |
| message_dispatcher_->Unsubscribe(ResponseType::ANSWER); |
| SendInboundMessage(answer_message); |
| scoped_task_environment_.RunUntilIdle(); |
| EXPECT_FALSE(last_answer_response_); |
| EXPECT_FALSE(last_status_response_); |
| EXPECT_FALSE(last_error_message_.empty()); // Expect an error reported. |
| last_error_message_.clear(); |
| |
| // However, STATUS_RESPONSE messages should still be dispatcher to the |
| // remaining subscriber. |
| SendInboundMessage(status_message); |
| scoped_task_environment_.RunUntilIdle(); |
| EXPECT_FALSE(last_answer_response_); |
| EXPECT_TRUE(last_status_response_); |
| last_status_response_.reset(); |
| EXPECT_TRUE(last_error_message_.empty()); |
| |
| // Finally, unsubscribe from STATUS_RESPONSE messages, and when feeding-in |
| // either an ANSWER or a STATUS_RESPONSE message, nothing should happen. |
| message_dispatcher_->Unsubscribe(ResponseType::STATUS_RESPONSE); |
| SendInboundMessage(answer_message); |
| scoped_task_environment_.RunUntilIdle(); |
| EXPECT_FALSE(last_answer_response_); |
| EXPECT_FALSE(last_status_response_); |
| EXPECT_FALSE(last_error_message_.empty()); |
| last_error_message_.clear(); |
| SendInboundMessage(status_message); |
| scoped_task_environment_.RunUntilIdle(); |
| EXPECT_FALSE(last_answer_response_); |
| EXPECT_FALSE(last_status_response_); |
| EXPECT_FALSE(last_error_message_.empty()); |
| } |
| |
| TEST_F(MessageDispatcherTest, IgnoreMalformedMessage) { |
| const CastMessage message = |
| CastMessage{mojom::kWebRtcNamespace, "MUAHAHAHAHAHAHAHA!"}; |
| SendInboundMessage(message); |
| scoped_task_environment_.RunUntilIdle(); |
| EXPECT_FALSE(last_answer_response_); |
| EXPECT_FALSE(last_status_response_); |
| EXPECT_FALSE(last_error_message_.empty()); |
| } |
| |
| TEST_F(MessageDispatcherTest, IgnoreMessageWithWrongNamespace) { |
| const std::string answer_response = |
| "{\"type\":\"ANSWER\",\"seqNum\":12345,\"result\":\"ok\"," |
| "\"answer\":{\"udpPort\":50691}}"; |
| const CastMessage answer_message = |
| CastMessage{"Wrong_namespace", answer_response}; |
| SendInboundMessage(answer_message); |
| scoped_task_environment_.RunUntilIdle(); |
| EXPECT_FALSE(last_answer_response_); |
| EXPECT_FALSE(last_status_response_); |
| // Messages with different namespace are ignored with no error reported. |
| EXPECT_TRUE(last_error_message_.empty()); |
| } |
| |
| TEST_F(MessageDispatcherTest, RequestReply) { |
| EXPECT_FALSE(last_answer_response_); |
| EXPECT_FALSE(last_status_response_); |
| message_dispatcher_->Unsubscribe(ResponseType::ANSWER); |
| scoped_task_environment_.RunUntilIdle(); |
| const std::string fake_offer = "{\"type\":\"OFFER\",\"seqNum\":45623}"; |
| const CastMessage offer_message = |
| CastMessage{mojom::kWebRtcNamespace, fake_offer}; |
| message_dispatcher_->RequestReply( |
| offer_message.Clone(), ResponseType::ANSWER, 45623, |
| base::TimeDelta::FromMilliseconds(100), |
| base::BindRepeating(&MessageDispatcherTest::OnAnswerResponse, |
| base::Unretained(this))); |
| scoped_task_environment_.RunUntilIdle(); |
| // Received the request to send the outbound message. |
| EXPECT_TRUE(IsEqual(offer_message, last_outbound_message_)); |
| |
| std::string answer_response = |
| "{\"type\":\"ANSWER\",\"seqNum\":12345,\"result\":\"ok\"," |
| "\"answer\":{\"udpPort\":50691}}"; |
| CastMessage answer_message = |
| CastMessage{mojom::kWebRtcNamespace, answer_response}; |
| SendInboundMessage(answer_message); |
| scoped_task_environment_.RunUntilIdle(); |
| // The answer message with mismatched sequence number is ignored. |
| EXPECT_FALSE(last_answer_response_); |
| EXPECT_FALSE(last_status_response_); |
| EXPECT_TRUE(last_error_message_.empty()); |
| |
| answer_response = |
| "{\"type\":\"ANSWER\",\"seqNum\":45623,\"result\":\"ok\"," |
| "\"answer\":{\"udpPort\":50691}}"; |
| answer_message = CastMessage{mojom::kWebRtcNamespace, answer_response}; |
| SendInboundMessage(answer_message); |
| scoped_task_environment_.RunUntilIdle(); |
| ASSERT_TRUE(last_answer_response_); |
| EXPECT_FALSE(last_status_response_); |
| EXPECT_TRUE(last_error_message_.empty()); |
| EXPECT_EQ(45623, last_answer_response_->sequence_number); |
| EXPECT_EQ(ResponseType::ANSWER, last_answer_response_->type); |
| EXPECT_EQ("ok", last_answer_response_->result); |
| EXPECT_EQ(50691, last_answer_response_->answer->udp_port); |
| last_answer_response_.reset(); |
| |
| // Expect that the callback for ANSWER message was already unsubscribed. |
| SendInboundMessage(answer_message); |
| scoped_task_environment_.RunUntilIdle(); |
| EXPECT_FALSE(last_answer_response_); |
| EXPECT_FALSE(last_status_response_); |
| EXPECT_FALSE(last_error_message_.empty()); |
| last_error_message_.clear(); |
| |
| const CastMessage fake_message = CastMessage{ |
| mojom::kWebRtcNamespace, "{\"type\":\"OFFER\",\"seqNum\":12345}"}; |
| message_dispatcher_->RequestReply( |
| fake_message.Clone(), ResponseType::ANSWER, 12345, |
| base::TimeDelta::FromMilliseconds(100), |
| base::BindRepeating(&MessageDispatcherTest::OnAnswerResponse, |
| base::Unretained(this))); |
| scoped_task_environment_.RunUntilIdle(); |
| // Received the request to send the outbound message. |
| EXPECT_TRUE(IsEqual(fake_message, last_outbound_message_)); |
| EXPECT_FALSE(last_answer_response_); |
| EXPECT_FALSE(last_status_response_); |
| |
| // Destroy the dispatcher. |
| message_dispatcher_.reset(); |
| scoped_task_environment_.RunUntilIdle(); |
| ASSERT_FALSE(last_answer_response_); |
| EXPECT_FALSE(last_status_response_); |
| } |
| |
| } // namespace mirroring |