| // Copyright 2020 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 "cast/receiver/application_agent.h" |
| |
| #include <iomanip> |
| #include <sstream> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "cast/common/channel/message_util.h" |
| #include "cast/common/channel/testing/fake_cast_socket.h" |
| #include "cast/common/public/message_port.h" |
| #include "cast/receiver/channel/static_credentials.h" |
| #include "cast/receiver/channel/testing/device_auth_test_helpers.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "json/writer.h" // Included to teach gtest how to pretty-print. |
| #include "platform/api/time.h" |
| #include "platform/test/fake_task_runner.h" |
| #include "platform/test/paths.h" |
| #include "testing/util/read_file.h" |
| #include "util/json/json_serialization.h" |
| |
| namespace openscreen { |
| namespace cast { |
| namespace { |
| |
| using ::cast::channel::CastMessage; |
| using ::testing::_; |
| using ::testing::Invoke; |
| using ::testing::Mock; |
| using ::testing::Ne; |
| using ::testing::NiceMock; |
| using ::testing::NotNull; |
| using ::testing::Sequence; |
| using ::testing::StrEq; |
| using ::testing::StrictMock; |
| |
| // Returns the location of certificate and auth challenge data files for cast |
| // receiver tests. |
| std::string GetTestDataSubdir() { |
| return GetTestDataPath() + "cast/receiver/channel"; |
| } |
| |
| class TestCredentialsProvider final |
| : public DeviceAuthNamespaceHandler::CredentialsProvider { |
| public: |
| TestCredentialsProvider() { |
| const std::string dir = GetTestDataSubdir(); |
| bssl::UniquePtr<X509> parsed_cert; |
| TrustStore fake_trust_store; |
| InitStaticCredentialsFromFiles( |
| &creds_, &parsed_cert, &fake_trust_store, dir + "/device_key.pem", |
| dir + "/device_chain.pem", dir + "/device_tls.pem"); |
| } |
| |
| absl::Span<const uint8_t> GetCurrentTlsCertAsDer() final { |
| return absl::Span<uint8_t>(creds_.tls_cert_der); |
| } |
| const DeviceCredentials& GetCurrentDeviceCredentials() final { |
| return creds_.device_creds; |
| } |
| |
| private: |
| StaticCredentialsProvider creds_; |
| }; |
| |
| CastMessage TestAuthChallengeMessage() { |
| CastMessage message; |
| const auto result = message.ParseFromString( |
| ReadEntireFileToString(GetTestDataSubdir() + "/auth_challenge.pb")); |
| OSP_CHECK(result); |
| return message; |
| } |
| |
| class FakeApplication : public ApplicationAgent::Application, |
| public MessagePort::Client { |
| public: |
| FakeApplication(const char* app_id, const char* display_name) |
| : app_ids_({app_id}), display_name_(display_name) { |
| OSP_CHECK(app_ids_.front().size() == 8); |
| } |
| |
| // These are called at the end of the Launch() and Stop() methods for |
| // confirming those methods were called. |
| MOCK_METHOD(void, DidLaunch, (Json::Value params, MessagePort* port), ()); |
| MOCK_METHOD(void, DidStop, (), ()); |
| |
| // MessagePort::Client overrides. |
| MOCK_METHOD(void, |
| OnMessage, |
| (const std::string& source_sender_id, |
| const std::string& message_namespace, |
| const std::string& message), |
| (override)); |
| MOCK_METHOD(void, OnError, (Error error), (override)); |
| |
| const std::vector<std::string>& GetAppIds() const override { |
| return app_ids_; |
| } |
| |
| bool Launch(const std::string& app_id, |
| const Json::Value& app_params, |
| MessagePort* message_port) override { |
| EXPECT_EQ(GetAppIds().front(), app_id); |
| EXPECT_TRUE(message_port); |
| EXPECT_FALSE(is_launched_); |
| ++session_id_; |
| is_launched_ = true; |
| DidLaunch(app_params, message_port); |
| return true; |
| } |
| |
| std::string GetSessionId() override { |
| std::ostringstream oss; |
| if (is_launched_) { |
| oss << GetAppIds().front() << "-9ABC-DEF0-1234-"; |
| oss << std::setfill('0') << std::hex << std::setw(12) << session_id_; |
| } |
| return oss.str(); |
| } |
| |
| std::string GetDisplayName() override { return display_name_; } |
| |
| std::vector<std::string> GetSupportedNamespaces() override { |
| return namespaces_; |
| } |
| void SetSupportedNamespaces(std::vector<std::string> the_namespaces) { |
| namespaces_ = std::move(the_namespaces); |
| } |
| |
| void Stop() override { |
| EXPECT_TRUE(is_launched_); |
| is_launched_ = false; |
| DidStop(); |
| } |
| |
| private: |
| const std::vector<std::string> app_ids_; |
| const std::string display_name_; |
| |
| std::vector<std::string> namespaces_; |
| |
| int session_id_ = 0; |
| bool is_launched_ = false; |
| }; |
| |
| class ApplicationAgentTest : public ::testing::Test { |
| public: |
| ApplicationAgentTest() { |
| EXPECT_CALL(idle_app_, DidLaunch(_, NotNull())); |
| agent_.RegisterApplication(&idle_app_, true); |
| Mock::VerifyAndClearExpectations(&idle_app_); |
| |
| ConnectAndDoAuth(); |
| } |
| |
| ~ApplicationAgentTest() override { EXPECT_CALL(idle_app_, DidStop()); } |
| |
| ApplicationAgent* agent() { return &agent_; } |
| StrictMock<FakeApplication>* idle_app() { return &idle_app_; } |
| |
| MockCastSocketClient* sender_inbound() { |
| return &socket_pair_.mock_peer_client; |
| } |
| CastSocket* sender_outbound() { return socket_pair_.peer_socket.get(); } |
| |
| // Examines the |message| for the correct source/destination transport IDs and |
| // namespace, confirms there is JSON in the payload, and returns parsed JSON |
| // (or an empty object if the parse fails). |
| static Json::Value ValidateAndParseMessage(const CastMessage& message, |
| const std::string& from, |
| const std::string& to, |
| const std::string& the_namespace) { |
| EXPECT_EQ(from, message.source_id()); |
| EXPECT_EQ(to, message.destination_id()); |
| EXPECT_EQ(the_namespace, message.namespace_()); |
| EXPECT_EQ(::cast::channel::CastMessage_PayloadType_STRING, |
| message.payload_type()); |
| EXPECT_FALSE(message.payload_utf8().empty()); |
| ErrorOr<Json::Value> parsed = json::Parse(message.payload_utf8()); |
| return parsed.value(Json::Value(Json::objectValue)); |
| } |
| |
| // Constructs a CastMessage proto for sending via the CastSocket::Send() API. |
| static CastMessage MakeCastMessage(const std::string& source_id, |
| const std::string& destination_id, |
| const std::string& the_namespace, |
| const std::string& json) { |
| CastMessage message = MakeSimpleUTF8Message(the_namespace, json); |
| message.set_source_id(source_id); |
| message.set_destination_id(destination_id); |
| return message; |
| } |
| |
| private: |
| // Walk through all the steps to establish a network connection to the |
| // ApplicationAgent, and test the plumbing for the auth challenge/reply. |
| void ConnectAndDoAuth() { |
| static_cast<ReceiverSocketFactory::Client*>(&agent_)->OnConnected( |
| nullptr, socket_pair_.local_endpoint, std::move(socket_pair_.socket)); |
| |
| // The remote will send the auth challenge message and get a reply. |
| EXPECT_CALL(*sender_inbound(), OnMessage(_, _)) |
| .WillOnce(Invoke([](CastSocket*, CastMessage message) { |
| EXPECT_EQ(kAuthNamespace, message.namespace_()); |
| EXPECT_FALSE(message.payload_binary().empty()); |
| })); |
| const auto result = sender_outbound()->Send(TestAuthChallengeMessage()); |
| ASSERT_TRUE(result.ok()) << result; |
| Mock::VerifyAndClearExpectations(sender_inbound()); |
| } |
| |
| void TearDown() override { |
| // The ApplicationAgent should send a final "no apps running" |
| // RECEIVER_STATUS broadcast to the sender at destruction time. |
| EXPECT_CALL(*sender_inbound(), OnMessage(_, _)) |
| .WillOnce(Invoke([](CastSocket*, CastMessage message) { |
| constexpr char kExpectedJson[] = R"({ |
| "requestId":0, |
| "type":"RECEIVER_STATUS", |
| "status":{ |
| "userEq":{}, |
| "volume":{ |
| "controlType":"attenuation", |
| "level":1.0, |
| "muted":false, |
| "stepInterval":0.05 |
| } |
| } |
| })"; |
| const Json::Value payload = ValidateAndParseMessage( |
| message, kPlatformReceiverId, kBroadcastId, kReceiverNamespace); |
| EXPECT_EQ(json::Parse(kExpectedJson).value(), payload); |
| })); |
| } |
| |
| FakeClock clock_{Clock::time_point() + std::chrono::hours(1)}; |
| FakeTaskRunner task_runner_{&clock_}; |
| FakeCastSocketPair socket_pair_; |
| StrictMock<FakeApplication> idle_app_{"E8C28D3C", "Backdrop"}; |
| TestCredentialsProvider creds_; |
| ApplicationAgent agent_{&task_runner_, &creds_}; |
| }; |
| |
| TEST_F(ApplicationAgentTest, JustConnectsWithoutDoingAnything) {} |
| |
| TEST_F(ApplicationAgentTest, IgnoresGarbageMessages) { |
| EXPECT_CALL(*sender_inbound(), OnMessage(_, _)).Times(0); |
| |
| const char* kGarbageStrings[] = { |
| "", |
| R"(naked text)", |
| R"("")", |
| R"(123)", |
| R"("just a string")", |
| R"([])", |
| R"({})", |
| R"({"type":"GET_STATUS"})", // Note: Missing requestId. |
| }; |
| for (const char* some_string : kGarbageStrings) { |
| const auto result = sender_outbound()->Send( |
| MakeCastMessage(kPlatformSenderId, kPlatformReceiverId, |
| kReceiverNamespace, some_string)); |
| ASSERT_TRUE(result.ok()) << result; |
| } |
| } |
| |
| TEST_F(ApplicationAgentTest, HandlesInvalidCommands) { |
| EXPECT_CALL(*sender_inbound(), OnMessage(_, _)) |
| .WillOnce(Invoke([&](CastSocket*, CastMessage message) { |
| constexpr char kExpectedJson[] = R"({ |
| "requestId":3, |
| "type":"INVALID_REQUEST", |
| "reason":"INVALID_COMMAND" |
| })"; |
| const Json::Value payload = |
| ValidateAndParseMessage(message, kPlatformReceiverId, |
| kPlatformSenderId, kReceiverNamespace); |
| EXPECT_EQ(json::Parse(kExpectedJson).value(), payload); |
| })); |
| auto result = sender_outbound()->Send(MakeCastMessage( |
| kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({ |
| "requestId":3, |
| "type":"FINISH_Q3_OKRS_BY_END_OF_Q3" |
| })")); |
| ASSERT_TRUE(result.ok()) << result; |
| } |
| |
| TEST_F(ApplicationAgentTest, HandlesPings) { |
| constexpr int kNumPings = 3; |
| |
| int num_pongs = 0; |
| EXPECT_CALL(*sender_inbound(), OnMessage(_, _)) |
| .Times(kNumPings) |
| .WillRepeatedly(Invoke([&num_pongs](CastSocket*, CastMessage message) { |
| const Json::Value payload = |
| ValidateAndParseMessage(message, kPlatformReceiverId, |
| kPlatformSenderId, kHeartbeatNamespace); |
| EXPECT_EQ(json::Parse(R"({"type":"PONG"})").value(), payload); |
| ++num_pongs; |
| })); |
| |
| const CastMessage message = |
| MakeCastMessage(kPlatformSenderId, kPlatformReceiverId, |
| kHeartbeatNamespace, R"({"type":"PING"})"); |
| for (int i = 0; i < kNumPings; ++i) { |
| const auto result = sender_outbound()->Send(message); |
| ASSERT_TRUE(result.ok()) << result; |
| } |
| EXPECT_EQ(kNumPings, num_pongs); |
| } |
| |
| TEST_F(ApplicationAgentTest, HandlesGetAppAvailability) { |
| // Send the request before any apps have been registered. Expect an |
| // "unavailable" response. |
| EXPECT_CALL(*sender_inbound(), OnMessage(_, _)) |
| .WillOnce(Invoke([&](CastSocket*, CastMessage message) { |
| constexpr char kExpectedJson[] = R"({ |
| "requestId":548, |
| "responseType":"GET_APP_AVAILABILITY", |
| "availability":{"1A2B3C4D":"APP_UNAVAILABLE"} |
| })"; |
| const Json::Value payload = |
| ValidateAndParseMessage(message, kPlatformReceiverId, |
| kPlatformSenderId, kReceiverNamespace); |
| EXPECT_EQ(json::Parse(kExpectedJson).value(), payload); |
| })); |
| auto result = sender_outbound()->Send(MakeCastMessage( |
| kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({ |
| "requestId":548, |
| "type":"GET_APP_AVAILABILITY", |
| "appId":["1A2B3C4D"] |
| })")); |
| ASSERT_TRUE(result.ok()) << result; |
| |
| // Register an application. |
| FakeApplication some_app("1A2B3C4D", "Something Doer"); |
| agent()->RegisterApplication(&some_app); |
| |
| // Send another request, and expect the application to be available. |
| EXPECT_CALL(*sender_inbound(), OnMessage(_, _)) |
| .WillOnce(Invoke([&](CastSocket*, CastMessage message) { |
| constexpr char kExpectedJson[] = R"({ |
| "requestId":549, |
| "responseType":"GET_APP_AVAILABILITY", |
| "availability":{"1A2B3C4D":"APP_AVAILABLE"} |
| })"; |
| const Json::Value payload = |
| ValidateAndParseMessage(message, kPlatformReceiverId, |
| kPlatformSenderId, kReceiverNamespace); |
| EXPECT_EQ(json::Parse(kExpectedJson).value(), payload); |
| })); |
| result = sender_outbound()->Send(MakeCastMessage( |
| kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({ |
| "requestId":549, |
| "type":"GET_APP_AVAILABILITY", |
| "appId":["1A2B3C4D"] |
| })")); |
| ASSERT_TRUE(result.ok()) << result; |
| |
| agent()->UnregisterApplication(&some_app); |
| } |
| |
| TEST_F(ApplicationAgentTest, HandlesGetStatus) { |
| EXPECT_CALL(*sender_inbound(), OnMessage(_, _)) |
| .WillOnce(Invoke([&](CastSocket*, CastMessage message) { |
| constexpr char kExpectedJson[] = R"({ |
| "requestId":123, |
| "type":"RECEIVER_STATUS", |
| "status":{ |
| "applications":[ |
| { |
| // NOTE: These IDs and the displayName come from |idle_app_|. |
| "sessionId":"E8C28D3C-9ABC-DEF0-1234-000000000001", |
| "appId":"E8C28D3C", |
| "universalAppId":"E8C28D3C", |
| "displayName":"Backdrop", |
| "isIdleScreen":true, |
| "launchedFromCloud":false, |
| "namespaces":[] |
| } |
| ], |
| "userEq":{}, |
| "volume":{ |
| "controlType":"attenuation", |
| "level":1.0, |
| "muted":false, |
| "stepInterval":0.05 |
| } |
| } |
| })"; |
| const Json::Value payload = |
| ValidateAndParseMessage(message, kPlatformReceiverId, |
| kPlatformSenderId, kReceiverNamespace); |
| EXPECT_EQ(json::Parse(kExpectedJson).value(), payload); |
| })); |
| auto result = sender_outbound()->Send(MakeCastMessage( |
| kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({ |
| "requestId":123, |
| "type":"GET_STATUS" |
| })")); |
| ASSERT_TRUE(result.ok()) << result; |
| } |
| |
| TEST_F(ApplicationAgentTest, FailsLaunchRequestWithBadAppID) { |
| EXPECT_CALL(*sender_inbound(), OnMessage(_, _)) |
| .WillOnce(Invoke([&](CastSocket*, CastMessage message) { |
| constexpr char kExpectedJson[] = R"({ |
| "requestId":1, |
| "type":"LAUNCH_ERROR", |
| "reason":"NOT_FOUND" |
| })"; |
| const Json::Value payload = |
| ValidateAndParseMessage(message, kPlatformReceiverId, |
| kPlatformSenderId, kReceiverNamespace); |
| EXPECT_EQ(json::Parse(kExpectedJson).value(), payload); |
| })); |
| auto launch_result = sender_outbound()->Send(MakeCastMessage( |
| kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({ |
| "requestId":1, |
| "type":"LAUNCH", |
| "appId":"DEADBEEF" |
| })")); |
| ASSERT_TRUE(launch_result.ok()) << launch_result; |
| } |
| |
| TEST_F(ApplicationAgentTest, LaunchesApp_PassesMessages_ThenStopsApp) { |
| StrictMock<FakeApplication> some_app("1A2B3C4D", "Something Doer"); |
| constexpr char kAppNamespace[] = "urn:x-cast:com.google.cast.something"; |
| some_app.SetSupportedNamespaces({std::string(kAppNamespace)}); |
| agent()->RegisterApplication(&some_app); |
| |
| // Phase 1: Sender sends a LAUNCH request, which causes the idle app to stop |
| // and the receiver app to launch. The receiver (ApplicationAgent) broadcasts |
| // a RECEIVER_STATUS to indicate the app is running; but the receiver app |
| // should not get a copy of that. |
| Sequence phase1; |
| MessagePort* port_for_app = nullptr; |
| EXPECT_CALL(*idle_app(), DidStop()).InSequence(phase1); |
| EXPECT_CALL(some_app, DidLaunch(_, NotNull())) |
| .InSequence(phase1) |
| .WillOnce(Invoke([&](Json::Value params, MessagePort* port) { |
| EXPECT_EQ(json::Parse(R"({"a":1,"b":2})").value(), params); |
| port_for_app = port; |
| port->SetClient(&some_app, some_app.GetSessionId()); |
| })); |
| const std::string kRunningAppReceiverStatus = R"({ |
| "requestId":0, // Note: 0 for broadcast (no requestor). |
| "type":"RECEIVER_STATUS", |
| "status":{ |
| "applications":[ |
| { |
| // NOTE: These IDs and the displayName come from |some_app|. |
| "transportId":"1A2B3C4D-9ABC-DEF0-1234-000000000001", |
| "sessionId":"1A2B3C4D-9ABC-DEF0-1234-000000000001", |
| "appId":"1A2B3C4D", |
| "universalAppId":"1A2B3C4D", |
| "displayName":"Something Doer", |
| "isIdleScreen":false, |
| "launchedFromCloud":false, |
| "namespaces":[{"name":"urn:x-cast:com.google.cast.something"}] |
| } |
| ], |
| "userEq":{}, |
| "volume":{ |
| "controlType":"attenuation", |
| "level":1.0, |
| "muted":false, |
| "stepInterval":0.05 |
| } |
| } |
| })"; |
| EXPECT_CALL(*sender_inbound(), OnMessage(_, _)) |
| .InSequence(phase1) |
| .WillOnce(Invoke([&](CastSocket*, CastMessage message) { |
| const Json::Value payload = ValidateAndParseMessage( |
| message, kPlatformReceiverId, kBroadcastId, kReceiverNamespace); |
| EXPECT_EQ(json::Parse(kRunningAppReceiverStatus).value(), payload); |
| })); |
| auto launch_result = sender_outbound()->Send(MakeCastMessage( |
| kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({ |
| "requestId":17, |
| "type":"LAUNCH", |
| "appId":"1A2B3C4D", |
| "appParams":{"a":1,"b":2}, |
| "language":"en-US", |
| "supportedAppTypes":["WEB"] |
| })")); |
| ASSERT_TRUE(launch_result.ok()) << launch_result; |
| Mock::VerifyAndClearExpectations(idle_app()); |
| Mock::VerifyAndClearExpectations(&some_app); |
| Mock::VerifyAndClearExpectations(sender_inbound()); |
| |
| // Phase 2: Sender sends a message to the app, and the receiver app sends a |
| // reply. |
| constexpr char kMessage[] = R"({"type":"FOO","data":"Hello world!"})"; |
| constexpr char kReplyMessage[] = |
| R"({"type":"FOO_REPLY","data":"Hi yourself!"})"; |
| constexpr char kSenderTransportId[] = "sender-1"; |
| Sequence phase2; |
| EXPECT_CALL(some_app, OnMessage(_, _, _)) |
| .InSequence(phase2) |
| .WillOnce(Invoke([&](const std::string& source_id, |
| const std::string& the_namespace, |
| const std::string& message) { |
| EXPECT_EQ(kSenderTransportId, source_id); |
| EXPECT_EQ(kAppNamespace, the_namespace); |
| const auto parsed = json::Parse(message); |
| EXPECT_TRUE(parsed.is_value()) << parsed.error(); |
| if (parsed.is_value()) { |
| EXPECT_EQ(json::Parse(kMessage).value(), parsed.value()); |
| if (port_for_app) { |
| port_for_app->PostMessage(kSenderTransportId, kAppNamespace, |
| kReplyMessage); |
| } |
| } |
| })); |
| EXPECT_CALL(*sender_inbound(), OnMessage(_, _)) |
| .InSequence(phase2) |
| .WillOnce(Invoke([&](CastSocket*, CastMessage message) { |
| const Json::Value payload = |
| ValidateAndParseMessage(message, some_app.GetSessionId(), |
| kSenderTransportId, kAppNamespace); |
| EXPECT_EQ(json::Parse(kReplyMessage).value(), payload); |
| })); |
| auto message_send_result = sender_outbound()->Send(MakeCastMessage( |
| kSenderTransportId, some_app.GetSessionId(), kAppNamespace, kMessage)); |
| ASSERT_TRUE(message_send_result.ok()) << message_send_result; |
| Mock::VerifyAndClearExpectations(&some_app); |
| Mock::VerifyAndClearExpectations(sender_inbound()); |
| |
| // Phase 3: Sender sends a STOP request, which causes the receiver |
| // (ApplicationAgent) to stop the app. Then, the idle app will automatically |
| // be re-launched, and a RECEIVER_STATUS broadcast message will notify the |
| // sender of that. |
| Sequence phase3; |
| EXPECT_CALL(some_app, DidStop()).InSequence(phase3); |
| EXPECT_CALL(*idle_app(), DidLaunch(_, NotNull())).InSequence(phase3); |
| EXPECT_CALL(*sender_inbound(), OnMessage(_, _)) |
| .InSequence(phase3) |
| .WillOnce(Invoke([&](CastSocket*, CastMessage message) { |
| const std::string kExpectedJson = R"({ |
| "requestId":0, // Note: 0 for broadcast (no requestor). |
| "type":"RECEIVER_STATUS", |
| "status":{ |
| "applications":[ |
| { |
| // NOTE: These IDs and the displayName come from |idle_app_|. |
| "sessionId":"E8C28D3C-9ABC-DEF0-1234-000000000002", |
| "appId":"E8C28D3C", |
| "universalAppId":"E8C28D3C", |
| "displayName":"Backdrop", |
| "isIdleScreen":true, |
| "launchedFromCloud":false, |
| "namespaces":[] |
| } |
| ], |
| "userEq":{}, |
| "volume":{ |
| "controlType":"attenuation", |
| "level":1.0, |
| "muted":false, |
| "stepInterval":0.05 |
| } |
| } |
| })"; |
| const Json::Value payload = ValidateAndParseMessage( |
| message, kPlatformReceiverId, kBroadcastId, kReceiverNamespace); |
| EXPECT_EQ(json::Parse(kExpectedJson).value(), payload); |
| })); |
| auto stop_result = sender_outbound()->Send(MakeCastMessage( |
| kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({ |
| "requestId":18, |
| "type":"STOP", |
| "sessionId":"1A2B3C4D-9ABC-DEF0-1234-000000000001" |
| })")); |
| ASSERT_TRUE(stop_result.ok()) << stop_result; |
| Mock::VerifyAndClearExpectations(idle_app()); |
| Mock::VerifyAndClearExpectations(&some_app); |
| Mock::VerifyAndClearExpectations(sender_inbound()); |
| |
| agent()->UnregisterApplication(&some_app); |
| } |
| |
| TEST_F(ApplicationAgentTest, AllowsVirtualConnectionsToApp) { |
| NiceMock<FakeApplication> some_app("1A2B3C4D", "Something Doer"); |
| agent()->RegisterApplication(&some_app); |
| |
| // Launch the app, using gMock to simulate an app that calls |
| // MessagePort::SetClient() (to permit messaging) and to get the transport ID |
| // of the app. |
| EXPECT_CALL(*idle_app(), DidStop()); |
| EXPECT_CALL(some_app, DidLaunch(_, NotNull())) |
| .WillOnce(Invoke([&](Json::Value params, MessagePort* port) { |
| port->SetClient(&some_app, some_app.GetSessionId()); |
| })); |
| std::string transport_id; |
| EXPECT_CALL(*sender_inbound(), OnMessage(_, _)) |
| .WillRepeatedly(Invoke([&](CastSocket*, CastMessage message) { |
| const Json::Value payload = ValidateAndParseMessage( |
| message, kPlatformReceiverId, kBroadcastId, kReceiverNamespace); |
| if (payload["type"].asString() == "RECEIVER_STATUS") { |
| transport_id = |
| payload["status"]["applications"][0]["transportId"].asString(); |
| } |
| })); |
| auto launch_result = sender_outbound()->Send(MakeCastMessage( |
| kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({ |
| "requestId":1, |
| "type":"LAUNCH", |
| "appId":"1A2B3C4D", |
| "appParams":{}, |
| "language":"en-US", |
| "supportedAppTypes":["WEB"] |
| })")); |
| ASSERT_TRUE(launch_result.ok()) << launch_result; |
| Mock::VerifyAndClearExpectations(idle_app()); |
| Mock::VerifyAndClearExpectations(&some_app); |
| Mock::VerifyAndClearExpectations(sender_inbound()); |
| |
| // Now that the application has launched, check that the policy allows |
| // connections to both the ApplicationAgent and the running application. |
| auto* const policy = |
| static_cast<ConnectionNamespaceHandler::VirtualConnectionPolicy*>( |
| agent()); |
| EXPECT_TRUE(policy->IsConnectionAllowed( |
| VirtualConnection{kPlatformReceiverId, "any-sender-12345", 0})); |
| ASSERT_FALSE(transport_id.empty()); |
| EXPECT_TRUE(policy->IsConnectionAllowed( |
| VirtualConnection{transport_id, "any-sender-12345", 0})); |
| EXPECT_FALSE(policy->IsConnectionAllowed( |
| VirtualConnection{"wherever i likes", "any-sender-12345", 0})); |
| |
| // Unregister the app, which will automatically stop it too. |
| EXPECT_CALL(some_app, DidStop()); |
| EXPECT_CALL(*idle_app(), DidLaunch(_, NotNull())); |
| EXPECT_CALL(*sender_inbound(), OnMessage(_, _)); // RECEIVER_STATUS update. |
| agent()->UnregisterApplication(&some_app); |
| |
| // With the app stopped, check that the policy no longer allows connections to |
| // the now-stale |transport_id|. |
| EXPECT_TRUE(policy->IsConnectionAllowed( |
| VirtualConnection{kPlatformReceiverId, "any-sender-12345", 0})); |
| EXPECT_FALSE(policy->IsConnectionAllowed( |
| VirtualConnection{transport_id, "any-sender-12345", 0})); |
| EXPECT_FALSE(policy->IsConnectionAllowed( |
| VirtualConnection{"wherever i likes", "any-sender-12345", 0})); |
| } |
| |
| } // namespace |
| } // namespace cast |
| } // namespace openscreen |