1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "cast/sender/cast_platform_client.h"
6 
7 #include <memory>
8 #include <random>
9 #include <utility>
10 
11 #include "absl/strings/str_cat.h"
12 #include "cast/common/channel/virtual_connection_manager.h"
13 #include "cast/common/channel/virtual_connection_router.h"
14 #include "cast/common/public/cast_socket.h"
15 #include "cast/common/public/service_info.h"
16 #include "util/json/json_serialization.h"
17 #include "util/osp_logging.h"
18 #include "util/stringprintf.h"
19 
20 namespace openscreen {
21 namespace cast {
22 
23 static constexpr std::chrono::seconds kRequestTimeout = std::chrono::seconds(5);
24 
CastPlatformClient(VirtualConnectionRouter * router,VirtualConnectionManager * manager,ClockNowFunctionPtr clock,TaskRunner * task_runner)25 CastPlatformClient::CastPlatformClient(VirtualConnectionRouter* router,
26                                        VirtualConnectionManager* manager,
27                                        ClockNowFunctionPtr clock,
28                                        TaskRunner* task_runner)
29     : sender_id_(MakeUniqueSessionId("sender")),
30       virtual_conn_router_(router),
31       virtual_conn_manager_(manager),
32       clock_(clock),
33       task_runner_(task_runner) {
34   OSP_DCHECK(virtual_conn_manager_);
35   OSP_DCHECK(clock_);
36   OSP_DCHECK(task_runner_);
37   virtual_conn_router_->AddHandlerForLocalId(sender_id_, this);
38 }
39 
~CastPlatformClient()40 CastPlatformClient::~CastPlatformClient() {
41   virtual_conn_router_->RemoveHandlerForLocalId(sender_id_);
42 
43   for (auto& pending_requests : pending_requests_by_device_id_) {
44     for (auto& avail_request : pending_requests.second.availability) {
45       avail_request.callback(avail_request.app_id,
46                              AppAvailabilityResult::kUnknown);
47     }
48   }
49 }
50 
RequestAppAvailability(const std::string & device_id,const std::string & app_id,AppAvailabilityCallback callback)51 absl::optional<int> CastPlatformClient::RequestAppAvailability(
52     const std::string& device_id,
53     const std::string& app_id,
54     AppAvailabilityCallback callback) {
55   auto entry = socket_id_by_device_id_.find(device_id);
56   if (entry == socket_id_by_device_id_.end()) {
57     callback(app_id, AppAvailabilityResult::kUnknown);
58     return absl::nullopt;
59   }
60   int socket_id = entry->second;
61 
62   int request_id = GetNextRequestId();
63   ErrorOr<::cast::channel::CastMessage> message =
64       CreateAppAvailabilityRequest(sender_id_, request_id, app_id);
65   OSP_DCHECK(message);
66 
67   PendingRequests& pending_requests = pending_requests_by_device_id_[device_id];
68   auto timeout = std::make_unique<Alarm>(clock_, task_runner_);
69   timeout->ScheduleFromNow(
70       [this, request_id]() { CancelAppAvailabilityRequest(request_id); },
71       kRequestTimeout);
72   pending_requests.availability.push_back(AvailabilityRequest{
73       request_id, app_id, std::move(timeout), std::move(callback)});
74 
75   VirtualConnection virtual_conn{sender_id_, kPlatformReceiverId, socket_id};
76   if (!virtual_conn_manager_->GetConnectionData(virtual_conn)) {
77     virtual_conn_manager_->AddConnection(virtual_conn,
78                                          VirtualConnection::AssociatedData{});
79   }
80 
81   virtual_conn_router_->Send(std::move(virtual_conn),
82                              std::move(message.value()));
83 
84   return request_id;
85 }
86 
AddOrUpdateReceiver(const ServiceInfo & device,int socket_id)87 void CastPlatformClient::AddOrUpdateReceiver(const ServiceInfo& device,
88                                              int socket_id) {
89   socket_id_by_device_id_[device.unique_id] = socket_id;
90 }
91 
RemoveReceiver(const ServiceInfo & device)92 void CastPlatformClient::RemoveReceiver(const ServiceInfo& device) {
93   auto pending_requests_it =
94       pending_requests_by_device_id_.find(device.unique_id);
95   if (pending_requests_it != pending_requests_by_device_id_.end()) {
96     for (const AvailabilityRequest& availability :
97          pending_requests_it->second.availability) {
98       availability.callback(availability.app_id,
99                             AppAvailabilityResult::kUnknown);
100     }
101     pending_requests_by_device_id_.erase(pending_requests_it);
102   }
103   socket_id_by_device_id_.erase(device.unique_id);
104 }
105 
CancelRequest(int request_id)106 void CastPlatformClient::CancelRequest(int request_id) {
107   for (auto entry = pending_requests_by_device_id_.begin();
108        entry != pending_requests_by_device_id_.end(); ++entry) {
109     auto& pending_requests = entry->second;
110     auto it = std::find_if(pending_requests.availability.begin(),
111                            pending_requests.availability.end(),
112                            [request_id](const AvailabilityRequest& request) {
113                              return request.request_id == request_id;
114                            });
115     if (it != pending_requests.availability.end()) {
116       pending_requests.availability.erase(it);
117       break;
118     }
119   }
120 }
121 
OnMessage(VirtualConnectionRouter * router,CastSocket * socket,::cast::channel::CastMessage message)122 void CastPlatformClient::OnMessage(VirtualConnectionRouter* router,
123                                    CastSocket* socket,
124                                    ::cast::channel::CastMessage message) {
125   if (message.payload_type() !=
126           ::cast::channel::CastMessage_PayloadType_STRING ||
127       message.namespace_() != kReceiverNamespace ||
128       message.source_id() != kPlatformReceiverId) {
129     return;
130   }
131   ErrorOr<Json::Value> dict_or_error = json::Parse(message.payload_utf8());
132   if (dict_or_error.is_error()) {
133     OSP_DVLOG << "Failed to deserialize CastMessage payload.";
134     return;
135   }
136 
137   Json::Value& dict = dict_or_error.value();
138   absl::optional<int> request_id =
139       MaybeGetInt(dict, JSON_EXPAND_FIND_CONSTANT_ARGS(kMessageKeyRequestId));
140   if (request_id) {
141     auto entry = std::find_if(
142         socket_id_by_device_id_.begin(), socket_id_by_device_id_.end(),
143         [socket_id =
144              ToCastSocketId(socket)](const std::pair<std::string, int>& entry) {
145           return entry.second == socket_id;
146         });
147     if (entry != socket_id_by_device_id_.end()) {
148       HandleResponse(entry->first, request_id.value(), dict);
149     }
150   }
151 }
152 
HandleResponse(const std::string & device_id,int request_id,const Json::Value & message)153 void CastPlatformClient::HandleResponse(const std::string& device_id,
154                                         int request_id,
155                                         const Json::Value& message) {
156   auto entry = pending_requests_by_device_id_.find(device_id);
157   if (entry == pending_requests_by_device_id_.end()) {
158     return;
159   }
160   PendingRequests& pending_requests = entry->second;
161   auto it = std::find_if(pending_requests.availability.begin(),
162                          pending_requests.availability.end(),
163                          [request_id](const AvailabilityRequest& request) {
164                            return request.request_id == request_id;
165                          });
166   if (it != pending_requests.availability.end()) {
167     // TODO(btolsch): Can all of this manual parsing/checking be cleaned up into
168     // a single parsing API along with other message handling?
169     const Json::Value* maybe_availability =
170         message.find(JSON_EXPAND_FIND_CONSTANT_ARGS(kMessageKeyAvailability));
171     if (maybe_availability && maybe_availability->isObject()) {
172       absl::optional<absl::string_view> result =
173           MaybeGetString(*maybe_availability, &it->app_id[0],
174                          &it->app_id[0] + it->app_id.size());
175       if (result) {
176         AppAvailabilityResult availability_result =
177             AppAvailabilityResult::kUnknown;
178         if (result.value() == kMessageValueAppAvailable) {
179           availability_result = AppAvailabilityResult::kAvailable;
180         } else if (result.value() == kMessageValueAppUnavailable) {
181           availability_result = AppAvailabilityResult::kUnavailable;
182         } else {
183           OSP_DVLOG << "Invalid availability result: " << result.value();
184         }
185         it->callback(it->app_id, availability_result);
186       }
187     }
188     pending_requests.availability.erase(it);
189   }
190 }
191 
CancelAppAvailabilityRequest(int request_id)192 void CastPlatformClient::CancelAppAvailabilityRequest(int request_id) {
193   for (auto& entry : pending_requests_by_device_id_) {
194     PendingRequests& pending_requests = entry.second;
195     auto it = std::find_if(pending_requests.availability.begin(),
196                            pending_requests.availability.end(),
197                            [request_id](const AvailabilityRequest& request) {
198                              return request.request_id == request_id;
199                            });
200     if (it != pending_requests.availability.end()) {
201       it->callback(it->app_id, AppAvailabilityResult::kUnknown);
202       pending_requests.availability.erase(it);
203     }
204   }
205 }
206 
207 // static
GetNextRequestId()208 int CastPlatformClient::GetNextRequestId() {
209   return next_request_id_++;
210 }
211 
212 // static
213 int CastPlatformClient::next_request_id_ = 0;
214 
215 }  // namespace cast
216 }  // namespace openscreen
217