1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "cast/receiver/application_agent.h"
6
7 #include <iomanip>
8 #include <sstream>
9 #include <string>
10 #include <utility>
11 #include <vector>
12
13 #include "cast/common/channel/message_util.h"
14 #include "cast/common/channel/testing/fake_cast_socket.h"
15 #include "cast/common/public/message_port.h"
16 #include "cast/receiver/channel/static_credentials.h"
17 #include "cast/receiver/channel/testing/device_auth_test_helpers.h"
18 #include "gmock/gmock.h"
19 #include "gtest/gtest.h"
20 #include "json/writer.h" // Included to teach gtest how to pretty-print.
21 #include "platform/api/time.h"
22 #include "platform/test/fake_task_runner.h"
23 #include "platform/test/paths.h"
24 #include "testing/util/read_file.h"
25 #include "util/json/json_serialization.h"
26
27 namespace openscreen {
28 namespace cast {
29 namespace {
30
31 using ::cast::channel::CastMessage;
32 using ::testing::_;
33 using ::testing::Invoke;
34 using ::testing::Mock;
35 using ::testing::Ne;
36 using ::testing::NiceMock;
37 using ::testing::NotNull;
38 using ::testing::Sequence;
39 using ::testing::StrEq;
40 using ::testing::StrictMock;
41
42 // Returns the location of certificate and auth challenge data files for cast
43 // receiver tests.
GetTestDataSubdir()44 std::string GetTestDataSubdir() {
45 return GetTestDataPath() + "cast/receiver/channel";
46 }
47
48 class TestCredentialsProvider final
49 : public DeviceAuthNamespaceHandler::CredentialsProvider {
50 public:
TestCredentialsProvider()51 TestCredentialsProvider() {
52 const std::string dir = GetTestDataSubdir();
53 bssl::UniquePtr<X509> parsed_cert;
54 TrustStore fake_trust_store;
55 InitStaticCredentialsFromFiles(
56 &creds_, &parsed_cert, &fake_trust_store, dir + "/device_key.pem",
57 dir + "/device_chain.pem", dir + "/device_tls.pem");
58 }
59
GetCurrentTlsCertAsDer()60 absl::Span<const uint8_t> GetCurrentTlsCertAsDer() final {
61 return absl::Span<uint8_t>(creds_.tls_cert_der);
62 }
GetCurrentDeviceCredentials()63 const DeviceCredentials& GetCurrentDeviceCredentials() final {
64 return creds_.device_creds;
65 }
66
67 private:
68 StaticCredentialsProvider creds_;
69 };
70
TestAuthChallengeMessage()71 CastMessage TestAuthChallengeMessage() {
72 CastMessage message;
73 const auto result = message.ParseFromString(
74 ReadEntireFileToString(GetTestDataSubdir() + "/auth_challenge.pb"));
75 OSP_CHECK(result);
76 return message;
77 }
78
79 class FakeApplication : public ApplicationAgent::Application,
80 public MessagePort::Client {
81 public:
FakeApplication(const char * app_id,const char * display_name)82 FakeApplication(const char* app_id, const char* display_name)
83 : app_ids_({app_id}), display_name_(display_name) {
84 OSP_CHECK(app_ids_.front().size() == 8);
85 }
86
87 // These are called at the end of the Launch() and Stop() methods for
88 // confirming those methods were called.
89 MOCK_METHOD(void, DidLaunch, (Json::Value params, MessagePort* port), ());
90 MOCK_METHOD(void, DidStop, (), ());
91
92 // MessagePort::Client overrides.
93 MOCK_METHOD(void,
94 OnMessage,
95 (const std::string& source_sender_id,
96 const std::string& message_namespace,
97 const std::string& message),
98 (override));
99 MOCK_METHOD(void, OnError, (Error error), (override));
100
GetAppIds() const101 const std::vector<std::string>& GetAppIds() const override {
102 return app_ids_;
103 }
104
Launch(const std::string & app_id,const Json::Value & app_params,MessagePort * message_port)105 bool Launch(const std::string& app_id,
106 const Json::Value& app_params,
107 MessagePort* message_port) override {
108 EXPECT_EQ(GetAppIds().front(), app_id);
109 EXPECT_TRUE(message_port);
110 EXPECT_FALSE(is_launched_);
111 ++session_id_;
112 is_launched_ = true;
113 DidLaunch(app_params, message_port);
114 return true;
115 }
116
GetSessionId()117 std::string GetSessionId() override {
118 std::ostringstream oss;
119 if (is_launched_) {
120 oss << GetAppIds().front() << "-9ABC-DEF0-1234-";
121 oss << std::setfill('0') << std::hex << std::setw(12) << session_id_;
122 }
123 return oss.str();
124 }
125
GetDisplayName()126 std::string GetDisplayName() override { return display_name_; }
127
GetSupportedNamespaces()128 std::vector<std::string> GetSupportedNamespaces() override {
129 return namespaces_;
130 }
SetSupportedNamespaces(std::vector<std::string> the_namespaces)131 void SetSupportedNamespaces(std::vector<std::string> the_namespaces) {
132 namespaces_ = std::move(the_namespaces);
133 }
134
Stop()135 void Stop() override {
136 EXPECT_TRUE(is_launched_);
137 is_launched_ = false;
138 DidStop();
139 }
140
141 private:
142 const std::vector<std::string> app_ids_;
143 const std::string display_name_;
144
145 std::vector<std::string> namespaces_;
146
147 int session_id_ = 0;
148 bool is_launched_ = false;
149 };
150
151 class ApplicationAgentTest : public ::testing::Test {
152 public:
ApplicationAgentTest()153 ApplicationAgentTest() {
154 EXPECT_CALL(idle_app_, DidLaunch(_, NotNull()));
155 agent_.RegisterApplication(&idle_app_, true);
156 Mock::VerifyAndClearExpectations(&idle_app_);
157
158 ConnectAndDoAuth();
159 }
160
~ApplicationAgentTest()161 ~ApplicationAgentTest() override { EXPECT_CALL(idle_app_, DidStop()); }
162
agent()163 ApplicationAgent* agent() { return &agent_; }
idle_app()164 StrictMock<FakeApplication>* idle_app() { return &idle_app_; }
165
sender_inbound()166 MockCastSocketClient* sender_inbound() {
167 return &socket_pair_.mock_peer_client;
168 }
sender_outbound()169 CastSocket* sender_outbound() { return socket_pair_.peer_socket.get(); }
170
171 // Examines the |message| for the correct source/destination transport IDs and
172 // namespace, confirms there is JSON in the payload, and returns parsed JSON
173 // (or an empty object if the parse fails).
ValidateAndParseMessage(const CastMessage & message,const std::string & from,const std::string & to,const std::string & the_namespace)174 static Json::Value ValidateAndParseMessage(const CastMessage& message,
175 const std::string& from,
176 const std::string& to,
177 const std::string& the_namespace) {
178 EXPECT_EQ(from, message.source_id());
179 EXPECT_EQ(to, message.destination_id());
180 EXPECT_EQ(the_namespace, message.namespace_());
181 EXPECT_EQ(::cast::channel::CastMessage_PayloadType_STRING,
182 message.payload_type());
183 EXPECT_FALSE(message.payload_utf8().empty());
184 ErrorOr<Json::Value> parsed = json::Parse(message.payload_utf8());
185 return parsed.value(Json::Value(Json::objectValue));
186 }
187
188 // Constructs a CastMessage proto for sending via the CastSocket::Send() API.
MakeCastMessage(const std::string & source_id,const std::string & destination_id,const std::string & the_namespace,const std::string & json)189 static CastMessage MakeCastMessage(const std::string& source_id,
190 const std::string& destination_id,
191 const std::string& the_namespace,
192 const std::string& json) {
193 CastMessage message = MakeSimpleUTF8Message(the_namespace, json);
194 message.set_source_id(source_id);
195 message.set_destination_id(destination_id);
196 return message;
197 }
198
199 private:
200 // Walk through all the steps to establish a network connection to the
201 // ApplicationAgent, and test the plumbing for the auth challenge/reply.
ConnectAndDoAuth()202 void ConnectAndDoAuth() {
203 static_cast<ReceiverSocketFactory::Client*>(&agent_)->OnConnected(
204 nullptr, socket_pair_.local_endpoint, std::move(socket_pair_.socket));
205
206 // The remote will send the auth challenge message and get a reply.
207 EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
208 .WillOnce(Invoke([](CastSocket*, CastMessage message) {
209 EXPECT_EQ(kAuthNamespace, message.namespace_());
210 EXPECT_FALSE(message.payload_binary().empty());
211 }));
212 const auto result = sender_outbound()->Send(TestAuthChallengeMessage());
213 ASSERT_TRUE(result.ok()) << result;
214 Mock::VerifyAndClearExpectations(sender_inbound());
215 }
216
TearDown()217 void TearDown() override {
218 // The ApplicationAgent should send a final "no apps running"
219 // RECEIVER_STATUS broadcast to the sender at destruction time.
220 EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
221 .WillOnce(Invoke([](CastSocket*, CastMessage message) {
222 constexpr char kExpectedJson[] = R"({
223 "requestId":0,
224 "type":"RECEIVER_STATUS",
225 "status":{
226 "userEq":{},
227 "volume":{
228 "controlType":"attenuation",
229 "level":1.0,
230 "muted":false,
231 "stepInterval":0.05
232 }
233 }
234 })";
235 const Json::Value payload = ValidateAndParseMessage(
236 message, kPlatformReceiverId, kBroadcastId, kReceiverNamespace);
237 EXPECT_EQ(json::Parse(kExpectedJson).value(), payload);
238 }));
239 }
240
241 FakeClock clock_{Clock::time_point() + std::chrono::hours(1)};
242 FakeTaskRunner task_runner_{&clock_};
243 FakeCastSocketPair socket_pair_;
244 StrictMock<FakeApplication> idle_app_{"E8C28D3C", "Backdrop"};
245 TestCredentialsProvider creds_;
246 ApplicationAgent agent_{&task_runner_, &creds_};
247 };
248
TEST_F(ApplicationAgentTest,JustConnectsWithoutDoingAnything)249 TEST_F(ApplicationAgentTest, JustConnectsWithoutDoingAnything) {}
250
TEST_F(ApplicationAgentTest,IgnoresGarbageMessages)251 TEST_F(ApplicationAgentTest, IgnoresGarbageMessages) {
252 EXPECT_CALL(*sender_inbound(), OnMessage(_, _)).Times(0);
253
254 const char* kGarbageStrings[] = {
255 "",
256 R"(naked text)",
257 R"("")",
258 R"(123)",
259 R"("just a string")",
260 R"([])",
261 R"({})",
262 R"({"type":"GET_STATUS"})", // Note: Missing requestId.
263 };
264 for (const char* some_string : kGarbageStrings) {
265 const auto result = sender_outbound()->Send(
266 MakeCastMessage(kPlatformSenderId, kPlatformReceiverId,
267 kReceiverNamespace, some_string));
268 ASSERT_TRUE(result.ok()) << result;
269 }
270 }
271
TEST_F(ApplicationAgentTest,HandlesInvalidCommands)272 TEST_F(ApplicationAgentTest, HandlesInvalidCommands) {
273 EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
274 .WillOnce(Invoke([&](CastSocket*, CastMessage message) {
275 constexpr char kExpectedJson[] = R"({
276 "requestId":3,
277 "type":"INVALID_REQUEST",
278 "reason":"INVALID_COMMAND"
279 })";
280 const Json::Value payload =
281 ValidateAndParseMessage(message, kPlatformReceiverId,
282 kPlatformSenderId, kReceiverNamespace);
283 EXPECT_EQ(json::Parse(kExpectedJson).value(), payload);
284 }));
285 auto result = sender_outbound()->Send(MakeCastMessage(
286 kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({
287 "requestId":3,
288 "type":"FINISH_Q3_OKRS_BY_END_OF_Q3"
289 })"));
290 ASSERT_TRUE(result.ok()) << result;
291 }
292
TEST_F(ApplicationAgentTest,HandlesPings)293 TEST_F(ApplicationAgentTest, HandlesPings) {
294 constexpr int kNumPings = 3;
295
296 int num_pongs = 0;
297 EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
298 .Times(kNumPings)
299 .WillRepeatedly(Invoke([&num_pongs](CastSocket*, CastMessage message) {
300 const Json::Value payload =
301 ValidateAndParseMessage(message, kPlatformReceiverId,
302 kPlatformSenderId, kHeartbeatNamespace);
303 EXPECT_EQ(json::Parse(R"({"type":"PONG"})").value(), payload);
304 ++num_pongs;
305 }));
306
307 const CastMessage message =
308 MakeCastMessage(kPlatformSenderId, kPlatformReceiverId,
309 kHeartbeatNamespace, R"({"type":"PING"})");
310 for (int i = 0; i < kNumPings; ++i) {
311 const auto result = sender_outbound()->Send(message);
312 ASSERT_TRUE(result.ok()) << result;
313 }
314 EXPECT_EQ(kNumPings, num_pongs);
315 }
316
TEST_F(ApplicationAgentTest,HandlesGetAppAvailability)317 TEST_F(ApplicationAgentTest, HandlesGetAppAvailability) {
318 // Send the request before any apps have been registered. Expect an
319 // "unavailable" response.
320 EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
321 .WillOnce(Invoke([&](CastSocket*, CastMessage message) {
322 constexpr char kExpectedJson[] = R"({
323 "requestId":548,
324 "responseType":"GET_APP_AVAILABILITY",
325 "availability":{"1A2B3C4D":"APP_UNAVAILABLE"}
326 })";
327 const Json::Value payload =
328 ValidateAndParseMessage(message, kPlatformReceiverId,
329 kPlatformSenderId, kReceiverNamespace);
330 EXPECT_EQ(json::Parse(kExpectedJson).value(), payload);
331 }));
332 auto result = sender_outbound()->Send(MakeCastMessage(
333 kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({
334 "requestId":548,
335 "type":"GET_APP_AVAILABILITY",
336 "appId":["1A2B3C4D"]
337 })"));
338 ASSERT_TRUE(result.ok()) << result;
339
340 // Register an application.
341 FakeApplication some_app("1A2B3C4D", "Something Doer");
342 agent()->RegisterApplication(&some_app);
343
344 // Send another request, and expect the application to be available.
345 EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
346 .WillOnce(Invoke([&](CastSocket*, CastMessage message) {
347 constexpr char kExpectedJson[] = R"({
348 "requestId":549,
349 "responseType":"GET_APP_AVAILABILITY",
350 "availability":{"1A2B3C4D":"APP_AVAILABLE"}
351 })";
352 const Json::Value payload =
353 ValidateAndParseMessage(message, kPlatformReceiverId,
354 kPlatformSenderId, kReceiverNamespace);
355 EXPECT_EQ(json::Parse(kExpectedJson).value(), payload);
356 }));
357 result = sender_outbound()->Send(MakeCastMessage(
358 kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({
359 "requestId":549,
360 "type":"GET_APP_AVAILABILITY",
361 "appId":["1A2B3C4D"]
362 })"));
363 ASSERT_TRUE(result.ok()) << result;
364
365 agent()->UnregisterApplication(&some_app);
366 }
367
TEST_F(ApplicationAgentTest,HandlesGetStatus)368 TEST_F(ApplicationAgentTest, HandlesGetStatus) {
369 EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
370 .WillOnce(Invoke([&](CastSocket*, CastMessage message) {
371 constexpr char kExpectedJson[] = R"({
372 "requestId":123,
373 "type":"RECEIVER_STATUS",
374 "status":{
375 "applications":[
376 {
377 // NOTE: These IDs and the displayName come from |idle_app_|.
378 "sessionId":"E8C28D3C-9ABC-DEF0-1234-000000000001",
379 "appId":"E8C28D3C",
380 "universalAppId":"E8C28D3C",
381 "displayName":"Backdrop",
382 "isIdleScreen":true,
383 "launchedFromCloud":false,
384 "namespaces":[]
385 }
386 ],
387 "userEq":{},
388 "volume":{
389 "controlType":"attenuation",
390 "level":1.0,
391 "muted":false,
392 "stepInterval":0.05
393 }
394 }
395 })";
396 const Json::Value payload =
397 ValidateAndParseMessage(message, kPlatformReceiverId,
398 kPlatformSenderId, kReceiverNamespace);
399 EXPECT_EQ(json::Parse(kExpectedJson).value(), payload);
400 }));
401 auto result = sender_outbound()->Send(MakeCastMessage(
402 kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({
403 "requestId":123,
404 "type":"GET_STATUS"
405 })"));
406 ASSERT_TRUE(result.ok()) << result;
407 }
408
TEST_F(ApplicationAgentTest,FailsLaunchRequestWithBadAppID)409 TEST_F(ApplicationAgentTest, FailsLaunchRequestWithBadAppID) {
410 EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
411 .WillOnce(Invoke([&](CastSocket*, CastMessage message) {
412 constexpr char kExpectedJson[] = R"({
413 "requestId":1,
414 "type":"LAUNCH_ERROR",
415 "reason":"NOT_FOUND"
416 })";
417 const Json::Value payload =
418 ValidateAndParseMessage(message, kPlatformReceiverId,
419 kPlatformSenderId, kReceiverNamespace);
420 EXPECT_EQ(json::Parse(kExpectedJson).value(), payload);
421 }));
422 auto launch_result = sender_outbound()->Send(MakeCastMessage(
423 kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({
424 "requestId":1,
425 "type":"LAUNCH",
426 "appId":"DEADBEEF"
427 })"));
428 ASSERT_TRUE(launch_result.ok()) << launch_result;
429 }
430
TEST_F(ApplicationAgentTest,LaunchesApp_PassesMessages_ThenStopsApp)431 TEST_F(ApplicationAgentTest, LaunchesApp_PassesMessages_ThenStopsApp) {
432 StrictMock<FakeApplication> some_app("1A2B3C4D", "Something Doer");
433 constexpr char kAppNamespace[] = "urn:x-cast:com.google.cast.something";
434 some_app.SetSupportedNamespaces({std::string(kAppNamespace)});
435 agent()->RegisterApplication(&some_app);
436
437 // Phase 1: Sender sends a LAUNCH request, which causes the idle app to stop
438 // and the receiver app to launch. The receiver (ApplicationAgent) broadcasts
439 // a RECEIVER_STATUS to indicate the app is running; but the receiver app
440 // should not get a copy of that.
441 Sequence phase1;
442 MessagePort* port_for_app = nullptr;
443 EXPECT_CALL(*idle_app(), DidStop()).InSequence(phase1);
444 EXPECT_CALL(some_app, DidLaunch(_, NotNull()))
445 .InSequence(phase1)
446 .WillOnce(Invoke([&](Json::Value params, MessagePort* port) {
447 EXPECT_EQ(json::Parse(R"({"a":1,"b":2})").value(), params);
448 port_for_app = port;
449 port->SetClient(&some_app, some_app.GetSessionId());
450 }));
451 const std::string kRunningAppReceiverStatus = R"({
452 "requestId":0, // Note: 0 for broadcast (no requestor).
453 "type":"RECEIVER_STATUS",
454 "status":{
455 "applications":[
456 {
457 // NOTE: These IDs and the displayName come from |some_app|.
458 "transportId":"1A2B3C4D-9ABC-DEF0-1234-000000000001",
459 "sessionId":"1A2B3C4D-9ABC-DEF0-1234-000000000001",
460 "appId":"1A2B3C4D",
461 "universalAppId":"1A2B3C4D",
462 "displayName":"Something Doer",
463 "isIdleScreen":false,
464 "launchedFromCloud":false,
465 "namespaces":[{"name":"urn:x-cast:com.google.cast.something"}]
466 }
467 ],
468 "userEq":{},
469 "volume":{
470 "controlType":"attenuation",
471 "level":1.0,
472 "muted":false,
473 "stepInterval":0.05
474 }
475 }
476 })";
477 EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
478 .InSequence(phase1)
479 .WillOnce(Invoke([&](CastSocket*, CastMessage message) {
480 const Json::Value payload = ValidateAndParseMessage(
481 message, kPlatformReceiverId, kBroadcastId, kReceiverNamespace);
482 EXPECT_EQ(json::Parse(kRunningAppReceiverStatus).value(), payload);
483 }));
484 auto launch_result = sender_outbound()->Send(MakeCastMessage(
485 kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({
486 "requestId":17,
487 "type":"LAUNCH",
488 "appId":"1A2B3C4D",
489 "appParams":{"a":1,"b":2},
490 "language":"en-US",
491 "supportedAppTypes":["WEB"]
492 })"));
493 ASSERT_TRUE(launch_result.ok()) << launch_result;
494 Mock::VerifyAndClearExpectations(idle_app());
495 Mock::VerifyAndClearExpectations(&some_app);
496 Mock::VerifyAndClearExpectations(sender_inbound());
497
498 // Phase 2: Sender sends a message to the app, and the receiver app sends a
499 // reply.
500 constexpr char kMessage[] = R"({"type":"FOO","data":"Hello world!"})";
501 constexpr char kReplyMessage[] =
502 R"({"type":"FOO_REPLY","data":"Hi yourself!"})";
503 constexpr char kSenderTransportId[] = "sender-1";
504 Sequence phase2;
505 EXPECT_CALL(some_app, OnMessage(_, _, _))
506 .InSequence(phase2)
507 .WillOnce(Invoke([&](const std::string& source_id,
508 const std::string& the_namespace,
509 const std::string& message) {
510 EXPECT_EQ(kSenderTransportId, source_id);
511 EXPECT_EQ(kAppNamespace, the_namespace);
512 const auto parsed = json::Parse(message);
513 EXPECT_TRUE(parsed.is_value()) << parsed.error();
514 if (parsed.is_value()) {
515 EXPECT_EQ(json::Parse(kMessage).value(), parsed.value());
516 if (port_for_app) {
517 port_for_app->PostMessage(kSenderTransportId, kAppNamespace,
518 kReplyMessage);
519 }
520 }
521 }));
522 EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
523 .InSequence(phase2)
524 .WillOnce(Invoke([&](CastSocket*, CastMessage message) {
525 const Json::Value payload =
526 ValidateAndParseMessage(message, some_app.GetSessionId(),
527 kSenderTransportId, kAppNamespace);
528 EXPECT_EQ(json::Parse(kReplyMessage).value(), payload);
529 }));
530 auto message_send_result = sender_outbound()->Send(MakeCastMessage(
531 kSenderTransportId, some_app.GetSessionId(), kAppNamespace, kMessage));
532 ASSERT_TRUE(message_send_result.ok()) << message_send_result;
533 Mock::VerifyAndClearExpectations(&some_app);
534 Mock::VerifyAndClearExpectations(sender_inbound());
535
536 // Phase 3: Sender sends a STOP request, which causes the receiver
537 // (ApplicationAgent) to stop the app. Then, the idle app will automatically
538 // be re-launched, and a RECEIVER_STATUS broadcast message will notify the
539 // sender of that.
540 Sequence phase3;
541 EXPECT_CALL(some_app, DidStop()).InSequence(phase3);
542 EXPECT_CALL(*idle_app(), DidLaunch(_, NotNull())).InSequence(phase3);
543 EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
544 .InSequence(phase3)
545 .WillOnce(Invoke([&](CastSocket*, CastMessage message) {
546 const std::string kExpectedJson = R"({
547 "requestId":0, // Note: 0 for broadcast (no requestor).
548 "type":"RECEIVER_STATUS",
549 "status":{
550 "applications":[
551 {
552 // NOTE: These IDs and the displayName come from |idle_app_|.
553 "sessionId":"E8C28D3C-9ABC-DEF0-1234-000000000002",
554 "appId":"E8C28D3C",
555 "universalAppId":"E8C28D3C",
556 "displayName":"Backdrop",
557 "isIdleScreen":true,
558 "launchedFromCloud":false,
559 "namespaces":[]
560 }
561 ],
562 "userEq":{},
563 "volume":{
564 "controlType":"attenuation",
565 "level":1.0,
566 "muted":false,
567 "stepInterval":0.05
568 }
569 }
570 })";
571 const Json::Value payload = ValidateAndParseMessage(
572 message, kPlatformReceiverId, kBroadcastId, kReceiverNamespace);
573 EXPECT_EQ(json::Parse(kExpectedJson).value(), payload);
574 }));
575 auto stop_result = sender_outbound()->Send(MakeCastMessage(
576 kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({
577 "requestId":18,
578 "type":"STOP",
579 "sessionId":"1A2B3C4D-9ABC-DEF0-1234-000000000001"
580 })"));
581 ASSERT_TRUE(stop_result.ok()) << stop_result;
582 Mock::VerifyAndClearExpectations(idle_app());
583 Mock::VerifyAndClearExpectations(&some_app);
584 Mock::VerifyAndClearExpectations(sender_inbound());
585
586 agent()->UnregisterApplication(&some_app);
587 }
588
TEST_F(ApplicationAgentTest,AllowsVirtualConnectionsToApp)589 TEST_F(ApplicationAgentTest, AllowsVirtualConnectionsToApp) {
590 NiceMock<FakeApplication> some_app("1A2B3C4D", "Something Doer");
591 agent()->RegisterApplication(&some_app);
592
593 // Launch the app, using gMock to simulate an app that calls
594 // MessagePort::SetClient() (to permit messaging) and to get the transport ID
595 // of the app.
596 EXPECT_CALL(*idle_app(), DidStop());
597 EXPECT_CALL(some_app, DidLaunch(_, NotNull()))
598 .WillOnce(Invoke([&](Json::Value params, MessagePort* port) {
599 port->SetClient(&some_app, some_app.GetSessionId());
600 }));
601 std::string transport_id;
602 EXPECT_CALL(*sender_inbound(), OnMessage(_, _))
603 .WillRepeatedly(Invoke([&](CastSocket*, CastMessage message) {
604 const Json::Value payload = ValidateAndParseMessage(
605 message, kPlatformReceiverId, kBroadcastId, kReceiverNamespace);
606 if (payload["type"].asString() == "RECEIVER_STATUS") {
607 transport_id =
608 payload["status"]["applications"][0]["transportId"].asString();
609 }
610 }));
611 auto launch_result = sender_outbound()->Send(MakeCastMessage(
612 kPlatformSenderId, kPlatformReceiverId, kReceiverNamespace, R"({
613 "requestId":1,
614 "type":"LAUNCH",
615 "appId":"1A2B3C4D",
616 "appParams":{},
617 "language":"en-US",
618 "supportedAppTypes":["WEB"]
619 })"));
620 ASSERT_TRUE(launch_result.ok()) << launch_result;
621 Mock::VerifyAndClearExpectations(idle_app());
622 Mock::VerifyAndClearExpectations(&some_app);
623 Mock::VerifyAndClearExpectations(sender_inbound());
624
625 // Now that the application has launched, check that the policy allows
626 // connections to both the ApplicationAgent and the running application.
627 auto* const policy =
628 static_cast<ConnectionNamespaceHandler::VirtualConnectionPolicy*>(
629 agent());
630 EXPECT_TRUE(policy->IsConnectionAllowed(
631 VirtualConnection{kPlatformReceiverId, "any-sender-12345", 0}));
632 ASSERT_FALSE(transport_id.empty());
633 EXPECT_TRUE(policy->IsConnectionAllowed(
634 VirtualConnection{transport_id, "any-sender-12345", 0}));
635 EXPECT_FALSE(policy->IsConnectionAllowed(
636 VirtualConnection{"wherever i likes", "any-sender-12345", 0}));
637
638 // Unregister the app, which will automatically stop it too.
639 EXPECT_CALL(some_app, DidStop());
640 EXPECT_CALL(*idle_app(), DidLaunch(_, NotNull()));
641 EXPECT_CALL(*sender_inbound(), OnMessage(_, _)); // RECEIVER_STATUS update.
642 agent()->UnregisterApplication(&some_app);
643
644 // With the app stopped, check that the policy no longer allows connections to
645 // the now-stale |transport_id|.
646 EXPECT_TRUE(policy->IsConnectionAllowed(
647 VirtualConnection{kPlatformReceiverId, "any-sender-12345", 0}));
648 EXPECT_FALSE(policy->IsConnectionAllowed(
649 VirtualConnection{transport_id, "any-sender-12345", 0}));
650 EXPECT_FALSE(policy->IsConnectionAllowed(
651 VirtualConnection{"wherever i likes", "any-sender-12345", 0}));
652 }
653
654 } // namespace
655 } // namespace cast
656 } // namespace openscreen
657