Commit ef89b3f2 authored by Xiangjun Zhang's avatar Xiangjun Zhang Committed by Commit Bot

Mirroring service: Add cast message dispatcher.

Add MessageDispatcher to handle sending/receiving cast messages.

Bug: 734672
Change-Id: I1941ba72107b4629da8c47f0b726484d800b2891
Reviewed-on: https://chromium-review.googlesource.com/1008729
Commit-Queue: Xiangjun Zhang <xjz@chromium.org>
Reviewed-by: default avatarYuri Wiitala <miu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#555622}
parent 79e486bd
...@@ -24,12 +24,18 @@ source_set("interface") { ...@@ -24,12 +24,18 @@ source_set("interface") {
source_set("service") { source_set("service") {
sources = [ sources = [
"message_dispatcher.cc",
"message_dispatcher.h",
"receiver_response.cc",
"receiver_response.h",
"rtp_stream.cc", "rtp_stream.cc",
"rtp_stream.h", "rtp_stream.h",
"session.cc", "session.cc",
"session.h", "session.h",
"udp_socket_client.cc", "udp_socket_client.cc",
"udp_socket_client.h", "udp_socket_client.h",
"value_util.cc",
"value_util.h",
"video_capture_client.cc", "video_capture_client.cc",
"video_capture_client.h", "video_capture_client.h",
] ]
...@@ -61,6 +67,8 @@ source_set("unittests") { ...@@ -61,6 +67,8 @@ source_set("unittests") {
"fake_network_service.h", "fake_network_service.h",
"fake_video_capture_host.cc", "fake_video_capture_host.cc",
"fake_video_capture_host.h", "fake_video_capture_host.h",
"message_dispatcher_unittest.cc",
"receiver_response_unittest.cc",
"rtp_stream_unittest.cc", "rtp_stream_unittest.cc",
"session_unittest.cc", "session_unittest.cc",
"udp_socket_client_unittest.cc", "udp_socket_client_unittest.cc",
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#ifndef COMPONENTS_MIRRORING_SERVICE_INTERFACE_H_ #ifndef COMPONENTS_MIRRORING_SERVICE_INTERFACE_H_
#define COMPONENTS_MIRRORING_SERVICE_INTERFACE_H_ #define COMPONENTS_MIRRORING_SERVICE_INTERFACE_H_
#include <string>
#include <vector> #include <vector>
#include "base/callback.h" #include "base/callback.h"
...@@ -34,6 +35,20 @@ enum SessionType { ...@@ -34,6 +35,20 @@ enum SessionType {
AUDIO_AND_VIDEO, AUDIO_AND_VIDEO,
}; };
constexpr char kRemotingNamespace[] = "urn:x-cast:com.google.cast.remoting";
constexpr char kWebRtcNamespace[] = "urn:x-cast:com.google.cast.webrtc";
struct CastMessage {
std::string message_namespace;
base::Value data;
};
class CastMessageChannel {
public:
virtual ~CastMessageChannel() {}
virtual void Send(const CastMessage& message) = 0;
};
class SessionClient { class SessionClient {
public: public:
virtual ~SessionClient() {} virtual ~SessionClient() {}
...@@ -49,7 +64,7 @@ class SessionClient { ...@@ -49,7 +64,7 @@ class SessionClient {
virtual void GetVideoCaptureHost( virtual void GetVideoCaptureHost(
media::mojom::VideoCaptureHostRequest request) = 0; media::mojom::VideoCaptureHostRequest request) = 0;
virtual void GetNewWorkContext( virtual void GetNetWorkContext(
network::mojom::NetworkContextRequest request) = 0; network::mojom::NetworkContextRequest request) = 0;
// TODO(xjz): Add interface to get AudioCaptureHost. // TODO(xjz): Add interface to get AudioCaptureHost.
// TODO(xjz): Add interface for HW encoder profiles query and VEA create // TODO(xjz): Add interface for HW encoder profiles query and VEA create
......
// Copyright 2018 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/mirroring/service/message_dispatcher.h"
#include "base/bind_helpers.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/rand_util.h"
namespace mirroring {
namespace {
std::string GetMessageString(const CastMessage& message) {
std::string message_string;
base::JSONWriter::Write(message.data, &message_string);
return message_string;
}
} // namespace
// Holds a request until |timeout| elapses or an acceptable response is
// received. When timeout, |response_callback| runs with an UNKNOWN type
// response.
class MessageDispatcher::RequestHolder {
public:
RequestHolder() {}
~RequestHolder() {
if (!response_callback_.is_null())
std::move(response_callback_).Run(ReceiverResponse());
}
void Start(const base::TimeDelta& timeout,
int32_t sequence_number,
OnceResponseCallback response_callback) {
response_callback_ = std::move(response_callback);
sequence_number_ = sequence_number;
DCHECK(!response_callback_.is_null());
timer_.Start(
FROM_HERE, timeout,
base::BindRepeating(&RequestHolder::SendResponse,
base::Unretained(this), ReceiverResponse()));
}
// Send |response| if the sequence number matches, or if the request times
// out, in which case the |response| is UNKNOWN type.
void SendResponse(const ReceiverResponse& response) {
if (!timer_.IsRunning() || response.sequence_number == sequence_number_)
std::move(response_callback_).Run(response);
// Ignore the response with mismatched sequence number.
}
private:
OnceResponseCallback response_callback_;
base::OneShotTimer timer_;
int32_t sequence_number_ = -1;
DISALLOW_COPY_AND_ASSIGN(RequestHolder);
};
MessageDispatcher::MessageDispatcher(CastMessageChannel* outbound_channel,
ErrorCallback error_callback)
: outbound_channel_(outbound_channel),
error_callback_(std::move(error_callback)),
last_sequence_number_(base::RandInt(0, 1e9)) {
DCHECK(outbound_channel_);
DCHECK(!error_callback_.is_null());
}
MessageDispatcher::~MessageDispatcher() {
// Prevent the re-entrant operations on |callback_map_|.
decltype(callback_map_) subscriptions;
subscriptions.swap(callback_map_);
subscriptions.clear();
}
void MessageDispatcher::Send(const CastMessage& message) {
if (message.message_namespace != kWebRtcNamespace &&
message.message_namespace != kRemotingNamespace) {
DVLOG(2) << "Ignore message with unknown namespace = "
<< message.message_namespace;
return; // Ignore message with wrong namespace.
}
if (message.data.is_none())
return; // Ignore null message.
ReceiverResponse response;
if (!response.Parse(message.data)) {
error_callback_.Run("Response parsing error. message=" +
GetMessageString(message));
return;
}
#if DCHECK_IS_ON()
if (response.type == ResponseType::RPC)
DCHECK_EQ(kRemotingNamespace, message.message_namespace);
else
DCHECK_EQ(kWebRtcNamespace, message.message_namespace);
#endif // DCHECK_IS_ON()
const auto callback_iter = callback_map_.find(response.type);
if (callback_iter == callback_map_.end()) {
error_callback_.Run("No callback subscribed. message=" +
GetMessageString(message));
return;
}
callback_iter->second.Run(response);
}
void MessageDispatcher::Subscribe(ResponseType type,
ResponseCallback callback) {
DCHECK(type != ResponseType::UNKNOWN);
DCHECK(!callback.is_null());
const auto insert_result =
callback_map_.emplace(std::make_pair(type, std::move(callback)));
DCHECK(insert_result.second);
}
void MessageDispatcher::Unsubscribe(ResponseType type) {
auto iter = callback_map_.find(type);
if (iter != callback_map_.end())
callback_map_.erase(iter);
}
int32_t MessageDispatcher::GetNextSeqNumber() {
// Skip 0, which is used by Cast receiver to indicate that the broadcast
// status message is not coming from a specific sender (it is an autonomous
// status change, not triggered by a command from any sender). Strange usage
// of 0 though; could be a null / optional field.
return ++last_sequence_number_;
}
void MessageDispatcher::SendOutboundMessage(const CastMessage& message) {
outbound_channel_->Send(message);
}
void MessageDispatcher::RequestReply(const CastMessage& message,
ResponseType response_type,
int32_t sequence_number,
const base::TimeDelta& timeout,
OnceResponseCallback callback) {
DCHECK(!callback.is_null());
DCHECK(timeout > base::TimeDelta());
RequestHolder* const request_holder = new RequestHolder();
request_holder->Start(
timeout, sequence_number,
base::BindOnce(
[](MessageDispatcher* dispatcher, ResponseType response_type,
OnceResponseCallback callback, const ReceiverResponse& response) {
dispatcher->Unsubscribe(response_type);
std::move(callback).Run(response);
},
this, response_type, std::move(callback)));
// |request_holder| keeps alive until the callback is unsubscribed.
Subscribe(response_type, base::BindRepeating(
[](RequestHolder* request_holder,
const ReceiverResponse& response) {
request_holder->SendResponse(response);
},
base::Owned(request_holder)));
SendOutboundMessage(message);
}
} // namespace mirroring
// Copyright 2018 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.
#ifndef COMPONENTS_MIRRORING_SERVICE_MESSAGE_DISPATCHER_H_
#define COMPONENTS_MIRRORING_SERVICE_MESSAGE_DISPATCHER_H_
#include "base/callback.h"
#include "base/containers/flat_map.h"
#include "base/macros.h"
#include "components/mirroring/service/interface.h"
#include "components/mirroring/service/receiver_response.h"
namespace mirroring {
// Dispatches inbound/outbound messages. The outbound messages are sent out
// through |outbound_channel|, and the inbound messages are handled by this
// class.
class MessageDispatcher final : public CastMessageChannel {
public:
using ErrorCallback = base::RepeatingCallback<void(const std::string&)>;
// TODO(xjz): Also pass a CastMessageChannel interface request for inbound
// message channel.
MessageDispatcher(CastMessageChannel* outbound_channel,
ErrorCallback error_callback);
~MessageDispatcher() override;
using ResponseCallback =
base::RepeatingCallback<void(const ReceiverResponse& response)>;
// Registers/Unregisters callback for a certain type of responses.
void Subscribe(ResponseType type, ResponseCallback callback);
void Unsubscribe(ResponseType type);
using OnceResponseCallback =
base::OnceCallback<void(const ReceiverResponse& response)>;
// Sends the given message and subscribes to replies until an acceptable one
// is received or a timeout elapses. Message of the given response type is
// delivered to the supplied callback if the sequence number of the response
// matches |sequence_number|. If the timeout period elapses, the callback will
// be run once with an unknown type of |response|.
void RequestReply(const CastMessage& message,
ResponseType response_type,
int32_t sequence_number,
const base::TimeDelta& timeout,
OnceResponseCallback callback);
// Get the sequence number for the next outbound message. Never returns 0.
int32_t GetNextSeqNumber();
// Requests to send outbound |message|.
void SendOutboundMessage(const CastMessage& message);
private:
class RequestHolder;
// CastMessageChannel implementation. Handles inbound messages.
void Send(const CastMessage& message) override;
// Takes care of sending outbound messages.
CastMessageChannel* const outbound_channel_;
const ErrorCallback error_callback_;
int32_t last_sequence_number_;
// Holds callbacks for different types of responses.
base::flat_map<ResponseType, ResponseCallback> callback_map_;
DISALLOW_COPY_AND_ASSIGN(MessageDispatcher);
};
} // namespace mirroring
#endif // COMPONENTS_MIRRORING_SERVICE_MESSAGE_DISPATCHER_H_
// Copyright 2018 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/mirroring/service/message_dispatcher.h"
#include "base/base64.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/json/json_reader.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_task_environment.h"
#include "base/values.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::InvokeWithoutArgs;
using ::testing::_;
namespace mirroring {
namespace {
// Generates a CastMessage from the JSON format |message_string|.
CastMessage GenerateCastMessage(const std::string& message_string,
const std::string& message_namespace) {
std::unique_ptr<base::Value> value = base::JSONReader::Read(message_string);
EXPECT_TRUE(value);
CastMessage message;
message.message_namespace = message_namespace;
message.data = base::Value::FromUniquePtrValue(std::move(value));
return message;
}
bool IsEqual(const CastMessage& message1, const CastMessage& message2) {
return message1.message_namespace == message2.message_namespace &&
message1.data == message2.data;
}
void CloneResponse(const ReceiverResponse& response,
ReceiverResponse* cloned_response) {
cloned_response->type = response.type;
cloned_response->session_id = response.session_id;
cloned_response->sequence_number = response.sequence_number;
cloned_response->result = response.result;
if (response.answer)
cloned_response->answer = std::make_unique<Answer>(*response.answer);
if (response.status)
cloned_response->status =
std::make_unique<ReceiverStatus>(*response.status);
if (response.capabilities) {
cloned_response->capabilities =
std::make_unique<ReceiverCapability>(*response.capabilities);
}
cloned_response->rpc = response.rpc;
if (response.error) {
cloned_response->error = std::make_unique<ReceiverError>();
cloned_response->error->code = response.error->code;
cloned_response->error->description = response.error->description;
cloned_response->error->details = response.error->details.Clone();
}
}
} // namespace
class MessageDispatcherTest : public CastMessageChannel,
public ::testing::Test {
public:
MessageDispatcherTest() {
message_dispatcher_ = std::make_unique<MessageDispatcher>(
this, base::BindRepeating(&MessageDispatcherTest::OnParsingError,
base::Unretained(this)));
message_dispatcher_->Subscribe(
ResponseType::ANSWER,
base::BindRepeating(&MessageDispatcherTest::OnAnswerResponse,
base::Unretained(this)));
message_dispatcher_->Subscribe(
ResponseType::STATUS_RESPONSE,
base::BindRepeating(&MessageDispatcherTest::OnStatusResponse,
base::Unretained(this)));
}
~MessageDispatcherTest() override { scoped_task_environment_.RunUntilIdle(); }
void OnParsingError(const std::string& error_message) {
last_error_message_ = error_message;
}
void OnAnswerResponse(const ReceiverResponse& response) {
if (!last_answer_response_)
last_answer_response_ = std::make_unique<ReceiverResponse>();
CloneResponse(response, last_answer_response_.get());
}
void OnStatusResponse(const ReceiverResponse& response) {
if (!last_status_response_)
last_status_response_ = std::make_unique<ReceiverResponse>();
CloneResponse(response, last_status_response_.get());
}
protected:
// CastMessageChannel implementation. Handles outbound message.
void Send(const CastMessage& message) override {
last_outbound_message_.message_namespace = message.message_namespace;
last_outbound_message_.data = message.data.Clone();
}
// Simulates receiving an inbound message from receiver.
void SendInboundMessage(const CastMessage& message) {
CastMessageChannel* inbound_message_channel = message_dispatcher_.get();
inbound_message_channel->Send(message);
}
base::test::ScopedTaskEnvironment scoped_task_environment_;
std::unique_ptr<MessageDispatcher> message_dispatcher_;
CastMessage last_outbound_message_;
std::string last_error_message_;
std::unique_ptr<ReceiverResponse> last_answer_response_;
std::unique_ptr<ReceiverResponse> last_status_response_;
private:
DISALLOW_COPY_AND_ASSIGN(MessageDispatcherTest);
};
TEST_F(MessageDispatcherTest, SendsOutboundMessage) {
const std::string test1 = "{\"a\": 1, \"b\": 2}";
const CastMessage message1 = GenerateCastMessage(test1, kWebRtcNamespace);
message_dispatcher_->SendOutboundMessage(message1);
scoped_task_environment_.RunUntilIdle();
EXPECT_TRUE(IsEqual(message1, last_outbound_message_));
EXPECT_TRUE(last_error_message_.empty());
const std::string test2 = "{\"m\": 99, \"i\": 98, \"u\": 97}";
const CastMessage message2 = GenerateCastMessage(test2, kWebRtcNamespace);
message_dispatcher_->SendOutboundMessage(message2);
scoped_task_environment_.RunUntilIdle();
EXPECT_TRUE(IsEqual(message2, last_outbound_message_));
EXPECT_TRUE(last_error_message_.empty());
}
TEST_F(MessageDispatcherTest, DispatchMessageToSubscriber) {
// Simulate a receiver ANSWER response and expect that just the ANSWER
// subscriber processes the message.
const std::string answer_response =
"{\"type\":\"ANSWER\",\"seqNum\":12345,\"result\":\"ok\","
"\"answer\":{\"udpPort\":50691}}";
const CastMessage answer_message =
GenerateCastMessage(answer_response, kWebRtcNamespace);
SendInboundMessage(answer_message);
scoped_task_environment_.RunUntilIdle();
EXPECT_TRUE(last_answer_response_);
EXPECT_FALSE(last_status_response_);
EXPECT_EQ(12345, last_answer_response_->sequence_number);
EXPECT_EQ(ResponseType::ANSWER, last_answer_response_->type);
EXPECT_EQ("ok", last_answer_response_->result);
EXPECT_EQ(50691, last_answer_response_->answer->udp_port);
EXPECT_FALSE(last_answer_response_->status);
last_answer_response_.reset();
EXPECT_TRUE(last_error_message_.empty());
// Simulate a receiver STATUS_RESPONSE and expect that just the
// STATUS_RESPONSE subscriber processes the message.
const std::string status_response =
"{\"type\":\"STATUS_RESPONSE\",\"seqNum\":12345,\"result\":\"ok\","
"\"status\":{\"wifiSnr\":42}}";
const CastMessage status_message =
GenerateCastMessage(status_response, kWebRtcNamespace);
SendInboundMessage(status_message);
scoped_task_environment_.RunUntilIdle();
EXPECT_FALSE(last_answer_response_);
EXPECT_TRUE(last_status_response_);
EXPECT_EQ(12345, last_status_response_->sequence_number);
EXPECT_EQ(ResponseType::STATUS_RESPONSE, last_status_response_->type);
EXPECT_EQ("ok", last_status_response_->result);
EXPECT_EQ(42, last_status_response_->status->wifi_snr);
last_status_response_.reset();
EXPECT_TRUE(last_error_message_.empty());
// Unsubscribe from ANSWER messages, and when feeding-in an ANSWER message,
// nothing should happen.
message_dispatcher_->Unsubscribe(ResponseType::ANSWER);
SendInboundMessage(answer_message);
scoped_task_environment_.RunUntilIdle();
EXPECT_FALSE(last_answer_response_);
EXPECT_FALSE(last_status_response_);
EXPECT_FALSE(last_error_message_.empty()); // Expect an error reported.
last_error_message_.clear();
// However, STATUS_RESPONSE messages should still be dispatcher to the
// remaining subscriber.
SendInboundMessage(status_message);
scoped_task_environment_.RunUntilIdle();
EXPECT_FALSE(last_answer_response_);
EXPECT_TRUE(last_status_response_);
last_status_response_.reset();
EXPECT_TRUE(last_error_message_.empty());
// Finally, unsubscribe from STATUS_RESPONSE messages, and when feeding-in
// either an ANSWER or a STATUS_RESPONSE message, nothing should happen.
message_dispatcher_->Unsubscribe(ResponseType::STATUS_RESPONSE);
SendInboundMessage(answer_message);
scoped_task_environment_.RunUntilIdle();
EXPECT_FALSE(last_answer_response_);
EXPECT_FALSE(last_status_response_);
EXPECT_FALSE(last_error_message_.empty());
last_error_message_.clear();
SendInboundMessage(status_message);
scoped_task_environment_.RunUntilIdle();
EXPECT_FALSE(last_answer_response_);
EXPECT_FALSE(last_status_response_);
EXPECT_FALSE(last_error_message_.empty());
}
TEST_F(MessageDispatcherTest, IgnoreMalformedMessage) {
CastMessage message;
message.message_namespace = kWebRtcNamespace;
message.data = base::Value("MUAHAHAHAHAHAHAHA!");
SendInboundMessage(message);
scoped_task_environment_.RunUntilIdle();
EXPECT_FALSE(last_answer_response_);
EXPECT_FALSE(last_status_response_);
EXPECT_FALSE(last_error_message_.empty());
}
TEST_F(MessageDispatcherTest, IgnoreMessageWithWrongNamespace) {
const std::string answer_response =
"{\"type\":\"ANSWER\",\"seqNum\":12345,\"result\":\"ok\","
"\"answer\":{\"udpPort\":50691}}";
const CastMessage answer_message =
GenerateCastMessage(answer_response, "Wrong_namespace");
SendInboundMessage(answer_message);
scoped_task_environment_.RunUntilIdle();
EXPECT_FALSE(last_answer_response_);
EXPECT_FALSE(last_status_response_);
// Messages with different namespace are ignored with no error reported.
EXPECT_TRUE(last_error_message_.empty());
}
TEST_F(MessageDispatcherTest, RequestReply) {
EXPECT_FALSE(last_answer_response_);
EXPECT_FALSE(last_status_response_);
message_dispatcher_->Unsubscribe(ResponseType::ANSWER);
scoped_task_environment_.RunUntilIdle();
const std::string fake_offer = "{\"type\":\"OFFER\",\"seqNum\":45623}";
const CastMessage offer_message =
GenerateCastMessage(fake_offer, kWebRtcNamespace);
message_dispatcher_->RequestReply(
offer_message, ResponseType::ANSWER, 45623,
base::TimeDelta::FromMilliseconds(100),
base::BindRepeating(&MessageDispatcherTest::OnAnswerResponse,
base::Unretained(this)));
scoped_task_environment_.RunUntilIdle();
// Received the request to send the outbound message.
EXPECT_TRUE(IsEqual(offer_message, last_outbound_message_));
std::string answer_response =
"{\"type\":\"ANSWER\",\"seqNum\":12345,\"result\":\"ok\","
"\"answer\":{\"udpPort\":50691}}";
CastMessage answer_message =
GenerateCastMessage(answer_response, kWebRtcNamespace);
SendInboundMessage(answer_message);
scoped_task_environment_.RunUntilIdle();
// The answer message with mismatched sequence number is ignored.
EXPECT_FALSE(last_answer_response_);
EXPECT_FALSE(last_status_response_);
EXPECT_TRUE(last_error_message_.empty());
answer_response =
"{\"type\":\"ANSWER\",\"seqNum\":45623,\"result\":\"ok\","
"\"answer\":{\"udpPort\":50691}}";
answer_message = GenerateCastMessage(answer_response, kWebRtcNamespace);
SendInboundMessage(answer_message);
scoped_task_environment_.RunUntilIdle();
EXPECT_TRUE(last_answer_response_);
EXPECT_FALSE(last_status_response_);
EXPECT_TRUE(last_error_message_.empty());
EXPECT_EQ(45623, last_answer_response_->sequence_number);
EXPECT_EQ(ResponseType::ANSWER, last_answer_response_->type);
EXPECT_EQ("ok", last_answer_response_->result);
EXPECT_EQ(50691, last_answer_response_->answer->udp_port);
last_answer_response_.reset();
// Expect that the callback for ANSWER message was already unsubscribed.
SendInboundMessage(answer_message);
scoped_task_environment_.RunUntilIdle();
EXPECT_FALSE(last_answer_response_);
EXPECT_FALSE(last_status_response_);
EXPECT_FALSE(last_error_message_.empty());
last_error_message_.clear();
const CastMessage fake_message = GenerateCastMessage(
"{\"type\":\"OFFER\",\"seqNum\":12345}", kWebRtcNamespace);
message_dispatcher_->RequestReply(
fake_message, ResponseType::ANSWER, 12345,
base::TimeDelta::FromMilliseconds(100),
base::BindRepeating(&MessageDispatcherTest::OnAnswerResponse,
base::Unretained(this)));
scoped_task_environment_.RunUntilIdle();
// Received the request to send the outbound message.
EXPECT_TRUE(IsEqual(fake_message, last_outbound_message_));
EXPECT_FALSE(last_answer_response_);
EXPECT_FALSE(last_status_response_);
// Destroy the dispatcher. Expect to receive an unknown type response.
message_dispatcher_.reset();
scoped_task_environment_.RunUntilIdle();
EXPECT_TRUE(last_answer_response_);
EXPECT_FALSE(last_status_response_);
EXPECT_TRUE(last_error_message_.empty());
EXPECT_EQ(ResponseType::UNKNOWN, last_answer_response_->type);
EXPECT_EQ(-1, last_answer_response_->sequence_number);
}
} // namespace mirroring
// Copyright 2018 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/mirroring/service/receiver_response.h"
#include "base/base64.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "components/mirroring/service/value_util.h"
namespace mirroring {
namespace {
// Get the response type from the type string value in the JSON message.
ResponseType GetResponseType(const std::string& type) {
if (type == "ANSWER")
return ResponseType::ANSWER;
if (type == "STATUS_RESPONSE")
return ResponseType::STATUS_RESPONSE;
if (type == "CAPABILITIES_RESPONSE")
return ResponseType::CAPABILITIES_RESPONSE;
if (type == "RPC")
return ResponseType::RPC;
return ResponseType::UNKNOWN;
}
} // namespace
Answer::Answer()
: udp_port(-1), supports_get_status(false), cast_mode("mirroring") {}
Answer::~Answer() {}
Answer::Answer(const Answer& answer) = default;
bool Answer::Parse(const base::Value& raw_value) {
return (raw_value.is_dict() && GetInt(raw_value, "udpPort", &udp_port) &&
GetIntArray(raw_value, "ssrcs", &ssrcs) &&
GetIntArray(raw_value, "sendIndexes", &send_indexes) &&
GetString(raw_value, "IV", &iv) &&
GetBool(raw_value, "receiverGetStatus", &supports_get_status) &&
GetString(raw_value, "castMode", &cast_mode));
}
// ----------------------------------------------------------------------------
ReceiverStatus::ReceiverStatus() : wifi_snr(0) {}
ReceiverStatus::~ReceiverStatus() {}
ReceiverStatus::ReceiverStatus(const ReceiverStatus& status) = default;
bool ReceiverStatus::Parse(const base::Value& raw_value) {
return (raw_value.is_dict() && GetDouble(raw_value, "wifiSnr", &wifi_snr) &&
GetIntArray(raw_value, "wifiSpeed", &wifi_speed));
}
// ----------------------------------------------------------------------------
ReceiverKeySystem::ReceiverKeySystem() {}
ReceiverKeySystem::~ReceiverKeySystem() {}
ReceiverKeySystem::ReceiverKeySystem(
const ReceiverKeySystem& receiver_key_system) = default;
bool ReceiverKeySystem::Parse(const base::Value& raw_value) {
return (raw_value.is_dict() && GetString(raw_value, "keySystemName", &name) &&
GetStringArray(raw_value, "initDataTypes", &init_data_types) &&
GetStringArray(raw_value, "codecs", &codecs) &&
GetStringArray(raw_value, "secureCodecs", &secure_codecs) &&
GetStringArray(raw_value, "audioRobustness", &audio_robustness) &&
GetStringArray(raw_value, "videoRobustness", &video_robustness) &&
GetString(raw_value, "persistentLicenseSessionSupport",
&persistent_license_session_support) &&
GetString(raw_value, "persistentReleaseMessageSessionSupport",
&persistent_release_message_session_support) &&
GetString(raw_value, "persistentStateSupport",
&persistent_state_support) &&
GetString(raw_value, "distinctiveIdentifierSupport",
&distinctive_identifier_support));
}
// ----------------------------------------------------------------------------
ReceiverCapability::ReceiverCapability() {}
ReceiverCapability::~ReceiverCapability() {}
ReceiverCapability::ReceiverCapability(const ReceiverCapability& capabilities) =
default;
bool ReceiverCapability::Parse(const base::Value& raw_value) {
if (!raw_value.is_dict() ||
!GetStringArray(raw_value, "mediaCaps", &media_caps))
return false;
auto* found = raw_value.FindKey("keySystems");
if (!found)
return true;
for (const auto& key_system_value : found->GetList()) {
ReceiverKeySystem key_system;
if (!key_system.Parse(key_system_value))
return false;
key_systems.emplace_back(key_system);
}
return true;
}
// ----------------------------------------------------------------------------
ReceiverError::ReceiverError() : code(-1) {}
ReceiverError::~ReceiverError() {}
bool ReceiverError::Parse(const base::Value& raw_value) {
if (!raw_value.is_dict() || !GetInt(raw_value, "code", &code) ||
!GetString(raw_value, "description", &description))
return false;
auto* found = raw_value.FindKey("details");
if (found && !found->is_dict())
return false;
if (found)
details = found->Clone();
return true;
}
// ----------------------------------------------------------------------------
ReceiverResponse::ReceiverResponse()
: type(ResponseType::UNKNOWN), session_id(-1), sequence_number(-1) {}
ReceiverResponse::~ReceiverResponse() {}
ReceiverResponse::ReceiverResponse(ReceiverResponse&& receiver_response) =
default;
ReceiverResponse& ReceiverResponse::operator=(
ReceiverResponse&& receiver_response) = default;
bool ReceiverResponse::Parse(const base::Value& raw_value) {
if (!raw_value.is_dict() || !GetInt(raw_value, "sessionId", &session_id) ||
!GetInt(raw_value, "seqNum", &sequence_number) ||
!GetString(raw_value, "result", &result))
return false;
if (result == "error") {
auto* found = raw_value.FindKey("error");
if (found) {
error = std::make_unique<ReceiverError>();
if (!error->Parse(*found))
return false;
}
}
std::string message_type;
if (!GetString(raw_value, "type", &message_type))
return false;
// Convert |message_type| to uppercase.
message_type = base::ToUpperASCII(message_type);
type = GetResponseType(message_type);
if (type == ResponseType::UNKNOWN) {
DVLOG(2) << "Unknown response message type= " << message_type;
return false;
}
auto* found = raw_value.FindKey("answer");
if (found && !found->is_none()) {
answer = std::make_unique<Answer>();
if (!answer->Parse(*found))
return false;
}
found = raw_value.FindKey("status");
if (found && !found->is_none()) {
status = std::make_unique<ReceiverStatus>();
if (!status->Parse(*found))
return false;
}
found = raw_value.FindKey("capabilities");
if (found && !found->is_none()) {
capabilities = std::make_unique<ReceiverCapability>();
if (!capabilities->Parse(*found))
return false;
}
found = raw_value.FindKey("rpc");
if (found && !found->is_none()) {
// Decode the base64-encoded string.
if (!found->is_string() || !base::Base64Decode(found->GetString(), &rpc))
return false;
}
return true;
}
} // namespace mirroring
// Copyright 2018 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.
#ifndef COMPONENTS_MIRRORING_SERVICE_RECEIVER_RESPONSE_H_
#define COMPONENTS_MIRRORING_SERVICE_RECEIVER_RESPONSE_H_
#include <memory>
#include <string>
#include <vector>
#include "base/values.h"
namespace mirroring {
// Receiver response message type.
enum ResponseType {
UNKNOWN,
ANSWER, // Response to OFFER message.
STATUS_RESPONSE, // Response to GET_STATUS message.
CAPABILITIES_RESPONSE, // Response to GET_CAPABILITIES message.
RPC, // Rpc binary messages. The payload is base64 encoded.
};
struct Answer {
Answer();
~Answer();
Answer(const Answer& answer);
bool Parse(const base::Value& raw_value);
// The UDP port used for all streams in this session.
int32_t udp_port;
// The indexes chosen from the OFFER message.
std::vector<int32_t> send_indexes;
// The RTP SSRC used to send the RTCP feedback of the stream, indicated by
// the |send_indexes| above.
std::vector<int32_t> ssrcs;
// A 128bit hex number containing the initialization vector for the crypto.
std::string iv;
// Indicates whether receiver supports the GET_STATUS command.
bool supports_get_status;
// "mirroring" for screen mirroring, or "remoting" for media remoting.
std::string cast_mode;
};
struct ReceiverStatus {
ReceiverStatus();
~ReceiverStatus();
ReceiverStatus(const ReceiverStatus& status);
bool Parse(const base::Value& raw_value);
// Current WiFi signal to noise ratio in decibels.
double wifi_snr;
// Min, max, average, and current bandwidth in bps in order of the WiFi link.
// Example: [1200, 1300, 1250, 1230].
std::vector<int32_t> wifi_speed;
};
struct ReceiverKeySystem {
ReceiverKeySystem();
~ReceiverKeySystem();
ReceiverKeySystem(const ReceiverKeySystem& receiver_key_system);
bool Parse(const base::Value& raw_value);
// Reverse URI (e.g. com.widevine.alpha).
std::string name;
// EME init data types (e.g. cenc).
std::vector<std::string> init_data_types;
// Codecs supported by key system. This will include AVC and VP8 on all
// Chromecasts.
std::vector<std::string> codecs;
// Codecs that are also hardware-secure.
std::vector<std::string> secure_codecs;
// Support levels for audio encryption robustness.
std::vector<std::string> audio_robustness;
// Support levels for video encryption robustness.
std::vector<std::string> video_robustness;
std::string persistent_license_session_support;
std::string persistent_release_message_session_support;
std::string persistent_state_support;
std::string distinctive_identifier_support;
};
struct ReceiverCapability {
ReceiverCapability();
~ReceiverCapability();
ReceiverCapability(const ReceiverCapability& capabilities);
bool Parse(const base::Value& raw_value);
// Set of capabilities (e.g., ac3, 4k, hevc, vp9, dolby_vision, etc.).
std::vector<std::string> media_caps;
std::vector<ReceiverKeySystem> key_systems;
};
struct ReceiverError {
ReceiverError();
~ReceiverError();
bool Parse(const base::Value& raw_value);
int32_t code;
std::string description;
base::Value details;
};
struct ReceiverResponse {
ReceiverResponse();
~ReceiverResponse();
ReceiverResponse(ReceiverResponse&& receiver_response);
ReceiverResponse& operator=(ReceiverResponse&& receiver_response);
bool Parse(const base::Value& raw_value);
ResponseType type;
// All messages have same |session_id| for each mirroring session. This value
// is provided by the media router provider.
int32_t session_id;
// This should be same as the value in the corresponding query/OFFER messages
// for non-rpc messages.
int32_t sequence_number;
std::string result; // "ok" or "error".
// Only one of the following has value, according to |type|.
std::unique_ptr<Answer> answer;
std::string rpc;
std::unique_ptr<ReceiverStatus> status;
std::unique_ptr<ReceiverCapability> capabilities;
// Can only be non-null when result is "error".
std::unique_ptr<ReceiverError> error;
};
} // namespace mirroring
#endif // COMPONENTS_MIRRORING_SERVICE_RECEIVER_RESPONSE_H_
// Copyright 2018 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/mirroring/service/receiver_response.h"
#include "base/base64.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/json/json_reader.h"
#include "base/macros.h"
#include "base/test/mock_callback.h"
#include "base/values.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::InvokeWithoutArgs;
using ::testing::_;
namespace mirroring {
class ReceiverResponseTest : public ::testing::Test {
public:
ReceiverResponseTest() {}
~ReceiverResponseTest() override {}
private:
DISALLOW_COPY_AND_ASSIGN(ReceiverResponseTest);
};
TEST_F(ReceiverResponseTest, ParseValidJson) {
const std::string response_string = "{\"type\":\"ANSWER\",\"result\":\"ok\"}";
base::Value value =
base::Value::FromUniquePtrValue(base::JSONReader::Read(response_string));
ReceiverResponse response;
EXPECT_TRUE(response.Parse(value));
EXPECT_EQ(-1, response.session_id);
EXPECT_EQ(-1, response.sequence_number);
EXPECT_EQ(ResponseType::ANSWER, response.type);
EXPECT_EQ("ok", response.result);
EXPECT_FALSE(response.answer);
EXPECT_FALSE(response.status);
EXPECT_FALSE(response.capabilities);
EXPECT_FALSE(response.error);
EXPECT_TRUE(response.rpc.empty());
}
TEST_F(ReceiverResponseTest, ParseInvalidValueType) {
const std::string response_string =
"{\"sessionId\":123, \"seqNum\":\"one-two-three\"}";
base::Value value =
base::Value::FromUniquePtrValue(base::JSONReader::Read(response_string));
ReceiverResponse response;
EXPECT_FALSE(response.Parse(value));
}
TEST_F(ReceiverResponseTest, ParseNonJsonString) {
base::Value value("This is not JSON.");
ReceiverResponse response;
EXPECT_FALSE(response.Parse(value));
}
TEST_F(ReceiverResponseTest, ParseRealWorldAnswerMessage) {
const std::string response_string =
"{\"answer\":{\"receiverRtcpEventLog\":[0,1],\"rtpExtensions\":"
"[[\"adaptive_playout_delay\"],[\"adaptive_playout_delay\"]],"
"\"sendIndexes\":[0,1],\"ssrcs\":[40863,759293],\"udpPort\":50691,"
"\"castMode\":\"mirroring\"},\"result\":\"ok\",\"seqNum\":989031000,"
"\"type\":\"ANSWER\"}";
base::Value value =
base::Value::FromUniquePtrValue(base::JSONReader::Read(response_string));
ReceiverResponse response;
EXPECT_TRUE(response.Parse(value));
EXPECT_EQ(-1, response.session_id);
EXPECT_EQ(989031000, response.sequence_number);
EXPECT_EQ(ResponseType::ANSWER, response.type);
EXPECT_EQ("ok", response.result);
EXPECT_TRUE(response.answer);
EXPECT_EQ(50691, response.answer->udp_port);
EXPECT_EQ(std::vector<int32_t>({0, 1}), response.answer->send_indexes);
EXPECT_EQ(std::vector<int32_t>({40863, 759293}), response.answer->ssrcs);
EXPECT_TRUE(response.answer->iv.empty());
EXPECT_EQ(false, response.answer->supports_get_status);
EXPECT_EQ("mirroring", response.answer->cast_mode);
EXPECT_FALSE(response.status);
EXPECT_FALSE(response.capabilities);
EXPECT_FALSE(response.error);
}
TEST_F(ReceiverResponseTest, ParseErrorMessage) {
const std::string response_string =
"{\"sessionId\": 123,"
"\"seqNum\": 999,"
"\"type\": \"ANSWER\","
"\"result\": \"error\","
"\"error\": {"
"\"code\": 42,"
"\"description\": \"it is broke\","
"\"details\": {\"foo\": -1, \"bar\": 88}"
"}"
"}";
base::Value value =
base::Value::FromUniquePtrValue(base::JSONReader::Read(response_string));
ReceiverResponse response;
EXPECT_TRUE(response.Parse(value));
EXPECT_EQ(123, response.session_id);
EXPECT_EQ(999, response.sequence_number);
EXPECT_EQ(ResponseType::ANSWER, response.type);
EXPECT_EQ("error", response.result);
EXPECT_TRUE(response.error);
EXPECT_EQ(42, response.error->code);
EXPECT_EQ("it is broke", response.error->description);
EXPECT_TRUE(response.error->details.is_dict());
base::Value expected_details = base::Value::FromUniquePtrValue(
base::JSONReader::Read("{\"foo\": -1, \"bar\": 88}"));
EXPECT_EQ(expected_details, response.error->details);
EXPECT_FALSE(response.answer);
EXPECT_FALSE(response.status);
EXPECT_FALSE(response.capabilities);
}
TEST_F(ReceiverResponseTest, ParseStatusMessage) {
const std::string response_string =
"{\"seqNum\": 777,"
"\"type\": \"STATUS_RESPONSE\","
"\"result\": \"ok\","
"\"status\": {"
"\"wifiSnr\": 36.7,"
"\"wifiSpeed\": [1234, 5678, 3000, 3001],"
"\"wifiFcsError\": [12, 13, 12, 12]}" // This will be ignored.
"}";
base::Value value =
base::Value::FromUniquePtrValue(base::JSONReader::Read(response_string));
ReceiverResponse response;
EXPECT_TRUE(response.Parse(value));
EXPECT_EQ(777, response.sequence_number);
EXPECT_EQ(ResponseType::STATUS_RESPONSE, response.type);
EXPECT_EQ("ok", response.result);
EXPECT_FALSE(response.error);
EXPECT_FALSE(response.answer);
EXPECT_TRUE(response.status);
EXPECT_EQ(36.7, response.status->wifi_snr);
const std::vector<int32_t> expect_speed({1234, 5678, 3000, 3001});
EXPECT_EQ(expect_speed, response.status->wifi_speed);
EXPECT_FALSE(response.capabilities);
}
TEST_F(ReceiverResponseTest, ParseCapabilityMessage) {
const std::string response_string =
"{\"sessionId\": 999888777,"
"\"seqNum\": 5551212,"
"\"type\": \"CAPABILITIES_RESPONSE\","
"\"result\": \"ok\","
"\"capabilities\": {"
"\"mediaCaps\": [\"audio\", \"video\", \"vp9\"],"
"\"keySystems\": ["
"{"
"\"keySystemName\": \"com.w3c.clearkey\""
"},"
"{"
"\"keySystemName\": \"com.widevine.alpha\","
"\"initDataTypes\": [\"a\", \"b\", \"c\", \"1\", \"2\", \"3\"],"
"\"codecs\": [\"vp8\", \"h264\"],"
"\"secureCodecs\": [\"h264\", \"vp8\"],"
"\"audioRobustness\": [\"nope\"],"
"\"videoRobustness\": [\"yep\"],"
"\"persistentLicenseSessionSupport\": \"SUPPORTED\","
"\"persistentReleaseMessageSessionSupport\": \"SUPPORTED_WITH_ID\","
"\"persistentStateSupport\": \"REQUESTABLE\","
"\"distinctiveIdentifierSupport\": \"ALWAYS_ENABLED\""
"}"
"]}}";
base::Value value =
base::Value::FromUniquePtrValue(base::JSONReader::Read(response_string));
ReceiverResponse response;
EXPECT_TRUE(response.Parse(value));
EXPECT_EQ(999888777, response.session_id);
EXPECT_EQ(5551212, response.sequence_number);
EXPECT_EQ(ResponseType::CAPABILITIES_RESPONSE, response.type);
EXPECT_EQ("ok", response.result);
EXPECT_FALSE(response.error);
EXPECT_FALSE(response.answer);
EXPECT_FALSE(response.status);
EXPECT_TRUE(response.capabilities);
EXPECT_EQ(std::vector<std::string>({"audio", "video", "vp9"}),
response.capabilities->media_caps);
const ReceiverKeySystem& first_key_system =
response.capabilities->key_systems[0];
EXPECT_EQ("com.w3c.clearkey", first_key_system.name);
EXPECT_TRUE(first_key_system.init_data_types.empty());
EXPECT_TRUE(first_key_system.codecs.empty());
EXPECT_TRUE(first_key_system.secure_codecs.empty());
EXPECT_TRUE(first_key_system.audio_robustness.empty());
EXPECT_TRUE(first_key_system.video_robustness.empty());
EXPECT_TRUE(first_key_system.persistent_license_session_support.empty());
EXPECT_TRUE(
first_key_system.persistent_release_message_session_support.empty());
EXPECT_TRUE(first_key_system.persistent_state_support.empty());
EXPECT_TRUE(first_key_system.distinctive_identifier_support.empty());
const ReceiverKeySystem& second_key_system =
response.capabilities->key_systems[1];
EXPECT_EQ("com.widevine.alpha", second_key_system.name);
EXPECT_EQ(std::vector<std::string>({"a", "b", "c", "1", "2", "3"}),
second_key_system.init_data_types);
EXPECT_EQ(std::vector<std::string>({"vp8", "h264"}),
second_key_system.codecs);
EXPECT_EQ(std::vector<std::string>({"h264", "vp8"}),
second_key_system.secure_codecs);
EXPECT_EQ(std::vector<std::string>({"nope"}),
second_key_system.audio_robustness);
EXPECT_EQ(std::vector<std::string>({"yep"}),
second_key_system.video_robustness);
EXPECT_EQ("SUPPORTED", second_key_system.persistent_license_session_support);
EXPECT_EQ("SUPPORTED_WITH_ID",
second_key_system.persistent_release_message_session_support);
EXPECT_EQ("REQUESTABLE", second_key_system.persistent_state_support);
EXPECT_EQ("ALWAYS_ENABLED", second_key_system.distinctive_identifier_support);
}
TEST_F(ReceiverResponseTest, ParseRpcMessage) {
const std::string message = "Hello from the Cast Receiver!";
std::string rpc_base64;
base::Base64Encode(message, &rpc_base64);
std::string response_string =
"{\"sessionId\": 735189,"
"\"seqNum\": 6789,"
"\"type\": \"RPC\","
"\"result\": \"ok\","
"\"rpc\": \"" +
rpc_base64 + "\"}";
base::Value value =
base::Value::FromUniquePtrValue(base::JSONReader::Read(response_string));
ReceiverResponse response;
EXPECT_TRUE(response.Parse(value));
EXPECT_EQ(735189, response.session_id);
EXPECT_EQ(6789, response.sequence_number);
EXPECT_EQ("ok", response.result);
EXPECT_EQ(ResponseType::RPC, response.type);
EXPECT_EQ(message, response.rpc);
EXPECT_FALSE(response.error);
EXPECT_FALSE(response.answer);
EXPECT_FALSE(response.status);
EXPECT_FALSE(response.capabilities);
}
TEST_F(ReceiverResponseTest, ParseResponseWithNullField) {
const std::string response_string =
"{\"sessionId\":null,\"seqNum\":808907000,\"type\":\"ANSWER\","
"\"result\":\"ok\",\"rpc\":null,\"error\":null,"
"\"answer\":{\"udpPort\":51706,\"sendIndexes\":[0,1],"
"\"ssrcs\":[152818,556029],\"IV\":null,\"receiverGetStatus\":true,"
"\"castMode\":\"mirroring\"},\"status\":null,\"capabilities\":null}";
base::Value value =
base::Value::FromUniquePtrValue(base::JSONReader::Read(response_string));
ReceiverResponse response;
EXPECT_TRUE(response.Parse(value));
EXPECT_EQ(808907000, response.sequence_number);
EXPECT_EQ("ok", response.result);
EXPECT_FALSE(response.error);
EXPECT_FALSE(response.status);
EXPECT_FALSE(response.capabilities);
EXPECT_TRUE(response.rpc.empty());
EXPECT_EQ(ResponseType::ANSWER, response.type);
EXPECT_TRUE(response.answer);
EXPECT_EQ(51706, response.answer->udp_port);
EXPECT_EQ(std::vector<int32_t>({0, 1}), response.answer->send_indexes);
EXPECT_EQ(std::vector<int32_t>({152818, 556029}), response.answer->ssrcs);
EXPECT_TRUE(response.answer->iv.empty());
EXPECT_EQ(true, response.answer->supports_get_status);
EXPECT_EQ("mirroring", response.answer->cast_mode);
}
} // namespace mirroring
...@@ -139,7 +139,7 @@ void Session::StartInternal(const net::IPEndPoint& receiver_endpoint, ...@@ -139,7 +139,7 @@ void Session::StartInternal(const net::IPEndPoint& receiver_endpoint,
base::ThreadTaskRunnerHandle::Get(), audio_encode_thread_, base::ThreadTaskRunnerHandle::Get(), audio_encode_thread_,
video_encode_thread_); video_encode_thread_);
network::mojom::NetworkContextPtr network_context; network::mojom::NetworkContextPtr network_context;
client_->GetNewWorkContext(mojo::MakeRequest(&network_context)); client_->GetNetWorkContext(mojo::MakeRequest(&network_context));
auto udp_client = std::make_unique<UdpSocketClient>( auto udp_client = std::make_unique<UdpSocketClient>(
receiver_endpoint, std::move(network_context), receiver_endpoint, std::move(network_context),
base::BindOnce(&Session::ReportError, weak_factory_.GetWeakPtr(), base::BindOnce(&Session::ReportError, weak_factory_.GetWeakPtr(),
......
...@@ -49,7 +49,7 @@ class SessionTest : public SessionClient, public ::testing::Test { ...@@ -49,7 +49,7 @@ class SessionTest : public SessionClient, public ::testing::Test {
OnGetVideoCaptureHost(); OnGetVideoCaptureHost();
} }
void GetNewWorkContext( void GetNetWorkContext(
network::mojom::NetworkContextRequest request) override { network::mojom::NetworkContextRequest request) override {
network_context_ = std::make_unique<MockNetworkContext>(std::move(request)); network_context_ = std::make_unique<MockNetworkContext>(std::move(request));
OnGetNetworkContext(); OnGetNetworkContext();
......
// Copyright 2018 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/mirroring/service/value_util.h"
namespace mirroring {
bool GetInt(const base::Value& value, const std::string& key, int32_t* result) {
auto* found = value.FindKey(key);
if (!found || found->is_none())
return true;
if (found->is_int()) {
*result = found->GetInt();
return true;
}
return false;
}
bool GetDouble(const base::Value& value,
const std::string& key,
double* result) {
auto* found = value.FindKey(key);
if (!found || found->is_none())
return true;
if (found->is_double()) {
*result = found->GetDouble();
return true;
}
if (found->is_int()) {
*result = found->GetInt();
return true;
}
return false;
}
bool GetString(const base::Value& value,
const std::string& key,
std::string* result) {
auto* found = value.FindKey(key);
if (!found || found->is_none())
return true;
if (found->is_string()) {
*result = found->GetString();
return true;
}
return false;
}
bool GetBool(const base::Value& value, const std::string& key, bool* result) {
auto* found = value.FindKey(key);
if (!found || found->is_none())
return true;
if (found->is_bool()) {
*result = found->GetBool();
return true;
}
return false;
}
bool GetIntArray(const base::Value& value,
const std::string& key,
std::vector<int32_t>* result) {
auto* found = value.FindKey(key);
if (!found || found->is_none())
return true;
if (!found->is_list())
return false;
for (const auto& number_value : found->GetList()) {
if (number_value.is_int())
result->emplace_back(number_value.GetInt());
else
return false;
}
return true;
}
bool GetStringArray(const base::Value& value,
const std::string& key,
std::vector<std::string>* result) {
auto* found = value.FindKey(key);
if (!found || found->is_none())
return true;
if (!found->is_list())
return false;
for (const auto& string_value : found->GetList()) {
if (string_value.is_string())
result->emplace_back(string_value.GetString());
else
return false;
}
return true;
}
} // namespace mirroring
// Copyright 2018 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.
#ifndef COMPONENTS_MIRRORING_SERVICE_VALUE_UTIL_H_
#define COMPONENTS_MIRRORING_SERVICE_VALUE_UTIL_H_
#include <string>
#include "base/values.h"
namespace mirroring {
// Read certain type of data from dictionary |value| if |key| exits. Return
// false if |key| exists and the type of the data mismatches. Return true
// otherwise.
bool GetInt(const base::Value& value, const std::string& key, int32_t* result);
bool GetDouble(const base::Value& value,
const std::string& key,
double* result);
bool GetString(const base::Value& value,
const std::string& key,
std::string* result);
bool GetBool(const base::Value& value, const std::string& key, bool* result);
bool GetIntArray(const base::Value& value,
const std::string& key,
std::vector<int32_t>* result);
bool GetStringArray(const base::Value& value,
const std::string& key,
std::vector<std::string>* result);
} // namespace mirroring
#endif // COMPONENTS_MIRRORING_SERVICE_VALUE_UTIL_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment