//
// Copyright (C) 2015 Google, Inc.
//
// 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.
//
#define LOG_TAG "bt_bluetooth_host"
#include "service/ipc/linux_ipc_host.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <algorithm>
#include <base/base64.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include "osi/include/osi.h"
#include "osi/include/log.h"
#include "service/adapter.h"
using bluetooth::Adapter;
using bluetooth::UUID;
using namespace bluetooth::gatt;
namespace {
// IPC API is according to:
// https://docs.google.com/document/d/1eRnku-jAyVU1wGJsLT2CzWi0-8bs2g49s1b3FR_GApM
const char kSetAdapterNameCommand[] = "set-device-name";
const char kCreateServiceCommand[] = "create-service";
const char kDestroyServiceCommand[] = "destroy-service";
const char kAddCharacteristicCommand[] = "add-characteristic";
const char kSetCharacteristicValueCommand[] = "set-characteristic-value";
const char kSetAdvertisementCommand[] = "set-advertisement";
const char kSetScanResponseCommand[] = "set-scan-response";
const char kStartServiceCommand[] = "start-service";
const char kStopServiceCommand[] = "stop-service";
const char kWriteCharacteristicCommand[] = "write-characteristic";
// Useful values for indexing LinuxIPCHost::pfds_
// Not super general considering that we should be able to support
// many GATT FDs owned by one LinuxIPCHost.
enum {
kFdIpc = 0,
kFdGatt = 1,
kPossibleFds = 2,
};
bool TokenBool(const std::string& text) {
return text == "true";
}
} // namespace
namespace ipc {
LinuxIPCHost::LinuxIPCHost(int sockfd, Adapter* adapter)
: adapter_(adapter), pfds_(1, {sockfd, POLLIN, 0}) {}
LinuxIPCHost::~LinuxIPCHost() {
close(pfds_[0].fd);
}
bool LinuxIPCHost::EventLoop() {
while (true) {
int status =
TEMP_FAILURE_RETRY(ppoll(pfds_.data(), pfds_.size(), nullptr, nullptr));
if (status < 1) {
LOG_ERROR(LOG_TAG, "ppoll error");
return false;
}
if (pfds_[kFdIpc].revents && !OnMessage()) {
return false;
}
if (pfds_.size() == kPossibleFds &&
pfds_[kFdGatt].revents &&
!OnGattWrite()) {
return false;
}
}
return true;
}
bool LinuxIPCHost::OnSetAdapterName(const std::string& name) {
std::string decoded_data;
base::Base64Decode(name, &decoded_data);
return adapter_->SetName(decoded_data);
}
bool LinuxIPCHost::OnCreateService(const std::string& service_uuid) {
gatt_servers_[service_uuid] = std::unique_ptr<Server>(new Server);
int gattfd;
bool status = gatt_servers_[service_uuid]->Initialize(
UUID(service_uuid), &gattfd);
if (!status) {
LOG_ERROR(LOG_TAG, "Failed to initialize bluetooth");
return false;
}
pfds_.resize(kPossibleFds);
pfds_[kFdGatt] = {gattfd, POLLIN, 0};
return true;
}
bool LinuxIPCHost::OnDestroyService(const std::string& service_uuid) {
gatt_servers_.erase(service_uuid);
close(pfds_[1].fd);
pfds_.resize(1);
return true;
}
bool LinuxIPCHost::OnAddCharacteristic(const std::string& service_uuid,
const std::string& characteristic_uuid,
const std::string& control_uuid,
const std::string& options) {
std::vector<std::string> option_tokens = base::SplitString(
options, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
int properties_mask = 0;
int permissions_mask = 0;
if (std::find(option_tokens.begin(), option_tokens.end(), "notify") !=
option_tokens.end()) {
permissions_mask |= kPermissionRead;
properties_mask |= kPropertyRead;
properties_mask |= kPropertyNotify;
}
if (std::find(option_tokens.begin(), option_tokens.end(), "read") !=
option_tokens.end()) {
permissions_mask |= kPermissionRead;
properties_mask |= kPropertyRead;
}
if (std::find(option_tokens.begin(), option_tokens.end(), "write") !=
option_tokens.end()) {
permissions_mask |= kPermissionWrite;
properties_mask |= kPropertyWrite;
}
if (control_uuid.empty()) {
gatt_servers_[service_uuid]->AddCharacteristic(
UUID(characteristic_uuid), properties_mask, permissions_mask);
} else {
gatt_servers_[service_uuid]->AddBlob(UUID(characteristic_uuid),
UUID(control_uuid), properties_mask,
permissions_mask);
}
return true;
}
bool LinuxIPCHost::OnSetCharacteristicValue(const std::string& service_uuid,
const std::string& characteristic_uuid,
const std::string& value) {
std::string decoded_data;
base::Base64Decode(value, &decoded_data);
std::vector<uint8_t> blob_data(decoded_data.begin(), decoded_data.end());
gatt_servers_[service_uuid]->SetCharacteristicValue(UUID(characteristic_uuid),
blob_data);
return true;
}
bool LinuxIPCHost::OnSetAdvertisement(const std::string& service_uuid,
const std::string& advertise_uuids,
const std::string& advertise_data,
const std::string& manufacturer_data,
const std::string& transmit_name) {
LOG_INFO(LOG_TAG, "%s: service:%s uuids:%s data:%s", __func__, service_uuid.c_str(),
advertise_uuids.c_str(), advertise_data.c_str());
std::vector<std::string> advertise_uuid_tokens = base::SplitString(
advertise_uuids, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
// string -> vector<UUID>
std::vector<UUID> ids;
for (const auto& uuid_token : advertise_uuid_tokens)
ids.emplace_back(uuid_token);
std::string decoded_data;
base::Base64Decode(advertise_data, &decoded_data);
std::vector<uint8_t> decoded_advertise_data(decoded_data.begin(),
decoded_data.end());
base::Base64Decode(manufacturer_data, &decoded_data);
std::vector<uint8_t> decoded_manufacturer_data(decoded_data.begin(),
decoded_data.end());
gatt_servers_[service_uuid]->SetAdvertisement(ids, decoded_advertise_data,
decoded_manufacturer_data,
TokenBool(transmit_name));
return true;
}
bool LinuxIPCHost::OnSetScanResponse(const std::string& service_uuid,
const std::string& scan_response_uuids,
const std::string& scan_response_data,
const std::string& manufacturer_data,
const std::string& transmit_name) {
std::vector<std::string> scan_response_uuid_tokens = base::SplitString(
scan_response_uuids, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
// string -> vector<UUID>
std::vector<UUID> ids;
for (const auto& uuid_token : scan_response_uuid_tokens)
ids.emplace_back(uuid_token);
std::string decoded_data;
base::Base64Decode(scan_response_data, &decoded_data);
std::vector<uint8_t> decoded_advertise_data(decoded_data.begin(),
decoded_data.end());
base::Base64Decode(manufacturer_data, &decoded_data);
std::vector<uint8_t> decoded_manufacturer_data(decoded_data.begin(),
decoded_data.end());
gatt_servers_[service_uuid]->SetScanResponse(ids, decoded_advertise_data,
decoded_manufacturer_data,
TokenBool(transmit_name));
return true;
}
bool LinuxIPCHost::OnStartService(const std::string& service_uuid) {
return gatt_servers_[service_uuid]->Start();
}
bool LinuxIPCHost::OnStopService(const std::string& service_uuid) {
return gatt_servers_[service_uuid]->Stop();
}
bool LinuxIPCHost::OnMessage() {
std::string ipc_msg;
ssize_t size;
OSI_NO_INTR(size = recv(pfds_[kFdIpc].fd, &ipc_msg[0], 0,
MSG_PEEK | MSG_TRUNC));
if (-1 == size) {
LOG_ERROR(LOG_TAG, "Error reading datagram size: %s", strerror(errno));
return false;
} else if (0 == size) {
LOG_INFO(LOG_TAG, "%s:%d: Connection closed", __func__, __LINE__);
return false;
}
ipc_msg.resize(size);
OSI_NO_INTR(size = read(pfds_[kFdIpc].fd, &ipc_msg[0], ipc_msg.size()));
if (-1 == size) {
LOG_ERROR(LOG_TAG, "Error reading IPC: %s", strerror(errno));
return false;
} else if (0 == size) {
LOG_INFO(LOG_TAG, "%s:%d: Connection closed", __func__, __LINE__);
return false;
}
std::vector<std::string> tokens = base::SplitString(
ipc_msg, "|", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
switch (tokens.size()) {
case 2:
if (tokens[0] == kSetAdapterNameCommand)
return OnSetAdapterName(tokens[1]);
if (tokens[0] == kCreateServiceCommand)
return OnCreateService(tokens[1]);
if (tokens[0] == kDestroyServiceCommand)
return OnDestroyService(tokens[1]);
if (tokens[0] == kStartServiceCommand)
return OnStartService(tokens[1]);
if (tokens[0] == kStopServiceCommand)
return OnStopService(tokens[1]);
break;
case 4:
if (tokens[0] == kSetCharacteristicValueCommand)
return OnSetCharacteristicValue(tokens[1], tokens[2], tokens[3]);
break;
case 5:
if (tokens[0] == kAddCharacteristicCommand)
return OnAddCharacteristic(tokens[1], tokens[2], tokens[3], tokens[4]);
break;
case 6:
if (tokens[0] == kSetAdvertisementCommand)
return OnSetAdvertisement(tokens[1], tokens[2], tokens[3], tokens[4], tokens[5]);
if (tokens[0] == kSetScanResponseCommand)
return OnSetScanResponse(tokens[1], tokens[2], tokens[3], tokens[4], tokens[5]);
break;
default:
break;
}
LOG_ERROR(LOG_TAG, "Malformed IPC message: %s", ipc_msg.c_str());
return false;
}
bool LinuxIPCHost::OnGattWrite() {
UUID::UUID128Bit id;
ssize_t r;
OSI_NO_INTR(r = read(pfds_[kFdGatt].fd, id.data(), id.size()));
if (r != id.size()) {
LOG_ERROR(LOG_TAG, "Error reading GATT attribute ID");
return false;
}
std::vector<uint8_t> value;
// TODO(icoolidge): Generalize this for multiple clients.
auto server = gatt_servers_.begin();
server->second->GetCharacteristicValue(UUID(id), &value);
const std::string value_string(value.begin(), value.end());
std::string encoded_value;
base::Base64Encode(value_string, &encoded_value);
std::string transmit(kWriteCharacteristicCommand);
transmit += "|" + server->first;
transmit += "|" + base::HexEncode(id.data(), id.size());
transmit += "|" + encoded_value;
OSI_NO_INTR(r = write(pfds_[kFdIpc].fd, transmit.data(), transmit.size()));
if (-1 == r) {
LOG_ERROR(LOG_TAG, "Error replying to IPC: %s", strerror(errno));
return false;
}
return true;
}
} // namespace ipc