| // |
| // 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(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::FromString(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::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(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::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(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::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(LOG_TAG, "Error replying to IPC: %s", strerror(errno)); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| } // namespace ipc |