// Copyright 2013 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 "ppapi/proxy/udp_socket_resource_base.h" #include #include "base/logging.h" #include "ppapi/c/pp_bool.h" #include "ppapi/c/pp_errors.h" #include "ppapi/proxy/error_conversion.h" #include "ppapi/proxy/plugin_globals.h" #include "ppapi/proxy/ppapi_messages.h" #include "ppapi/shared_impl/socket_option_data.h" #include "ppapi/thunk/enter.h" namespace ppapi { namespace proxy { const int32_t UDPSocketResourceBase::kMaxWriteSize = 128 * 1024; const int32_t UDPSocketResourceBase::kMaxSendBufferSize = 1024 * UDPSocketResourceBase::kMaxWriteSize; const size_t UDPSocketResourceBase::kPluginSendBufferSlots = 8u; namespace { void RunCallback(scoped_refptr callback, int32_t pp_result, bool private_api) { callback->Run(ConvertNetworkAPIErrorForCompatibility(pp_result, private_api)); } void PostAbortIfNecessary(const scoped_refptr& callback) { if (TrackedCallback::IsPending(callback)) callback->PostAbort(); } } // namespace UDPSocketResourceBase::UDPSocketResourceBase(Connection connection, PP_Instance instance, bool private_api) : PluginResource(connection, instance), private_api_(private_api), bind_called_(false), bound_(false), closed_(false), recv_filter_(PluginGlobals::Get()->udp_socket_filter()), bound_addr_() { recv_filter_->AddUDPResource( pp_instance(), pp_resource(), private_api, base::Bind(&UDPSocketResourceBase::SlotBecameAvailable, pp_resource())); if (private_api) SendCreate(BROWSER, PpapiHostMsg_UDPSocket_CreatePrivate()); else SendCreate(BROWSER, PpapiHostMsg_UDPSocket_Create()); } UDPSocketResourceBase::~UDPSocketResourceBase() { CloseImpl(); } int32_t UDPSocketResourceBase::SetOptionImpl( PP_UDPSocket_Option name, const PP_Var& value, bool check_bind_state, scoped_refptr callback) { if (closed_) return PP_ERROR_FAILED; // Check if socket is expected to be bound or not according to the option. switch (name) { case PP_UDPSOCKET_OPTION_ADDRESS_REUSE: case PP_UDPSOCKET_OPTION_BROADCAST: case PP_UDPSOCKET_OPTION_MULTICAST_LOOP: case PP_UDPSOCKET_OPTION_MULTICAST_TTL: { if ((check_bind_state || name == PP_UDPSOCKET_OPTION_ADDRESS_REUSE) && bind_called_) { // SetOption should fail in this case in order to give predictable // behavior while binding. Note that we use |bind_called_| rather // than |bound_| since the latter is only set on successful completion // of Bind(). return PP_ERROR_FAILED; } break; } case PP_UDPSOCKET_OPTION_SEND_BUFFER_SIZE: case PP_UDPSOCKET_OPTION_RECV_BUFFER_SIZE: { if (check_bind_state && !bound_) return PP_ERROR_FAILED; break; } } SocketOptionData option_data; switch (name) { case PP_UDPSOCKET_OPTION_ADDRESS_REUSE: case PP_UDPSOCKET_OPTION_BROADCAST: case PP_UDPSOCKET_OPTION_MULTICAST_LOOP: { if (value.type != PP_VARTYPE_BOOL) return PP_ERROR_BADARGUMENT; option_data.SetBool(PP_ToBool(value.value.as_bool)); break; } case PP_UDPSOCKET_OPTION_SEND_BUFFER_SIZE: case PP_UDPSOCKET_OPTION_RECV_BUFFER_SIZE: { if (value.type != PP_VARTYPE_INT32) return PP_ERROR_BADARGUMENT; option_data.SetInt32(value.value.as_int); break; } case PP_UDPSOCKET_OPTION_MULTICAST_TTL: { int32_t ival = value.value.as_int; if (value.type != PP_VARTYPE_INT32 && (ival < 0 || ival > 255)) return PP_ERROR_BADARGUMENT; option_data.SetInt32(ival); break; } default: { NOTREACHED(); return PP_ERROR_BADARGUMENT; } } Call( BROWSER, PpapiHostMsg_UDPSocket_SetOption(name, option_data), base::Bind(&UDPSocketResourceBase::OnPluginMsgGeneralReply, base::Unretained(this), callback), callback); return PP_OK_COMPLETIONPENDING; } int32_t UDPSocketResourceBase::BindImpl( const PP_NetAddress_Private* addr, scoped_refptr callback) { if (!addr) return PP_ERROR_BADARGUMENT; if (bound_ || closed_) return PP_ERROR_FAILED; if (TrackedCallback::IsPending(bind_callback_)) return PP_ERROR_INPROGRESS; bind_called_ = true; bind_callback_ = callback; // Send the request, the browser will call us back via BindReply. Call( BROWSER, PpapiHostMsg_UDPSocket_Bind(*addr), base::Bind(&UDPSocketResourceBase::OnPluginMsgBindReply, base::Unretained(this)), callback); return PP_OK_COMPLETIONPENDING; } PP_Bool UDPSocketResourceBase::GetBoundAddressImpl( PP_NetAddress_Private* addr) { if (!addr || !bound_ || closed_) return PP_FALSE; *addr = bound_addr_; return PP_TRUE; } int32_t UDPSocketResourceBase::RecvFromImpl( char* buffer_out, int32_t num_bytes, PP_Resource* addr, scoped_refptr callback) { if (!bound_) return PP_ERROR_FAILED; return recv_filter_->RequestData(pp_resource(), num_bytes, buffer_out, addr, callback); } PP_Bool UDPSocketResourceBase::GetRecvFromAddressImpl( PP_NetAddress_Private* addr) { if (!addr) return PP_FALSE; *addr = recv_filter_->GetLastAddrPrivate(pp_resource()); return PP_TRUE; } int32_t UDPSocketResourceBase::SendToImpl( const char* buffer, int32_t num_bytes, const PP_NetAddress_Private* addr, scoped_refptr callback) { if (!buffer || num_bytes <= 0 || !addr) return PP_ERROR_BADARGUMENT; if (!bound_) return PP_ERROR_FAILED; if (sendto_callbacks_.size() == kPluginSendBufferSlots) return PP_ERROR_INPROGRESS; if (num_bytes > kMaxWriteSize) num_bytes = kMaxWriteSize; sendto_callbacks_.push(callback); // Send the request, the browser will call us back via SendToReply. Call( BROWSER, PpapiHostMsg_UDPSocket_SendTo(std::string(buffer, num_bytes), *addr), base::Bind(&UDPSocketResourceBase::OnPluginMsgSendToReply, base::Unretained(this)), callback); return PP_OK_COMPLETIONPENDING; } void UDPSocketResourceBase::CloseImpl() { if(closed_) return; bound_ = false; closed_ = true; Post(BROWSER, PpapiHostMsg_UDPSocket_Close()); PostAbortIfNecessary(bind_callback_); while (!sendto_callbacks_.empty()) { scoped_refptr callback = sendto_callbacks_.front(); sendto_callbacks_.pop(); PostAbortIfNecessary(callback); } recv_filter_->RemoveUDPResource(pp_resource()); } int32_t UDPSocketResourceBase::JoinGroupImpl( const PP_NetAddress_Private *group, scoped_refptr callback) { DCHECK(group); Call( BROWSER, PpapiHostMsg_UDPSocket_JoinGroup(*group), base::Bind(&UDPSocketResourceBase::OnPluginMsgGeneralReply, base::Unretained(this), callback), callback); return PP_OK_COMPLETIONPENDING; } int32_t UDPSocketResourceBase::LeaveGroupImpl( const PP_NetAddress_Private *group, scoped_refptr callback) { DCHECK(group); Call( BROWSER, PpapiHostMsg_UDPSocket_LeaveGroup(*group), base::Bind(&UDPSocketResourceBase::OnPluginMsgGeneralReply, base::Unretained(this), callback), callback); return PP_OK_COMPLETIONPENDING; } void UDPSocketResourceBase::OnPluginMsgGeneralReply( scoped_refptr callback, const ResourceMessageReplyParams& params) { if (TrackedCallback::IsPending(callback)) RunCallback(callback, params.result(), private_api_); } void UDPSocketResourceBase::OnPluginMsgBindReply( const ResourceMessageReplyParams& params, const PP_NetAddress_Private& bound_addr) { // It is possible that |bind_callback_| is pending while |closed_| is true: // CloseImpl() has been called, but a BindReply came earlier than the task to // abort |bind_callback_|. We don't want to update |bound_| or |bound_addr_| // in that case. if (!TrackedCallback::IsPending(bind_callback_) || closed_) return; if (params.result() == PP_OK) bound_ = true; bound_addr_ = bound_addr; RunCallback(bind_callback_, params.result(), private_api_); } void UDPSocketResourceBase::OnPluginMsgSendToReply( const ResourceMessageReplyParams& params, int32_t bytes_written) { // This can be empty if the socket was closed, but there are still tasks // to be posted for this resource. if (sendto_callbacks_.empty()) return; scoped_refptr callback = sendto_callbacks_.front(); sendto_callbacks_.pop(); if (!TrackedCallback::IsPending(callback)) return; if (params.result() == PP_OK) RunCallback(callback, bytes_written, private_api_); else RunCallback(callback, params.result(), private_api_); } // static void UDPSocketResourceBase::SlotBecameAvailable(PP_Resource resource) { ProxyLock::AssertAcquired(); UDPSocketResourceBase* thiz = nullptr; // We have to try to enter all subclasses of UDPSocketResourceBase. Currently, // these are the public and private resources. thunk::EnterResourceNoLock enter(resource, false); if (enter.succeeded()) { thiz = static_cast(enter.resource()); } else { thunk::EnterResourceNoLock enter_private( resource, false); if (enter_private.succeeded()) thiz = static_cast(enter_private.resource()); } if (thiz && !thiz->closed_) thiz->Post(BROWSER, PpapiHostMsg_UDPSocket_RecvSlotAvailable()); } } // namespace proxy } // namespace ppapi