You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
215 lines
8.1 KiB
215 lines
8.1 KiB
// 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/sender/cast_platform_client.h"
|
|
|
|
#include <memory>
|
|
#include <random>
|
|
#include <utility>
|
|
|
|
#include "absl/strings/str_cat.h"
|
|
#include "cast/common/channel/virtual_connection_router.h"
|
|
#include "cast/common/public/cast_socket.h"
|
|
#include "cast/common/public/service_info.h"
|
|
#include "util/json/json_serialization.h"
|
|
#include "util/osp_logging.h"
|
|
#include "util/stringprintf.h"
|
|
|
|
namespace openscreen {
|
|
namespace cast {
|
|
|
|
static constexpr std::chrono::seconds kRequestTimeout = std::chrono::seconds(5);
|
|
|
|
CastPlatformClient::CastPlatformClient(VirtualConnectionRouter* router,
|
|
ClockNowFunctionPtr clock,
|
|
TaskRunner* task_runner)
|
|
: sender_id_(MakeUniqueSessionId("sender")),
|
|
virtual_conn_router_(router),
|
|
clock_(clock),
|
|
task_runner_(task_runner) {
|
|
OSP_DCHECK(virtual_conn_router_);
|
|
OSP_DCHECK(clock_);
|
|
OSP_DCHECK(task_runner_);
|
|
virtual_conn_router_->AddHandlerForLocalId(sender_id_, this);
|
|
}
|
|
|
|
CastPlatformClient::~CastPlatformClient() {
|
|
virtual_conn_router_->RemoveConnectionsByLocalId(sender_id_);
|
|
virtual_conn_router_->RemoveHandlerForLocalId(sender_id_);
|
|
|
|
for (auto& pending_requests : pending_requests_by_device_id_) {
|
|
for (auto& avail_request : pending_requests.second.availability) {
|
|
avail_request.callback(avail_request.app_id,
|
|
AppAvailabilityResult::kUnknown);
|
|
}
|
|
}
|
|
}
|
|
|
|
absl::optional<int> CastPlatformClient::RequestAppAvailability(
|
|
const std::string& device_id,
|
|
const std::string& app_id,
|
|
AppAvailabilityCallback callback) {
|
|
auto entry = socket_id_by_device_id_.find(device_id);
|
|
if (entry == socket_id_by_device_id_.end()) {
|
|
callback(app_id, AppAvailabilityResult::kUnknown);
|
|
return absl::nullopt;
|
|
}
|
|
int socket_id = entry->second;
|
|
|
|
int request_id = GetNextRequestId();
|
|
ErrorOr<::cast::channel::CastMessage> message =
|
|
CreateAppAvailabilityRequest(sender_id_, request_id, app_id);
|
|
OSP_DCHECK(message);
|
|
|
|
PendingRequests& pending_requests = pending_requests_by_device_id_[device_id];
|
|
auto timeout = std::make_unique<Alarm>(clock_, task_runner_);
|
|
timeout->ScheduleFromNow(
|
|
[this, request_id]() { CancelAppAvailabilityRequest(request_id); },
|
|
kRequestTimeout);
|
|
pending_requests.availability.push_back(AvailabilityRequest{
|
|
request_id, app_id, std::move(timeout), std::move(callback)});
|
|
|
|
VirtualConnection virtual_conn{sender_id_, kPlatformReceiverId, socket_id};
|
|
if (!virtual_conn_router_->GetConnectionData(virtual_conn)) {
|
|
virtual_conn_router_->AddConnection(virtual_conn,
|
|
VirtualConnection::AssociatedData{});
|
|
}
|
|
|
|
virtual_conn_router_->Send(std::move(virtual_conn),
|
|
std::move(message.value()));
|
|
|
|
return request_id;
|
|
}
|
|
|
|
void CastPlatformClient::AddOrUpdateReceiver(const ServiceInfo& device,
|
|
int socket_id) {
|
|
socket_id_by_device_id_[device.unique_id] = socket_id;
|
|
}
|
|
|
|
void CastPlatformClient::RemoveReceiver(const ServiceInfo& device) {
|
|
auto pending_requests_it =
|
|
pending_requests_by_device_id_.find(device.unique_id);
|
|
if (pending_requests_it != pending_requests_by_device_id_.end()) {
|
|
for (const AvailabilityRequest& availability :
|
|
pending_requests_it->second.availability) {
|
|
availability.callback(availability.app_id,
|
|
AppAvailabilityResult::kUnknown);
|
|
}
|
|
pending_requests_by_device_id_.erase(pending_requests_it);
|
|
}
|
|
socket_id_by_device_id_.erase(device.unique_id);
|
|
}
|
|
|
|
void CastPlatformClient::CancelRequest(int request_id) {
|
|
for (auto entry = pending_requests_by_device_id_.begin();
|
|
entry != pending_requests_by_device_id_.end(); ++entry) {
|
|
auto& pending_requests = entry->second;
|
|
auto it = std::find_if(pending_requests.availability.begin(),
|
|
pending_requests.availability.end(),
|
|
[request_id](const AvailabilityRequest& request) {
|
|
return request.request_id == request_id;
|
|
});
|
|
if (it != pending_requests.availability.end()) {
|
|
pending_requests.availability.erase(it);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CastPlatformClient::OnMessage(VirtualConnectionRouter* router,
|
|
CastSocket* socket,
|
|
::cast::channel::CastMessage message) {
|
|
if (message.payload_type() !=
|
|
::cast::channel::CastMessage_PayloadType_STRING ||
|
|
message.namespace_() != kReceiverNamespace ||
|
|
message.source_id() != kPlatformReceiverId) {
|
|
return;
|
|
}
|
|
ErrorOr<Json::Value> dict_or_error = json::Parse(message.payload_utf8());
|
|
if (dict_or_error.is_error()) {
|
|
OSP_DVLOG << "Failed to deserialize CastMessage payload.";
|
|
return;
|
|
}
|
|
|
|
Json::Value& dict = dict_or_error.value();
|
|
absl::optional<int> request_id =
|
|
MaybeGetInt(dict, JSON_EXPAND_FIND_CONSTANT_ARGS(kMessageKeyRequestId));
|
|
if (request_id) {
|
|
auto entry = std::find_if(
|
|
socket_id_by_device_id_.begin(), socket_id_by_device_id_.end(),
|
|
[socket_id =
|
|
ToCastSocketId(socket)](const std::pair<std::string, int>& entry) {
|
|
return entry.second == socket_id;
|
|
});
|
|
if (entry != socket_id_by_device_id_.end()) {
|
|
HandleResponse(entry->first, request_id.value(), dict);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CastPlatformClient::HandleResponse(const std::string& device_id,
|
|
int request_id,
|
|
const Json::Value& message) {
|
|
auto entry = pending_requests_by_device_id_.find(device_id);
|
|
if (entry == pending_requests_by_device_id_.end()) {
|
|
return;
|
|
}
|
|
PendingRequests& pending_requests = entry->second;
|
|
auto it = std::find_if(pending_requests.availability.begin(),
|
|
pending_requests.availability.end(),
|
|
[request_id](const AvailabilityRequest& request) {
|
|
return request.request_id == request_id;
|
|
});
|
|
if (it != pending_requests.availability.end()) {
|
|
// TODO(btolsch): Can all of this manual parsing/checking be cleaned up into
|
|
// a single parsing API along with other message handling?
|
|
const Json::Value* maybe_availability =
|
|
message.find(JSON_EXPAND_FIND_CONSTANT_ARGS(kMessageKeyAvailability));
|
|
if (maybe_availability && maybe_availability->isObject()) {
|
|
absl::optional<absl::string_view> result =
|
|
MaybeGetString(*maybe_availability, &it->app_id[0],
|
|
&it->app_id[0] + it->app_id.size());
|
|
if (result) {
|
|
AppAvailabilityResult availability_result =
|
|
AppAvailabilityResult::kUnknown;
|
|
if (result.value() == kMessageValueAppAvailable) {
|
|
availability_result = AppAvailabilityResult::kAvailable;
|
|
} else if (result.value() == kMessageValueAppUnavailable) {
|
|
availability_result = AppAvailabilityResult::kUnavailable;
|
|
} else {
|
|
OSP_DVLOG << "Invalid availability result: " << result.value();
|
|
}
|
|
it->callback(it->app_id, availability_result);
|
|
}
|
|
}
|
|
pending_requests.availability.erase(it);
|
|
}
|
|
}
|
|
|
|
void CastPlatformClient::CancelAppAvailabilityRequest(int request_id) {
|
|
for (auto& entry : pending_requests_by_device_id_) {
|
|
PendingRequests& pending_requests = entry.second;
|
|
auto it = std::find_if(pending_requests.availability.begin(),
|
|
pending_requests.availability.end(),
|
|
[request_id](const AvailabilityRequest& request) {
|
|
return request.request_id == request_id;
|
|
});
|
|
if (it != pending_requests.availability.end()) {
|
|
it->callback(it->app_id, AppAvailabilityResult::kUnknown);
|
|
pending_requests.availability.erase(it);
|
|
}
|
|
}
|
|
}
|
|
|
|
// static
|
|
int CastPlatformClient::GetNextRequestId() {
|
|
return next_request_id_++;
|
|
}
|
|
|
|
// static
|
|
int CastPlatformClient::next_request_id_ = 0;
|
|
|
|
} // namespace cast
|
|
} // namespace openscreen
|