/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "src/ipc/host_impl.h" #include <inttypes.h> #include <algorithm> #include <utility> #include "perfetto/base/task_runner.h" #include "perfetto/base/utils.h" #include "perfetto/ipc/service.h" #include "perfetto/ipc/service_descriptor.h" #include "src/ipc/wire_protocol.pb.h" // TODO(primiano): put limits on #connections/uid and req. queue (b/69093705). namespace perfetto { namespace ipc { // static std::unique_ptr<Host> Host::CreateInstance(const char* socket_name, base::TaskRunner* task_runner) { std::unique_ptr<HostImpl> host(new HostImpl(socket_name, task_runner)); if (!host->sock() || !host->sock()->is_listening()) return nullptr; return std::move(host); } // static std::unique_ptr<Host> Host::CreateInstance(base::ScopedFile socket_fd, base::TaskRunner* task_runner) { std::unique_ptr<HostImpl> host( new HostImpl(std::move(socket_fd), task_runner)); if (!host->sock() || !host->sock()->is_listening()) return nullptr; return std::move(host); } HostImpl::HostImpl(base::ScopedFile socket_fd, base::TaskRunner* task_runner) : task_runner_(task_runner), weak_ptr_factory_(this) { GOOGLE_PROTOBUF_VERIFY_VERSION; PERFETTO_DCHECK_THREAD(thread_checker_); sock_ = base::UnixSocket::Listen(std::move(socket_fd), this, task_runner_); } HostImpl::HostImpl(const char* socket_name, base::TaskRunner* task_runner) : task_runner_(task_runner), weak_ptr_factory_(this) { GOOGLE_PROTOBUF_VERIFY_VERSION; PERFETTO_DCHECK_THREAD(thread_checker_); sock_ = base::UnixSocket::Listen(socket_name, this, task_runner_); } HostImpl::~HostImpl() = default; bool HostImpl::ExposeService(std::unique_ptr<Service> service) { PERFETTO_DCHECK_THREAD(thread_checker_); const std::string& service_name = service->GetDescriptor().service_name; if (GetServiceByName(service_name)) { PERFETTO_DLOG("Duplicate ExposeService(): %s", service_name.c_str()); return false; } ServiceID sid = ++last_service_id_; ExposedService exposed_service(sid, service_name, std::move(service)); services_.emplace(sid, std::move(exposed_service)); return true; } void HostImpl::OnNewIncomingConnection( base::UnixSocket*, std::unique_ptr<base::UnixSocket> new_conn) { PERFETTO_DCHECK_THREAD(thread_checker_); std::unique_ptr<ClientConnection> client(new ClientConnection()); ClientID client_id = ++last_client_id_; clients_by_socket_[new_conn.get()] = client.get(); client->id = client_id; client->sock = std::move(new_conn); clients_[client_id] = std::move(client); } void HostImpl::OnDataAvailable(base::UnixSocket* sock) { PERFETTO_DCHECK_THREAD(thread_checker_); auto it = clients_by_socket_.find(sock); if (it == clients_by_socket_.end()) return; ClientConnection* client = it->second; BufferedFrameDeserializer& frame_deserializer = client->frame_deserializer; size_t rsize; do { auto buf = frame_deserializer.BeginReceive(); base::ScopedFile fd; rsize = client->sock->Receive(buf.data, buf.size, &fd); if (fd) { PERFETTO_DCHECK(!client->received_fd); client->received_fd = std::move(fd); } if (!frame_deserializer.EndReceive(rsize)) return OnDisconnect(client->sock.get()); } while (rsize > 0); for (;;) { std::unique_ptr<Frame> frame = frame_deserializer.PopNextFrame(); if (!frame) break; OnReceivedFrame(client, *frame); } } void HostImpl::OnReceivedFrame(ClientConnection* client, const Frame& req_frame) { if (req_frame.msg_case() == Frame::kMsgBindService) return OnBindService(client, req_frame); if (req_frame.msg_case() == Frame::kMsgInvokeMethod) return OnInvokeMethod(client, req_frame); PERFETTO_DLOG("Received invalid RPC frame %u from client %" PRIu64, req_frame.msg_case(), client->id); Frame reply_frame; reply_frame.set_request_id(req_frame.request_id()); reply_frame.mutable_msg_request_error()->set_error("unknown request"); SendFrame(client, reply_frame); } void HostImpl::OnBindService(ClientConnection* client, const Frame& req_frame) { // Binding a service doesn't do anything major. It just returns back the // service id and its method map. const Frame::BindService& req = req_frame.msg_bind_service(); Frame reply_frame; reply_frame.set_request_id(req_frame.request_id()); auto* reply = reply_frame.mutable_msg_bind_service_reply(); const ExposedService* service = GetServiceByName(req.service_name()); if (service) { reply->set_success(true); reply->set_service_id(service->id); uint32_t method_id = 1; // method ids start at index 1. for (const auto& desc_method : service->instance->GetDescriptor().methods) { Frame::BindServiceReply::MethodInfo* method_info = reply->add_methods(); method_info->set_name(desc_method.name); method_info->set_id(method_id++); } } SendFrame(client, reply_frame); } void HostImpl::OnInvokeMethod(ClientConnection* client, const Frame& req_frame) { const Frame::InvokeMethod& req = req_frame.msg_invoke_method(); Frame reply_frame; RequestID request_id = req_frame.request_id(); reply_frame.set_request_id(request_id); reply_frame.mutable_msg_invoke_method_reply()->set_success(false); auto svc_it = services_.find(req.service_id()); if (svc_it == services_.end()) return SendFrame(client, reply_frame); // |success| == false by default. Service* service = svc_it->second.instance.get(); const ServiceDescriptor& svc = service->GetDescriptor(); const auto& methods = svc.methods; const uint32_t method_id = req.method_id(); if (method_id == 0 || method_id > methods.size()) return SendFrame(client, reply_frame); const ServiceDescriptor::Method& method = methods[method_id - 1]; std::unique_ptr<ProtoMessage> decoded_req_args( method.request_proto_decoder(req.args_proto())); if (!decoded_req_args) return SendFrame(client, reply_frame); Deferred<ProtoMessage> deferred_reply; base::WeakPtr<HostImpl> host_weak_ptr = weak_ptr_factory_.GetWeakPtr(); ClientID client_id = client->id; if (!req.drop_reply()) { deferred_reply.Bind([host_weak_ptr, client_id, request_id](AsyncResult<ProtoMessage> reply) { if (!host_weak_ptr) return; // The reply came too late, the HostImpl has gone. host_weak_ptr->ReplyToMethodInvocation(client_id, request_id, std::move(reply)); }); } service->client_info_ = ClientInfo(client->id, client->sock->peer_uid()); service->received_fd_ = &client->received_fd; method.invoker(service, *decoded_req_args, std::move(deferred_reply)); service->received_fd_ = nullptr; service->client_info_ = ClientInfo(); } void HostImpl::ReplyToMethodInvocation(ClientID client_id, RequestID request_id, AsyncResult<ProtoMessage> reply) { auto client_iter = clients_.find(client_id); if (client_iter == clients_.end()) return; // client has disconnected by the time we got the async reply. ClientConnection* client = client_iter->second.get(); Frame reply_frame; reply_frame.set_request_id(request_id); // TODO(fmayer): add a test to guarantee that the reply is consumed within the // same call stack and not kept around. ConsumerIPCService::OnTraceData() // relies on this behavior. auto* reply_frame_data = reply_frame.mutable_msg_invoke_method_reply(); reply_frame_data->set_has_more(reply.has_more()); if (reply.success()) { std::string reply_proto; if (reply->SerializeToString(&reply_proto)) { reply_frame_data->set_reply_proto(reply_proto); reply_frame_data->set_success(true); } } SendFrame(client, reply_frame, reply.fd()); } // static void HostImpl::SendFrame(ClientConnection* client, const Frame& frame, int fd) { std::string buf = BufferedFrameDeserializer::Serialize(frame); // TODO(primiano): this should do non-blocking I/O. But then what if the // socket buffer is full? We might want to either drop the request or throttle // the send and PostTask the reply later? Right now we are making Send() // blocking as a workaround. Propagate bakpressure to the caller instead. bool res = client->sock->Send(buf.data(), buf.size(), fd, base::UnixSocket::BlockingMode::kBlocking); PERFETTO_CHECK(res || !client->sock->is_connected()); } void HostImpl::OnDisconnect(base::UnixSocket* sock) { PERFETTO_DCHECK_THREAD(thread_checker_); auto it = clients_by_socket_.find(sock); if (it == clients_by_socket_.end()) return; ClientID client_id = it->second->id; ClientInfo client_info(client_id, sock->peer_uid()); clients_by_socket_.erase(it); PERFETTO_DCHECK(clients_.count(client_id)); clients_.erase(client_id); for (const auto& service_it : services_) { Service& service = *service_it.second.instance; service.client_info_ = client_info; service.OnClientDisconnected(); service.client_info_ = ClientInfo(); } } const HostImpl::ExposedService* HostImpl::GetServiceByName( const std::string& name) { // This could be optimized by using another map<name,ServiceID>. However this // is used only by Bind/ExposeService that are quite rare (once per client // connection and once per service instance), not worth it. for (const auto& it : services_) { if (it.second.name == name) return &it.second; } return nullptr; } HostImpl::ExposedService::ExposedService(ServiceID id_, const std::string& name_, std::unique_ptr<Service> instance_) : id(id_), name(name_), instance(std::move(instance_)) {} HostImpl::ExposedService::ExposedService(ExposedService&&) noexcept = default; HostImpl::ExposedService& HostImpl::ExposedService::operator=( HostImpl::ExposedService&&) = default; HostImpl::ExposedService::~ExposedService() = default; HostImpl::ClientConnection::~ClientConnection() = default; } // namespace ipc } // namespace perfetto