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.
338 lines
11 KiB
338 lines
11 KiB
// |
|
// 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/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(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
|
|
|