1// Copyright (c) 2012 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 "ppapi/proxy/ppb_broker_proxy.h"
6
7#include "base/bind.h"
8#include "ppapi/c/pp_errors.h"
9#include "ppapi/c/trusted/ppb_broker_trusted.h"
10#include "ppapi/proxy/enter_proxy.h"
11#include "ppapi/proxy/plugin_dispatcher.h"
12#include "ppapi/proxy/ppapi_messages.h"
13#include "ppapi/shared_impl/platform_file.h"
14#include "ppapi/shared_impl/tracked_callback.h"
15#include "ppapi/thunk/ppb_broker_api.h"
16#include "ppapi/thunk/enter.h"
17#include "ppapi/thunk/resource_creation_api.h"
18#include "ppapi/thunk/thunk.h"
19
20using ppapi::IntToPlatformFile;
21using ppapi::PlatformFileToInt;
22using ppapi::thunk::PPB_Broker_API;
23
24namespace ppapi {
25namespace proxy {
26
27class Broker : public PPB_Broker_API, public Resource {
28 public:
29  explicit Broker(const HostResource& resource);
30  virtual ~Broker();
31
32  // Resource overrides.
33  virtual PPB_Broker_API* AsPPB_Broker_API() OVERRIDE;
34
35  // PPB_Broker_API implementation.
36  virtual int32_t Connect(
37      scoped_refptr<TrackedCallback> connect_callback) OVERRIDE;
38  virtual int32_t GetHandle(int32_t* handle) OVERRIDE;
39
40  // Called by the proxy when the host side has completed the request.
41  void ConnectComplete(IPC::PlatformFileForTransit socket_handle,
42                       int32_t result);
43
44 private:
45  bool called_connect_;
46  scoped_refptr<TrackedCallback> current_connect_callback_;
47
48  // The plugin module owns the handle.
49  // The host side transfers ownership of the handle to the plugin side when it
50  // sends the IPC. This member holds the handle value for the plugin module
51  // to read, but the plugin side of the proxy never takes ownership.
52  base::SyncSocket::Handle socket_handle_;
53
54  DISALLOW_COPY_AND_ASSIGN(Broker);
55};
56
57Broker::Broker(const HostResource& resource)
58    : Resource(OBJECT_IS_PROXY, resource),
59      called_connect_(false),
60      socket_handle_(base::SyncSocket::kInvalidHandle) {
61}
62
63Broker::~Broker() {
64  socket_handle_ = base::SyncSocket::kInvalidHandle;
65}
66
67PPB_Broker_API* Broker::AsPPB_Broker_API() {
68  return this;
69}
70
71int32_t Broker::Connect(scoped_refptr<TrackedCallback> connect_callback) {
72  if (TrackedCallback::IsPending(current_connect_callback_))
73    return PP_ERROR_INPROGRESS;
74  else if (called_connect_)
75    return PP_ERROR_FAILED;
76
77  current_connect_callback_ = connect_callback;
78  called_connect_ = true;
79
80  bool success = PluginDispatcher::GetForResource(this)->Send(
81      new PpapiHostMsg_PPBBroker_Connect(
82          API_ID_PPB_BROKER, host_resource()));
83  return success ?  PP_OK_COMPLETIONPENDING : PP_ERROR_FAILED;
84}
85
86int32_t Broker::GetHandle(int32_t* handle) {
87  if (socket_handle_ == base::SyncSocket::kInvalidHandle)
88    return PP_ERROR_FAILED;
89  *handle = PlatformFileToInt(socket_handle_);
90  return PP_OK;
91}
92
93void Broker::ConnectComplete(IPC::PlatformFileForTransit socket_handle,
94                             int32_t result) {
95  if (result == PP_OK) {
96    DCHECK(socket_handle_ == base::SyncSocket::kInvalidHandle);
97    socket_handle_ = IPC::PlatformFileForTransitToPlatformFile(socket_handle);
98  } else {
99    // The caller may still have given us a handle in the failure case.
100    // The easiest way to clean it up is to just put it in an object
101    // and then close them. This failure case is not performance critical.
102    base::SyncSocket temp_socket(
103        IPC::PlatformFileForTransitToPlatformFile(socket_handle));
104  }
105
106  if (!TrackedCallback::IsPending(current_connect_callback_)) {
107    // The handle might leak if the plugin never calls GetHandle().
108    return;
109  }
110
111  current_connect_callback_->Run(result);
112}
113
114PPB_Broker_Proxy::PPB_Broker_Proxy(Dispatcher* dispatcher)
115    : InterfaceProxy(dispatcher),
116      callback_factory_(this){
117}
118
119PPB_Broker_Proxy::~PPB_Broker_Proxy() {
120}
121
122// static
123PP_Resource PPB_Broker_Proxy::CreateProxyResource(PP_Instance instance) {
124  PluginDispatcher* dispatcher = PluginDispatcher::GetForInstance(instance);
125  if (!dispatcher)
126    return 0;
127
128  HostResource result;
129  dispatcher->Send(new PpapiHostMsg_PPBBroker_Create(
130      API_ID_PPB_BROKER, instance, &result));
131  if (result.is_null())
132    return 0;
133  return (new Broker(result))->GetReference();
134}
135
136bool PPB_Broker_Proxy::OnMessageReceived(const IPC::Message& msg) {
137  bool handled = true;
138  IPC_BEGIN_MESSAGE_MAP(PPB_Broker_Proxy, msg)
139    IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBBroker_Create, OnMsgCreate)
140    IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBBroker_Connect, OnMsgConnect)
141    IPC_MESSAGE_HANDLER(PpapiMsg_PPBBroker_ConnectComplete,
142                        OnMsgConnectComplete)
143    IPC_MESSAGE_UNHANDLED(handled = false)
144  IPC_END_MESSAGE_MAP()
145  return handled;
146}
147
148void PPB_Broker_Proxy::OnMsgCreate(PP_Instance instance,
149                                   HostResource* result_resource) {
150  if (!dispatcher()->permissions().HasPermission(PERMISSION_PRIVATE))
151    return;
152  thunk::EnterResourceCreation enter(instance);
153  if (enter.succeeded()) {
154    result_resource->SetHostResource(
155        instance,
156        enter.functions()->CreateBroker(instance));
157  }
158}
159
160void PPB_Broker_Proxy::OnMsgConnect(const HostResource& broker) {
161  if (!dispatcher()->permissions().HasPermission(PERMISSION_PRIVATE))
162    return;
163  EnterHostFromHostResourceForceCallback<PPB_Broker_API> enter(
164      broker, callback_factory_,
165      &PPB_Broker_Proxy::ConnectCompleteInHost, broker);
166  if (enter.succeeded())
167    enter.SetResult(enter.object()->Connect(enter.callback()));
168}
169
170// Called in the plugin to handle the connect callback.
171// The proxy owns the handle and transfers it to the Broker. At that point,
172// the plugin owns the handle and is responsible for closing it.
173// The caller guarantees that socket_handle is not valid if result is not PP_OK.
174void PPB_Broker_Proxy::OnMsgConnectComplete(
175    const HostResource& resource,
176    IPC::PlatformFileForTransit socket_handle,
177    int32_t result) {
178  DCHECK(result == PP_OK ||
179         socket_handle == IPC::InvalidPlatformFileForTransit());
180
181  EnterPluginFromHostResource<PPB_Broker_API> enter(resource);
182  if (enter.failed()) {
183    // As in Broker::ConnectComplete, we need to close the resource on error.
184    base::SyncSocket temp_socket(
185        IPC::PlatformFileForTransitToPlatformFile(socket_handle));
186  } else {
187    static_cast<Broker*>(enter.object())->ConnectComplete(socket_handle,
188                                                          result);
189  }
190}
191
192// Callback on the host side.
193// Transfers ownership of the handle to the plugin side. This function must
194// either successfully call the callback or close the handle.
195void PPB_Broker_Proxy::ConnectCompleteInHost(int32_t result,
196                                             const HostResource& broker) {
197  IPC::PlatformFileForTransit foreign_socket_handle =
198      IPC::InvalidPlatformFileForTransit();
199  if (result == PP_OK) {
200    int32_t socket_handle = PlatformFileToInt(base::SyncSocket::kInvalidHandle);
201    EnterHostFromHostResource<PPB_Broker_API> enter(broker);
202    if (enter.succeeded())
203      result = enter.object()->GetHandle(&socket_handle);
204    DCHECK(result == PP_OK ||
205           socket_handle ==
206               PlatformFileToInt(base::SyncSocket::kInvalidHandle));
207
208    if (result == PP_OK) {
209      foreign_socket_handle =
210          dispatcher()->ShareHandleWithRemote(IntToPlatformFile(socket_handle),
211                                              true);
212      if (foreign_socket_handle == IPC::InvalidPlatformFileForTransit()) {
213        result = PP_ERROR_FAILED;
214        // Assume the local handle was closed even if the foreign handle could
215        // not be created.
216      }
217    }
218  }
219  DCHECK(result == PP_OK ||
220         foreign_socket_handle == IPC::InvalidPlatformFileForTransit());
221
222  bool success = dispatcher()->Send(new PpapiMsg_PPBBroker_ConnectComplete(
223      API_ID_PPB_BROKER, broker, foreign_socket_handle, result));
224
225  if (!success || result != PP_OK) {
226      // The plugin did not receive the handle, so it must be closed.
227      // The easiest way to clean it up is to just put it in an object
228      // and then close it. This failure case is not performance critical.
229      // The handle could still leak if Send succeeded but the IPC later failed.
230      base::SyncSocket temp_socket(
231          IPC::PlatformFileForTransitToPlatformFile(foreign_socket_handle));
232  }
233}
234
235}  // namespace proxy
236}  // namespace ppapi
237