Implement a more-specific AppAgent::IsConnectionAllowed() policy.

Prevents connections if an app is not launched. Or, if an app is
launched, connections must match the "transportId" that was in the last
RECEIVER_STATUS update.

Bug: b/162542369
Change-Id: I99afcf8a4c80d015505fa688b7372b83ae650578
Reviewed-on: https://chromium-review.googlesource.com/c/openscreen/+/2552433
Commit-Queue: Jordan Bayles <jophba@chromium.org>
Reviewed-by: Jordan Bayles <jophba@chromium.org>
diff --git a/cast/receiver/application_agent.cc b/cast/receiver/application_agent.cc
index 97f2da2..ec8a44a 100644
--- a/cast/receiver/application_agent.cc
+++ b/cast/receiver/application_agent.cc
@@ -127,6 +127,7 @@
   if (message_port_.GetSocketId() == ToCastSocketId(socket) &&
       !message_port_.client_sender_id().empty() &&
       message_port_.client_sender_id() == message.destination_id()) {
+    OSP_DCHECK(message_port_.client_sender_id() != kPlatformReceiverId);
     message_port_.OnMessage(router, socket, std::move(message));
     return;
   }
@@ -179,7 +180,15 @@
 
 bool ApplicationAgent::IsConnectionAllowed(
     const VirtualConnection& virtual_conn) const {
-  return true;
+  if (virtual_conn.local_id == kPlatformReceiverId) {
+    return true;
+  }
+  if (!launched_app_ || message_port_.client_sender_id().empty()) {
+    // No app currently launched. Or, there is a launched app, but it did not
+    // call MessagePort::SetClient() to indicate it wants messages routed to it.
+    return false;
+  }
+  return virtual_conn.local_id == message_port_.client_sender_id();
 }
 
 void ApplicationAgent::OnClose(CastSocket* socket) {
diff --git a/cast/receiver/application_agent_unittest.cc b/cast/receiver/application_agent_unittest.cc
index 8a02707..90fe705 100644
--- a/cast/receiver/application_agent_unittest.cc
+++ b/cast/receiver/application_agent_unittest.cc
@@ -33,6 +33,7 @@
 using ::testing::Invoke;
 using ::testing::Mock;
 using ::testing::Ne;
+using ::testing::NiceMock;
 using ::testing::NotNull;
 using ::testing::Sequence;
 using ::testing::StrEq;
@@ -585,6 +586,71 @@
   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