blob: 302228f673dc7ad454cd9491d36ebca6d57aeb43 [file] [log] [blame]
// Copyright 2018 The Chromium OS 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 "media_perception/cros_dbus_service.h"
#include <vector>
#include <base/check.h>
namespace mri {
namespace {
// We need to poll the dbus message queue periodically for handling new method
// calls. This variable defines the polling period in milliseconds, and it will
// affect the responsiveness of the dbus server and cpu usage.
constexpr int kPollingPeriodMilliSeconds = 1;
std::string RequestOwnershipReplyToString(unsigned int reply) {
switch (reply) {
case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
return "DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER";
case DBUS_REQUEST_NAME_REPLY_IN_QUEUE:
return "DBUS_REQUEST_NAME_REPLY_IN_QUEUE";
case DBUS_REQUEST_NAME_REPLY_EXISTS:
return "DBUS_REQUEST_NAME_REPLY_EXISTS";
case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER:
return "DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER";
default:
return "UNKNOWN_TYPE";
}
}
} // namespace
CrOSDbusService::~CrOSDbusService() {
// Applications should unref the shared connection created with
// dbus_bus_get().
if (IsConnected()) {
std::lock_guard<std::mutex> lock(connection_lock_);
dbus_connection_unref(connection_);
connection_ = nullptr;
}
}
void CrOSDbusService::Connect(const Service service) {
if (IsConnected()) {
LOG(WARNING) << "Dbus connection has already been established.";
return;
}
DBusError error;
dbus_error_init(&error);
if (dbus_error_is_set(&error)) {
LOG(ERROR) << "Dbus connection error: " << error.message;
dbus_error_free(&error);
return;
}
std::lock_guard<std::mutex> lock(connection_lock_);
connection_ = dbus_bus_get(DBUS_BUS_SYSTEM, &error);
CHECK(connection_ != nullptr) << "Connection is nullptr.";
// This request will return -1 if error is set, and a non-negative number
// otherwise.
const int reply = dbus_bus_request_name(
connection_, ServiceEnumToServiceName(service).c_str(),
DBUS_NAME_FLAG_REPLACE_EXISTING, &error);
CHECK(reply >= 0) << "Failed to own media perception service: "
<< error.message;
DLOG(INFO) << "dbus_connection_get_server_id = "
<< dbus_connection_get_server_id(connection_);
DLOG(INFO) << "dbus_bus_get_id = " << dbus_bus_get_id(connection_, &error);
DLOG(INFO) << "dbus_get_local_machine_id = " << dbus_get_local_machine_id();
DLOG(INFO) << "dbus_request_name() has reply: "
<< RequestOwnershipReplyToString(reply);
// Store the service enum for the active connection.
service_ = service;
}
bool CrOSDbusService::IsConnected() const {
std::lock_guard<std::mutex> lock(connection_lock_);
return connection_ != nullptr;
}
bool CrOSDbusService::PublishSignal(const mri::Signal signal,
const std::vector<uint8_t>* bytes) {
if (bytes == nullptr) {
LOG(WARNING) << "Failed to publish signal - bytes is nullptr.";
return false;
}
if (!IsConnected()) {
LOG(WARNING) << "Failed to publish signal - not connected.";
return false;
}
DBusMessage* message =
dbus_message_new_signal(ServiceEnumToServicePath(service_).c_str(),
ServiceEnumToServiceName(service_).c_str(),
SignalEnumToSignalName(signal).c_str());
if (message == nullptr) {
LOG(WARNING) << "Out of memory!";
return false;
}
if (!dbus_message_append_args(message, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, bytes,
bytes->size(), DBUS_TYPE_INVALID)) {
LOG(WARNING) << "Out of memory!";
dbus_message_unref(message);
return false;
}
{
std::lock_guard<std::mutex> lock(connection_lock_);
dbus_connection_send(connection_, message, nullptr);
dbus_connection_flush(connection_);
}
dbus_message_unref(message);
return true;
}
void CrOSDbusService::PollMessageQueue() {
if (!IsConnected()) {
LOG(WARNING) << "Failed to poll message queue.";
return;
}
// This loop will continue until another management process explicitly kills
// the current program.
while (true) {
DBusMessage* message = nullptr;
{
std::lock_guard<std::mutex> lock(connection_lock_);
// Non-blocking read of the next available message.
dbus_connection_read_write(connection_, 0);
message = dbus_connection_pop_message(connection_);
}
// Poll the message queue every kPollingPeriodMilliSeconds for the new
// method call.
if (message == nullptr) {
usleep(kPollingPeriodMilliSeconds * 1000);
continue;
}
// Process this message and store the reply in |bytes|.
std::vector<uint8_t> bytes;
if (!ProcessMessage(message, &bytes)) {
continue;
}
DBusMessage* reply = dbus_message_new_method_return(message);
if (!bytes.empty()) {
dbus_message_append_args(reply, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &bytes,
bytes.size(), DBUS_TYPE_INVALID);
}
{
std::lock_guard<std::mutex> lock(connection_lock_);
dbus_connection_send(connection_, reply, nullptr);
dbus_connection_flush(connection_);
}
dbus_message_unref(reply);
dbus_message_unref(message);
}
}
bool CrOSDbusService::ProcessMessage(DBusMessage* message,
std::vector<uint8_t>* bytes) {
if (message == nullptr || bytes == nullptr) {
LOG(WARNING) << "Failed to process this Dbus message.";
return false;
}
// Check to see if its a BootstrapMojoConnection method call.
if (dbus_message_is_method_call(
message, ServiceEnumToServiceName(service_).c_str(),
MethodEnumToMethodName(Method::BOOTSTRAP_MOJO_CONNECTION).c_str())) {
DBusMessageIter iter;
if (!dbus_message_iter_init(message, &iter)) {
LOG(ERROR) << "Could not get iter.";
return false;
}
DBusBasicValue value;
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UNIX_FD) {
LOG(ERROR) << "Arg type is not UNIX_FD.";
}
dbus_message_iter_get_basic(&iter, &value);
if (mojo_connector_ == nullptr) {
LOG(ERROR) << "Mojo Connector is nullptr.";
return false;
}
LOG(INFO) << "File descriptor: " << value.fd;
mojo_connector_->ReceiveMojoInvitationFileDescriptor(value.fd);
return true;
}
if (message_handler_ == nullptr) {
LOG(ERROR) << "Message handler is not set.";
return false;
}
// Check to see if its a GetDiagnostics method call.
if (dbus_message_is_method_call(
message, ServiceEnumToServiceName(service_).c_str(),
MethodEnumToMethodName(Method::GET_DIAGNOSTICS).c_str())) {
// No input arguments for GetDiagnostics.
message_handler_(Method::GET_DIAGNOSTICS, nullptr, 0, bytes);
return true;
}
// Check to see if its a State method call.
if (!dbus_message_is_method_call(
message, ServiceEnumToServiceName(service_).c_str(),
MethodEnumToMethodName(Method::STATE).c_str())) {
// Neither GetDiagnostics or State.
return false;
}
// We have a State method call, check to see if it is a GetState call.
DBusMessageIter iter;
if (!dbus_message_iter_init(message, &iter)) {
// No input arguments for GetState.
message_handler_(Method::STATE, nullptr, 0, bytes);
return true;
}
// This means SetState and we use the following variables to store
// arguments of this method call.
uint8_t* arg_bytes = nullptr;
int arg_size = 0;
if (!dbus_message_get_args(message, nullptr, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
&arg_bytes, &arg_size, DBUS_TYPE_INVALID)) {
LOG(WARNING) << "Failed to parse args of a SetState method call.";
return false;
}
message_handler_(Method::STATE, arg_bytes, arg_size, bytes);
return true;
}
} // namespace mri