| // Copyright 2017 The Fuchsia 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 "fake_device.h" |
| |
| #include <endian.h> |
| #include <zircon/assert.h> |
| #include <zircon/syscalls.h> |
| |
| #include "garnet/drivers/bluetooth/lib/common/log.h" |
| #include "garnet/drivers/bluetooth/lib/common/packet_view.h" |
| #include "garnet/drivers/bluetooth/lib/l2cap/l2cap.h" |
| |
| namespace btlib { |
| |
| using common::ByteBuffer; |
| |
| namespace testing { |
| namespace { |
| |
| void WriteRandomRSSI(int8_t* out_mem) { |
| constexpr int8_t kRSSIMin = -127; |
| constexpr int8_t kRSSIMax = 20; |
| |
| int8_t rssi; |
| zx_cprng_draw(reinterpret_cast<unsigned char*>(&rssi), sizeof(rssi)); |
| rssi = (rssi % (kRSSIMax - kRSSIMin)) + kRSSIMin; |
| |
| *out_mem = rssi; |
| } |
| |
| } // namespace |
| |
| FakeDevice::FakeDevice(const common::DeviceAddress& address, bool connectable, |
| bool scannable) |
| : ctrl_(nullptr), |
| address_(address), |
| connected_(false), |
| connectable_(connectable), |
| scannable_(scannable), |
| directed_(false), |
| address_resolved_(false), |
| connect_status_(hci::StatusCode::kSuccess), |
| connect_response_(hci::StatusCode::kSuccess), |
| force_pending_connect_(false), |
| should_batch_reports_(false), |
| gatt_server_(this) {} |
| |
| void FakeDevice::SetAdvertisingData(const common::ByteBuffer& data) { |
| ZX_DEBUG_ASSERT(data.size() <= hci::kMaxLEAdvertisingDataLength); |
| adv_data_ = common::DynamicByteBuffer(data); |
| } |
| |
| void FakeDevice::SetScanResponse(bool should_batch_reports, |
| const common::ByteBuffer& data) { |
| ZX_DEBUG_ASSERT(scannable_); |
| ZX_DEBUG_ASSERT(data.size() <= hci::kMaxLEAdvertisingDataLength); |
| scan_rsp_ = common::DynamicByteBuffer(data); |
| should_batch_reports_ = should_batch_reports; |
| } |
| |
| common::DynamicByteBuffer FakeDevice::CreateInquiryResponseEvent( |
| hci::InquiryMode mode) const { |
| ZX_DEBUG_ASSERT(address_.type() == common::DeviceAddress::Type::kBREDR); |
| |
| size_t param_size; |
| if (mode == hci::InquiryMode::kStandard) { |
| param_size = |
| sizeof(hci::InquiryResultEventParams) + sizeof(hci::InquiryResult); |
| } else { |
| param_size = sizeof(hci::InquiryResultWithRSSIEventParams) + |
| sizeof(hci::InquiryResultRSSI); |
| } |
| |
| common::DynamicByteBuffer buffer(sizeof(hci::EventHeader) + param_size); |
| common::MutablePacketView<hci::EventHeader> event(&buffer, param_size); |
| event.mutable_header()->parameter_total_size = param_size; |
| |
| // TODO(jamuraa): simultate clock offset and RSSI |
| if (mode == hci::InquiryMode::kStandard) { |
| event.mutable_header()->event_code = hci::kInquiryResultEventCode; |
| auto payload = event.mutable_payload<hci::InquiryResultEventParams>(); |
| payload->num_responses = 1u; |
| |
| auto inq_result = reinterpret_cast<hci::InquiryResult*>(payload->responses); |
| inq_result->bd_addr = address_.value(); |
| inq_result->page_scan_repetition_mode = hci::PageScanRepetitionMode::kR0; |
| inq_result->class_of_device = class_of_device_; |
| inq_result->clock_offset = 0; |
| } else { |
| event.mutable_header()->event_code = hci::kInquiryResultWithRSSIEventCode; |
| auto payload = |
| event.mutable_payload<hci::InquiryResultWithRSSIEventParams>(); |
| payload->num_responses = 1u; |
| |
| auto inq_result = |
| reinterpret_cast<hci::InquiryResultRSSI*>(payload->responses); |
| inq_result->bd_addr = address_.value(); |
| inq_result->page_scan_repetition_mode = hci::PageScanRepetitionMode::kR0; |
| inq_result->class_of_device = class_of_device_; |
| inq_result->clock_offset = 0; |
| inq_result->rssi = -30; |
| } |
| |
| return buffer; |
| } |
| |
| common::DynamicByteBuffer FakeDevice::CreateAdvertisingReportEvent( |
| bool include_scan_rsp) const { |
| size_t param_size = sizeof(hci::LEMetaEventParams) + |
| sizeof(hci::LEAdvertisingReportSubeventParams) + |
| sizeof(hci::LEAdvertisingReportData) + adv_data_.size() + |
| sizeof(int8_t); |
| if (include_scan_rsp) { |
| ZX_DEBUG_ASSERT(scannable_); |
| param_size += sizeof(hci::LEAdvertisingReportData) + scan_rsp_.size() + |
| sizeof(int8_t); |
| } |
| |
| common::DynamicByteBuffer buffer(sizeof(hci::EventHeader) + param_size); |
| common::MutablePacketView<hci::EventHeader> event(&buffer, param_size); |
| event.mutable_header()->event_code = hci::kLEMetaEventCode; |
| event.mutable_header()->parameter_total_size = param_size; |
| |
| auto payload = event.mutable_payload<hci::LEMetaEventParams>(); |
| payload->subevent_code = hci::kLEAdvertisingReportSubeventCode; |
| |
| auto subevent_payload = |
| reinterpret_cast<hci::LEAdvertisingReportSubeventParams*>( |
| payload->subevent_parameters); |
| subevent_payload->num_reports = include_scan_rsp ? 2 : 1; |
| |
| auto report = reinterpret_cast<hci::LEAdvertisingReportData*>( |
| subevent_payload->reports); |
| if (directed_) { |
| report->event_type = hci::LEAdvertisingEventType::kAdvDirectInd; |
| } else if (connectable_) { |
| report->event_type = hci::LEAdvertisingEventType::kAdvInd; |
| } else if (scannable_) { |
| report->event_type = hci::LEAdvertisingEventType::kAdvScanInd; |
| } else { |
| report->event_type = hci::LEAdvertisingEventType::kAdvNonConnInd; |
| } |
| if (address_.type() == common::DeviceAddress::Type::kLERandom) { |
| report->address_type = address_resolved_ |
| ? hci::LEAddressType::kRandomIdentity |
| : hci::LEAddressType::kRandom; |
| } else { |
| report->address_type = address_resolved_ |
| ? hci::LEAddressType::kPublicIdentity |
| : hci::LEAddressType::kPublic; |
| } |
| report->address = address_.value(); |
| report->length_data = adv_data_.size(); |
| std::memcpy(report->data, adv_data_.data(), adv_data_.size()); |
| |
| WriteRandomRSSI( |
| reinterpret_cast<int8_t*>(report->data + report->length_data)); |
| |
| if (include_scan_rsp) { |
| WriteScanResponseReport(reinterpret_cast<hci::LEAdvertisingReportData*>( |
| report->data + report->length_data + sizeof(int8_t))); |
| } |
| |
| return buffer; |
| } |
| |
| common::DynamicByteBuffer FakeDevice::CreateScanResponseReportEvent() const { |
| ZX_DEBUG_ASSERT(scannable_); |
| size_t param_size = sizeof(hci::LEMetaEventParams) + |
| sizeof(hci::LEAdvertisingReportSubeventParams) + |
| sizeof(hci::LEAdvertisingReportData) + scan_rsp_.size() + |
| sizeof(int8_t); |
| |
| common::DynamicByteBuffer buffer(sizeof(hci::EventHeader) + param_size); |
| common::MutablePacketView<hci::EventHeader> event(&buffer, param_size); |
| event.mutable_header()->event_code = hci::kLEMetaEventCode; |
| event.mutable_header()->parameter_total_size = param_size; |
| |
| auto payload = event.mutable_payload<hci::LEMetaEventParams>(); |
| payload->subevent_code = hci::kLEAdvertisingReportSubeventCode; |
| |
| auto subevent_payload = |
| reinterpret_cast<hci::LEAdvertisingReportSubeventParams*>( |
| payload->subevent_parameters); |
| subevent_payload->num_reports = 1; |
| |
| auto report = reinterpret_cast<hci::LEAdvertisingReportData*>( |
| subevent_payload->reports); |
| WriteScanResponseReport(report); |
| |
| return buffer; |
| } |
| |
| void FakeDevice::AddLink(hci::ConnectionHandle handle) { |
| ZX_DEBUG_ASSERT(!HasLink(handle)); |
| logical_links_.insert(handle); |
| |
| if (logical_links_.size() == 1u) |
| set_connected(true); |
| } |
| |
| void FakeDevice::RemoveLink(hci::ConnectionHandle handle) { |
| ZX_DEBUG_ASSERT(HasLink(handle)); |
| logical_links_.erase(handle); |
| if (logical_links_.empty()) |
| set_connected(false); |
| } |
| |
| bool FakeDevice::HasLink(hci::ConnectionHandle handle) const { |
| return logical_links_.count(handle) != 0u; |
| } |
| |
| FakeDevice::HandleSet FakeDevice::Disconnect() { |
| set_connected(false); |
| return std::move(logical_links_); |
| } |
| |
| void FakeDevice::WriteScanResponseReport( |
| hci::LEAdvertisingReportData* report) const { |
| ZX_DEBUG_ASSERT(scannable_); |
| report->event_type = hci::LEAdvertisingEventType::kScanRsp; |
| report->address_type = |
| (address_.type() == common::DeviceAddress::Type::kLERandom) |
| ? hci::LEAddressType::kRandom |
| : hci::LEAddressType::kPublic; |
| report->address = address_.value(); |
| report->length_data = scan_rsp_.size(); |
| std::memcpy(report->data, scan_rsp_.data(), scan_rsp_.size()); |
| |
| WriteRandomRSSI( |
| reinterpret_cast<int8_t*>(report->data + report->length_data)); |
| } |
| |
| void FakeDevice::OnRxL2CAP(hci::ConnectionHandle conn, |
| const common::ByteBuffer& pdu) { |
| if (pdu.size() < sizeof(l2cap::BasicHeader)) { |
| bt_log(WARN, "fake-hci", "malformed L2CAP packet!"); |
| return; |
| } |
| |
| const auto& header = pdu.As<l2cap::BasicHeader>(); |
| uint16_t len = le16toh(header.length); |
| l2cap::ChannelId chan_id = le16toh(header.channel_id); |
| |
| auto payload = pdu.view(sizeof(l2cap::BasicHeader)); |
| if (payload.size() != len) { |
| bt_log(WARN, "fake-hci", "malformed L2CAP B-frame header!"); |
| return; |
| } |
| |
| switch (chan_id) { |
| case l2cap::kATTChannelId: |
| gatt_server_.HandlePdu(conn, payload); |
| break; |
| |
| // TODO(armansito): Support other channels |
| default: |
| break; |
| } |
| } |
| |
| } // namespace testing |
| } // namespace btlib |