xref: /aosp_15_r20/external/openscreen/cast/receiver/application_agent_unittest.cc (revision 3f982cf4871df8771c9d4abe6e9a6f8d829b2736)
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