You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
339 lines
11 KiB
339 lines
11 KiB
//
|
|
// Copyright 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/log.h"
|
|
#include "osi/include/osi.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("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::FromString(service_uuid), &gattfd);
|
|
if (!status) {
|
|
LOG_ERROR("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::FromString(characteristic_uuid), properties_mask,
|
|
permissions_mask);
|
|
} else {
|
|
gatt_servers_[service_uuid]->AddBlob(Uuid::FromString(characteristic_uuid),
|
|
Uuid::FromString(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::FromString(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("%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::FromString(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::FromString(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("Error reading datagram size: %s", strerror(errno));
|
|
return false;
|
|
} else if (0 == size) {
|
|
LOG_INFO("%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("Error reading IPC: %s", strerror(errno));
|
|
return false;
|
|
} else if (0 == size) {
|
|
LOG_INFO("%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("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("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::From128BitBE(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("Error replying to IPC: %s", strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace ipc
|