// 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 #include "cast/common/channel/message_util.h" #include "cast/common/channel/virtual_connection.h" #include "cast/common/public/cast_socket.h" #include "platform/base/tls_credentials.h" #include "platform/base/tls_listen_options.h" #include "util/json/json_serialization.h" #include "util/osp_logging.h" namespace openscreen { namespace cast { namespace { // Parses the given string as a JSON object. If the parse fails, an empty object // is returned. Json::Value ParseAsObject(absl::string_view value) { ErrorOr parsed = json::Parse(value); if (parsed.is_value() && parsed.value().isObject()) { return std::move(parsed.value()); } return Json::Value(Json::objectValue); } // Returns true if the type field in |object| is set to the given |type|. bool HasType(const Json::Value& object, CastMessageType type) { OSP_DCHECK(object.isObject()); const Json::Value& value = object.get(kMessageKeyType, Json::Value::nullSingleton()); return value.isString() && value.asString() == CastMessageTypeToString(type); } // Returns the first app ID for the given |app|, or the empty string if there is // none. std::string GetFirstAppId(ApplicationAgent::Application* app) { const auto& app_ids = app->GetAppIds(); return app_ids.empty() ? std::string() : app_ids.front(); } } // namespace ApplicationAgent::ApplicationAgent( TaskRunner* task_runner, DeviceAuthNamespaceHandler::CredentialsProvider* credentials_provider) : task_runner_(task_runner), auth_handler_(credentials_provider), connection_handler_(&router_, this), message_port_(&router_) { router_.AddHandlerForLocalId(kPlatformReceiverId, this); } ApplicationAgent::~ApplicationAgent() { OSP_DCHECK(task_runner_->IsRunningOnTaskRunner()); idle_screen_app_ = nullptr; // Prevent re-launching the idle screen app. SwitchToApplication({}, {}, nullptr); router_.RemoveHandlerForLocalId(kPlatformReceiverId); } void ApplicationAgent::RegisterApplication(Application* app, bool auto_launch_for_idle_screen) { OSP_DCHECK(app); for (const std::string& app_id : app->GetAppIds()) { OSP_DCHECK(!app_id.empty()); const auto insert_result = registered_applications_.insert({app_id, app}); // The insert must not fail (prior entry for same key). OSP_DCHECK(insert_result.second); } if (auto_launch_for_idle_screen) { OSP_DCHECK(!idle_screen_app_); idle_screen_app_ = app; // Launch the idle screen app, if no app was running. if (!launched_app_) { GoIdle(); } } } void ApplicationAgent::UnregisterApplication(Application* app) { for (auto it = registered_applications_.begin(); it != registered_applications_.end();) { if (it->second == app) { it = registered_applications_.erase(it); } else { ++it; } } if (idle_screen_app_ == app) { idle_screen_app_ = nullptr; } if (launched_app_ == app) { GoIdle(); } } void ApplicationAgent::StopApplicationIfRunning(Application* app) { if (launched_app_ == app) { GoIdle(); } } void ApplicationAgent::OnConnected(ReceiverSocketFactory* factory, const IPEndpoint& endpoint, std::unique_ptr socket) { router_.TakeSocket(this, std::move(socket)); } void ApplicationAgent::OnError(ReceiverSocketFactory* factory, Error error) { OSP_LOG_ERROR << "Cast agent received socket factory error: " << error; } void ApplicationAgent::OnMessage(VirtualConnectionRouter* router, CastSocket* socket, ::cast::channel::CastMessage message) { 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; } if (message.destination_id() != kPlatformReceiverId && message.destination_id() != kBroadcastId) { return; // Message not for us. } const std::string& ns = message.namespace_(); if (ns == kAuthNamespace) { auth_handler_.OnMessage(router, socket, std::move(message)); return; } const Json::Value request = ParseAsObject(message.payload_utf8()); Json::Value response; if (ns == kHeartbeatNamespace) { if (HasType(request, CastMessageType::kPing)) { response = HandlePing(); } } else if (ns == kReceiverNamespace) { if (request[kMessageKeyRequestId].isNull()) { response = HandleInvalidCommand(request); } else if (HasType(request, CastMessageType::kGetAppAvailability)) { response = HandleGetAppAvailability(request); } else if (HasType(request, CastMessageType::kGetStatus)) { response = HandleGetStatus(request); } else if (HasType(request, CastMessageType::kLaunch)) { response = HandleLaunch(request, socket); } else if (HasType(request, CastMessageType::kStop)) { response = HandleStop(request); } else { response = HandleInvalidCommand(request); } } else { // Ignore messages for all other namespaces. } if (!response.empty()) { router_.Send(VirtualConnection{message.destination_id(), message.source_id(), ToCastSocketId(socket)}, MakeSimpleUTF8Message(ns, json::Stringify(response).value())); } } bool ApplicationAgent::IsConnectionAllowed( const VirtualConnection& virtual_conn) const { 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) { if (message_port_.GetSocketId() == ToCastSocketId(socket)) { OSP_VLOG << "Cast agent socket closed."; GoIdle(); } } void ApplicationAgent::OnError(CastSocket* socket, Error error) { if (message_port_.GetSocketId() == ToCastSocketId(socket)) { OSP_LOG_ERROR << "Cast agent received socket error: " << error; GoIdle(); } } Json::Value ApplicationAgent::HandlePing() { Json::Value response; response[kMessageKeyType] = CastMessageTypeToString(CastMessageType::kPong); return response; } Json::Value ApplicationAgent::HandleGetAppAvailability( const Json::Value& request) { Json::Value response; const Json::Value& app_ids = request[kMessageKeyAppId]; if (app_ids.isArray()) { response[kMessageKeyRequestId] = request[kMessageKeyRequestId]; response[kMessageKeyResponseType] = request[kMessageKeyType]; Json::Value& availability = response[kMessageKeyAvailability]; for (const Json::Value& app_id : app_ids) { if (app_id.isString()) { const auto app_id_str = app_id.asString(); availability[app_id_str] = registered_applications_.count(app_id_str) ? kMessageValueAppAvailable : kMessageValueAppUnavailable; } } } return response; } Json::Value ApplicationAgent::HandleGetStatus(const Json::Value& request) { Json::Value response; PopulateReceiverStatus(&response); response[kMessageKeyRequestId] = request[kMessageKeyRequestId]; return response; } Json::Value ApplicationAgent::HandleLaunch(const Json::Value& request, CastSocket* socket) { const Json::Value& app_id = request[kMessageKeyAppId]; Error error; if (app_id.isString() && !app_id.asString().empty()) { error = SwitchToApplication(app_id.asString(), request[kMessageKeyAppParams], socket); } else { error = Error(Error::Code::kParameterInvalid, kMessageValueBadParameter); } if (!error.ok()) { Json::Value response; response[kMessageKeyRequestId] = request[kMessageKeyRequestId]; response[kMessageKeyType] = CastMessageTypeToString(CastMessageType::kLaunchError); response[kMessageKeyReason] = error.message(); return response; } // Note: No reply is sent. Instead, the requestor will get a RECEIVER_STATUS // broadcast message from SwitchToApplication(), which is how it will see that // the launch succeeded. return {}; } Json::Value ApplicationAgent::HandleStop(const Json::Value& request) { const Json::Value& session_id = request[kMessageKeySessionId]; if (session_id.isNull()) { GoIdle(); return {}; } if (session_id.isString() && launched_app_ && session_id.asString() == launched_app_->GetSessionId()) { GoIdle(); return {}; } Json::Value response; response[kMessageKeyRequestId] = request[kMessageKeyRequestId]; response[kMessageKeyType] = CastMessageTypeToString(CastMessageType::kInvalidRequest); response[kMessageKeyReason] = kMessageValueInvalidSessionId; return response; } Json::Value ApplicationAgent::HandleInvalidCommand(const Json::Value& request) { Json::Value response; if (request[kMessageKeyRequestId].isNull()) { return response; } response[kMessageKeyRequestId] = request[kMessageKeyRequestId]; response[kMessageKeyType] = CastMessageTypeToString(CastMessageType::kInvalidRequest); response[kMessageKeyReason] = kMessageValueInvalidCommand; return response; } Error ApplicationAgent::SwitchToApplication(std::string app_id, const Json::Value& app_params, CastSocket* socket) { Error error = Error::Code::kNone; Application* desired_app = nullptr; Application* fallback_app = nullptr; if (!app_id.empty()) { const auto it = registered_applications_.find(app_id); if (it != registered_applications_.end()) { desired_app = it->second; if (desired_app != idle_screen_app_) { fallback_app = idle_screen_app_; } } else { return Error(Error::Code::kItemNotFound, kMessageValueNotFound); } } if (launched_app_ == desired_app) { return error; } if (launched_app_) { launched_app_->Stop(); message_port_.SetSocket({}); launched_app_ = nullptr; launched_via_app_id_ = {}; } if (desired_app) { if (socket) { message_port_.SetSocket(socket->GetWeakPtr()); } if (desired_app->Launch(app_id, app_params, &message_port_)) { launched_app_ = desired_app; launched_via_app_id_ = std::move(app_id); } else { error = Error(Error::Code::kUnknownError, kMessageValueSystemError); message_port_.SetSocket({}); } } if (!launched_app_ && fallback_app) { app_id = GetFirstAppId(fallback_app); if (fallback_app->Launch(app_id, {}, &message_port_)) { launched_app_ = fallback_app; launched_via_app_id_ = std::move(app_id); } } BroadcastReceiverStatus(); return error; } void ApplicationAgent::GoIdle() { std::string app_id; if (idle_screen_app_) { app_id = GetFirstAppId(idle_screen_app_); } SwitchToApplication(app_id, {}, nullptr); } void ApplicationAgent::PopulateReceiverStatus(Json::Value* out) { Json::Value& message = *out; message[kMessageKeyType] = CastMessageTypeToString(CastMessageType::kReceiverStatus); Json::Value& status = message[kMessageKeyStatus]; if (launched_app_) { Json::Value& details = status[kMessageKeyApplications][0]; // If the Application can send/receive messages, the destination for such // messages is provided here, in |transportId|. However, the other end must // first set up the virtual connection by issuing a CONNECT request. // Otherwise, messages will not get routed to the Application by the // VirtualConnectionRouter. if (!message_port_.client_sender_id().empty()) { details[kMessageKeyTransportId] = message_port_.client_sender_id(); } details[kMessageKeySessionId] = launched_app_->GetSessionId(); details[kMessageKeyAppId] = launched_via_app_id_; details[kMessageKeyUniversalAppId] = launched_via_app_id_; details[kMessageKeyDisplayName] = launched_app_->GetDisplayName(); details[kMessageKeyIsIdleScreen] = (launched_app_ == idle_screen_app_); details[kMessageKeyLaunchedFromCloud] = false; std::vector app_namespaces = launched_app_->GetSupportedNamespaces(); Json::Value& namespaces = (details[kMessageKeyNamespaces] = Json::Value(Json::arrayValue)); for (int i = 0, count = app_namespaces.size(); i < count; ++i) { namespaces[i][kMessageKeyName] = std::move(app_namespaces[i]); } } status[kMessageKeyUserEq] = Json::Value(Json::objectValue); // Indicate a fixed 100% volume level. Json::Value& volume = status[kMessageKeyVolume]; volume[kMessageKeyControlType] = kMessageValueAttenuation; volume[kMessageKeyLevel] = 1.0; volume[kMessageKeyMuted] = false; volume[kMessageKeyStepInterval] = 0.05; } void ApplicationAgent::BroadcastReceiverStatus() { Json::Value message; PopulateReceiverStatus(&message); message[kMessageKeyRequestId] = Json::Value(0); // Indicates no requestor. router_.BroadcastFromLocalPeer( kPlatformReceiverId, MakeSimpleUTF8Message(kReceiverNamespace, json::Stringify(message).value())); } ApplicationAgent::Application::~Application() = default; } // namespace cast } // namespace openscreen