blob: ec8a44a4f5ec62a74263451c5c61d3abc37e3b55 [file] [log] [blame]
// 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 <utility>
#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<Json::Value> 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_(&connection_manager_, this),
router_(&connection_manager_),
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<CastSocket> 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 == kConnectionNamespace) {
connection_handler_.OnMessage(router, socket, std::move(message));
return;
}
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<std::string> 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