blob: 5ebf3a948c7c4ad3f52c041175d1b1a9b5207a77 [file] [log] [blame]
// Copyright 2014 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 "components/cast_channel/cast_message_util.h"
#include <memory>
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "components/cast_channel/cast_auth_util.h"
#include "components/cast_channel/enum_table.h"
#include "components/cast_channel/proto/cast_channel.pb.h"
using base::Value;
using cast_util::EnumToString;
using cast_util::StringToEnum;
namespace cast_util {
using namespace cast_channel;
template <>
const EnumTable<CastMessageType> EnumTable<CastMessageType>::instance({
{CastMessageType::kPing, "PING"},
{CastMessageType::kPong, "PONG"},
{CastMessageType::kGetAppAvailability, "GET_APP_AVAILABILITY"},
{CastMessageType::kReceiverStatusRequest, "GET_STATUS"},
{CastMessageType::kConnect, "CONNECT"},
{CastMessageType::kCloseConnection, "CLOSE"},
{CastMessageType::kBroadcast, "APPLICATION_BROADCAST"},
{CastMessageType::kLaunch, "LAUNCH"},
{CastMessageType::kStop, "STOP"},
{CastMessageType::kReceiverStatus, "RECEIVER_STATUS"},
{CastMessageType::kMediaStatus, "MEDIA_STATUS"},
{CastMessageType::kLaunchError, "LAUNCH_ERROR"},
});
template <>
const EnumTable<V2MessageType> EnumTable<V2MessageType>::instance({
{V2MessageType::kEditTracksInfo, "EDIT_TRACKS_INFO"},
{V2MessageType::kGetStatus, "GET_STATUS"},
{V2MessageType::kLoad, "LOAD"},
{V2MessageType::kMediaGetStatus, "MEDIA_GET_STATUS"},
{V2MessageType::kMediaSetVolume, "MEDIA_SET_VOLUME"},
{V2MessageType::kPause, "PAUSE"},
{V2MessageType::kPlay, "PLAY"},
{V2MessageType::kPrecache, "PRECACHE"},
{V2MessageType::kQueueInsert, "QUEUE_INSERT"},
{V2MessageType::kQueueLoad, "QUEUE_LOAD"},
{V2MessageType::kQueueRemove, "QUEUE_REMOVE"},
{V2MessageType::kQueueReorder, "QUEUE_REORDER"},
{V2MessageType::kQueueUpdate, "QUEUE_UPDATE"},
{V2MessageType::kSeek, "SEEK"},
{V2MessageType::kSetVolume, "SET_VOLUME"},
{V2MessageType::kStop, "STOP"},
{V2MessageType::kStopMedia, "STOP_MEDIA"},
});
template <>
const EnumTable<GetAppAvailabilityResult>
EnumTable<GetAppAvailabilityResult>::instance({
{GetAppAvailabilityResult::kAvailable, "APP_AVAILABLE"},
{GetAppAvailabilityResult::kUnavailable, "APP_UNAVAILABLE"},
});
} // namespace cast_util
namespace cast_channel {
namespace {
// The value used for "sdkType" in a virtual connect request. Historically, this
// value is used in the Media Router extension, but here it is reused in Chrome.
constexpr int kVirtualConnectSdkType = 2;
// The value used for "connectionType" in a virtual connect request. This value
// stands for CONNECTION_TYPE_LOCAL, which is the only type used in Chrome.
constexpr int kVirtualConnectTypeLocal = 1;
void FillCommonCastMessageFields(CastMessage* message,
const std::string& source_id,
const std::string& destination_id,
const std::string& message_namespace) {
message->set_protocol_version(CastMessage::CASTV2_1_0);
message->set_source_id(source_id);
message->set_destination_id(destination_id);
message->set_namespace_(message_namespace);
}
CastMessage CreateKeepAliveMessage(base::StringPiece keep_alive_type) {
base::Value type_dict(base::Value::Type::DICTIONARY);
type_dict.SetKey("type", base::Value(keep_alive_type));
return CreateCastMessage(kHeartbeatNamespace, type_dict, kPlatformSenderId,
kPlatformReceiverId);
}
// Returns the value to be set as the "platform" value in a virtual connect
// request. The value is platform-dependent and is taken from the Platform enum
// defined in third_party/metrics_proto/cast_logs.proto.
int GetVirtualConnectPlatformValue() {
#if defined(OS_WIN)
return 3;
#elif defined(OS_MACOSX)
return 4;
#elif defined(OS_CHROMEOS)
return 5;
#elif defined(OS_LINUX)
return 6;
#else
return 0;
#endif
}
// Maps from from API-internal message types to "real" message types from the
// Cast V2 protocol. This is necessary because the protocol defines messages
// with the same type in different namespaces, and the namespace is lost when
// messages are passed using a CastInternalMessage object.
base::StringPiece GetRemappedMediaRequestType(
base::StringPiece v2_message_type) {
base::Optional<V2MessageType> type =
StringToEnum<V2MessageType>(v2_message_type);
DCHECK(type && IsMediaRequestMessageType(*type));
switch (*type) {
case V2MessageType::kStopMedia:
type = V2MessageType::kStop;
break;
case V2MessageType::kMediaSetVolume:
type = V2MessageType::kSetVolume;
break;
case V2MessageType::kMediaGetStatus:
type = V2MessageType::kGetStatus;
break;
default:
return v2_message_type;
}
return *EnumToString(*type);
}
} // namespace
bool IsCastMessageValid(const CastMessage& message_proto) {
if (!message_proto.IsInitialized())
return false;
if (message_proto.namespace_().empty() || message_proto.source_id().empty() ||
message_proto.destination_id().empty()) {
return false;
}
return (message_proto.payload_type() == CastMessage_PayloadType_STRING &&
message_proto.has_payload_utf8()) ||
(message_proto.payload_type() == CastMessage_PayloadType_BINARY &&
message_proto.has_payload_binary());
}
bool IsCastInternalNamespace(const std::string& message_namespace) {
// Note: any namespace with the prefix is assumed to be reserved for internal
// messages.
return base::StartsWith(message_namespace, kCastInternalNamespacePrefix,
base::CompareCase::SENSITIVE);
}
CastMessageType ParseMessageTypeFromPayload(const base::Value& payload) {
const Value* type_string = payload.FindKeyOfType("type", Value::Type::STRING);
return type_string ? CastMessageTypeFromString(type_string->GetString())
: CastMessageType::kOther;
}
const char* ToString(CastMessageType message_type) {
return EnumToString(message_type).value_or("").data();
}
const char* ToString(V2MessageType message_type) {
return EnumToString(message_type).value_or(nullptr).data();
}
CastMessageType CastMessageTypeFromString(const std::string& type) {
auto result = StringToEnum<CastMessageType>(type);
DVLOG_IF(1, !result) << "Unknown message type: " << type;
return result.value_or(CastMessageType::kOther);
}
V2MessageType V2MessageTypeFromString(const std::string& type) {
return StringToEnum<V2MessageType>(type).value_or(V2MessageType::kOther);
}
std::string CastMessageToString(const CastMessage& message_proto) {
std::string out("{");
out += "namespace = " + message_proto.namespace_();
out += ", sourceId = " + message_proto.source_id();
out += ", destId = " + message_proto.destination_id();
out += ", type = " + base::NumberToString(message_proto.payload_type());
out += ", str = \"" + message_proto.payload_utf8() + "\"}";
return out;
}
std::string AuthMessageToString(const DeviceAuthMessage& message) {
std::string out("{");
if (message.has_challenge()) {
out += "challenge: {}, ";
}
if (message.has_response()) {
out += "response: {signature: (";
out += base::NumberToString(message.response().signature().length());
out += " bytes), certificate: (";
out += base::NumberToString(
message.response().client_auth_certificate().length());
out += " bytes)}";
}
if (message.has_error()) {
out += ", error: {";
out += base::NumberToString(message.error().error_type());
out += "}";
}
out += "}";
return out;
}
void CreateAuthChallengeMessage(CastMessage* message_proto,
const AuthContext& auth_context) {
CHECK(message_proto);
DeviceAuthMessage auth_message;
AuthChallenge* challenge = auth_message.mutable_challenge();
DCHECK(challenge);
challenge->set_sender_nonce(auth_context.nonce());
challenge->set_hash_algorithm(SHA256);
std::string auth_message_string;
auth_message.SerializeToString(&auth_message_string);
FillCommonCastMessageFields(message_proto, kPlatformSenderId,
kPlatformReceiverId, kAuthNamespace);
message_proto->set_payload_type(CastMessage_PayloadType_BINARY);
message_proto->set_payload_binary(auth_message_string);
}
bool IsAuthMessage(const CastMessage& message) {
return message.namespace_() == kAuthNamespace;
}
bool IsReceiverMessage(const CastMessage& message) {
return message.namespace_() == kReceiverNamespace;
}
bool IsPlatformSenderMessage(const CastMessage& message) {
return message.destination_id() != cast_channel::kPlatformSenderId;
}
CastMessage CreateKeepAlivePingMessage() {
return CreateKeepAliveMessage(
EnumToString<CastMessageType, CastMessageType::kPing>());
}
CastMessage CreateKeepAlivePongMessage() {
return CreateKeepAliveMessage(
EnumToString<CastMessageType, CastMessageType::kPong>());
}
CastMessage CreateVirtualConnectionRequest(
const std::string& source_id,
const std::string& destination_id,
VirtualConnectionType connection_type,
const std::string& user_agent,
const std::string& browser_version) {
DCHECK(destination_id != kPlatformReceiverId || connection_type == kStrong);
// Parse system_version from user agent string. It contains platform, OS and
// CPU info and is contained in the first set of parentheses of the user
// agent string (e.g., X11; Linux x86_64).
std::string system_version;
size_t start_index = user_agent.find('(');
if (start_index != std::string::npos) {
size_t end_index = user_agent.find(')', start_index + 1);
if (end_index != std::string::npos) {
system_version =
user_agent.substr(start_index + 1, end_index - start_index - 1);
}
}
Value dict(Value::Type::DICTIONARY);
dict.SetKey(
"type",
Value(EnumToString<CastMessageType, CastMessageType::kConnect>()));
dict.SetKey("userAgent", Value(user_agent));
dict.SetKey("connType", Value(connection_type));
dict.SetKey("origin", Value(Value::Type::DICTIONARY));
Value sender_info(Value::Type::DICTIONARY);
sender_info.SetKey("sdkType", Value(kVirtualConnectSdkType));
sender_info.SetKey("version", Value(browser_version));
sender_info.SetKey("browserVersion", Value(browser_version));
sender_info.SetKey("platform", Value(GetVirtualConnectPlatformValue()));
sender_info.SetKey("connectionType", Value(kVirtualConnectTypeLocal));
if (!system_version.empty())
sender_info.SetKey("systemVersion", Value(system_version));
dict.SetKey("senderInfo", std::move(sender_info));
return CreateCastMessage(kConnectionNamespace, dict, source_id,
destination_id);
}
CastMessage CreateGetAppAvailabilityRequest(const std::string& source_id,
int request_id,
const std::string& app_id) {
Value dict(Value::Type::DICTIONARY);
dict.SetKey("type",
Value(EnumToString<CastMessageType,
CastMessageType::kGetAppAvailability>()));
Value app_id_value(Value::Type::LIST);
app_id_value.GetList().push_back(Value(app_id));
dict.SetKey("appId", std::move(app_id_value));
dict.SetKey("requestId", Value(request_id));
return CreateCastMessage(kReceiverNamespace, dict, source_id,
kPlatformReceiverId);
}
CastMessage CreateReceiverStatusRequest(const std::string& source_id,
int request_id) {
Value dict(Value::Type::DICTIONARY);
dict.SetKey("type",
Value(EnumToString<CastMessageType,
CastMessageType::kReceiverStatusRequest>()));
dict.SetKey("requestId", Value(request_id));
return CreateCastMessage(kReceiverNamespace, dict, source_id,
kPlatformReceiverId);
}
BroadcastRequest::BroadcastRequest(const std::string& broadcast_namespace,
const std::string& message)
: broadcast_namespace(broadcast_namespace), message(message) {}
BroadcastRequest::~BroadcastRequest() = default;
bool BroadcastRequest::operator==(const BroadcastRequest& other) const {
return broadcast_namespace == other.broadcast_namespace &&
message == other.message;
}
CastMessage CreateBroadcastRequest(const std::string& source_id,
int request_id,
const std::vector<std::string>& app_ids,
const BroadcastRequest& request) {
Value dict(Value::Type::DICTIONARY);
dict.SetKey(
"type",
Value(EnumToString<CastMessageType, CastMessageType::kBroadcast>()));
std::vector<Value> app_ids_value;
for (const std::string& app_id : app_ids)
app_ids_value.push_back(Value(app_id));
dict.SetKey("appIds", Value(std::move(app_ids_value)));
dict.SetKey("namespace", Value(request.broadcast_namespace));
dict.SetKey("message", Value(request.message));
return CreateCastMessage(kBroadcastNamespace, dict, source_id,
kPlatformReceiverId);
}
CastMessage CreateLaunchRequest(const std::string& source_id,
int request_id,
const std::string& app_id,
const std::string& locale) {
Value dict(Value::Type::DICTIONARY);
dict.SetKey("type",
Value(EnumToString<CastMessageType, CastMessageType::kLaunch>()));
dict.SetKey("requestId", Value(request_id));
dict.SetKey("appId", Value(app_id));
dict.SetKey("language", Value(locale));
return CreateCastMessage(kReceiverNamespace, dict, source_id,
kPlatformReceiverId);
}
CastMessage CreateStopRequest(const std::string& source_id,
int request_id,
const std::string& session_id) {
Value dict(Value::Type::DICTIONARY);
dict.SetKey("type",
Value(EnumToString<CastMessageType, CastMessageType::kStop>()));
dict.SetKey("requestId", Value(request_id));
dict.SetKey("sessionId", Value(session_id));
return CreateCastMessage(kReceiverNamespace, dict, source_id,
kPlatformReceiverId);
}
CastMessage CreateCastMessage(const std::string& message_namespace,
const base::Value& message,
const std::string& source_id,
const std::string& destination_id) {
CastMessage output;
FillCommonCastMessageFields(&output, source_id, destination_id,
message_namespace);
output.set_payload_type(
CastMessage::PayloadType::CastMessage_PayloadType_STRING);
CHECK(base::JSONWriter::Write(message, output.mutable_payload_utf8()));
return output;
}
CastMessage CreateMediaRequest(const base::Value& body,
int request_id,
const std::string& source_id,
const std::string& destination_id) {
Value dict = body.Clone();
Value* type = dict.FindKeyOfType("type", Value::Type::STRING);
CHECK(type);
dict.SetKey("type", Value(GetRemappedMediaRequestType(type->GetString())));
dict.SetKey("requestId", Value(request_id));
return CreateCastMessage(kMediaNamespace, dict, source_id, destination_id);
}
CastMessage CreateSetVolumeRequest(const base::Value& body,
int request_id,
const std::string& source_id) {
DCHECK(body.FindKeyOfType("type", Value::Type::STRING) &&
body.FindKeyOfType("type", Value::Type::STRING)->GetString() ==
(EnumToString<V2MessageType, V2MessageType::kSetVolume>())
.as_string());
Value dict = body.Clone();
dict.RemoveKey("sessionId");
dict.SetKey("requestId", Value(request_id));
return CreateCastMessage(kReceiverNamespace, dict, source_id,
kPlatformReceiverId);
}
bool IsMediaRequestMessageType(V2MessageType type) {
switch (type) {
case V2MessageType::kEditTracksInfo:
case V2MessageType::kLoad:
case V2MessageType::kMediaGetStatus:
case V2MessageType::kMediaSetVolume:
case V2MessageType::kPause:
case V2MessageType::kPlay:
case V2MessageType::kPrecache:
case V2MessageType::kQueueInsert:
case V2MessageType::kQueueLoad:
case V2MessageType::kQueueRemove:
case V2MessageType::kQueueReorder:
case V2MessageType::kQueueUpdate:
case V2MessageType::kSeek:
case V2MessageType::kStopMedia:
return true;
default:
return false;
}
}
const char* ToString(GetAppAvailabilityResult result) {
return EnumToString(result).value_or(nullptr).data();
}
base::Optional<int> GetRequestIdFromResponse(const Value& payload) {
DCHECK(payload.is_dict());
const Value* request_id_value =
payload.FindKeyOfType("requestId", Value::Type::INTEGER);
if (!request_id_value)
return base::nullopt;
return request_id_value->GetInt();
}
GetAppAvailabilityResult GetAppAvailabilityResultFromResponse(
const Value& payload,
const std::string& app_id) {
DCHECK(payload.is_dict());
const Value* availability_value =
payload.FindPathOfType({"availability", app_id}, Value::Type::STRING);
if (!availability_value)
return GetAppAvailabilityResult::kUnknown;
return StringToEnum<GetAppAvailabilityResult>(availability_value->GetString())
.value_or(GetAppAvailabilityResult::kUnknown);
}
LaunchSessionResponse::LaunchSessionResponse() {}
LaunchSessionResponse::LaunchSessionResponse(LaunchSessionResponse&& other) =
default;
LaunchSessionResponse::~LaunchSessionResponse() = default;
LaunchSessionResponse GetLaunchSessionResponse(const base::Value& payload) {
const Value* type_value = payload.FindKeyOfType("type", Value::Type::STRING);
if (!type_value)
return LaunchSessionResponse();
CastMessageType type = CastMessageTypeFromString(type_value->GetString());
if (type != CastMessageType::kReceiverStatus &&
type != CastMessageType::kLaunchError)
return LaunchSessionResponse();
LaunchSessionResponse response;
if (type == CastMessageType::kLaunchError) {
response.result = LaunchSessionResponse::Result::kError;
return response;
}
const Value* receiver_status =
payload.FindKeyOfType("status", Value::Type::DICTIONARY);
if (!receiver_status)
return LaunchSessionResponse();
response.result = LaunchSessionResponse::Result::kOk;
response.receiver_status = receiver_status->Clone();
return response;
}
} // namespace cast_channel