blob: 06f7424085c03a6d910822a5d28c6b8723cd8d31 [file] [log] [blame]
// 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.
#ifndef SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_ATT_DATABASE_H_
#define SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_ATT_DATABASE_H_
#include <fbl/macros.h>
#include <list>
#include <memory>
#include <queue>
#include "src/connectivity/bluetooth/core/bt-host/att/att.h"
#include "src/connectivity/bluetooth/core/bt-host/att/attribute.h"
#include "src/connectivity/bluetooth/core/bt-host/common/byte_buffer.h"
#include "src/connectivity/bluetooth/core/bt-host/common/uuid.h"
#include "src/connectivity/bluetooth/core/bt-host/sm/types.h"
#include "src/lib/fxl/memory/ref_counted.h"
namespace bt {
namespace att {
// Represents a singe write operation queued for atomic submission by an ATT
// protocol write method.
class QueuedWrite {
public:
QueuedWrite() = default;
~QueuedWrite() = default;
// Constructs a write request by copying the contents of |value|.
QueuedWrite(Handle handle, uint16_t offset, const ByteBuffer& value);
// Allow move operations.
QueuedWrite(QueuedWrite&&) = default;
QueuedWrite& operator=(QueuedWrite&&) = default;
Handle handle() const { return handle_; }
uint16_t offset() const { return offset_; }
const ByteBuffer& value() const { return value_; }
private:
Handle handle_;
uint16_t offset_;
DynamicByteBuffer value_;
};
// Represents a prepare queue used to handle the ATT Prepare Write and Execute
// Write requests.
using PrepareWriteQueue = std::queue<QueuedWrite>;
// This class provides a simple attribute database abstraction. Attributes can
// be populated directly and queried to fulfill ATT protocol requests.
//
// Many Database instances can be created as long as care is taken that the
// referenced handle ranges are distinct. While this class is primarily intended
// to be used as a local ATT server database, it could also be used to represent
// a remote attribute cache.
//
// THREAD-SAFETY:
//
// This class is not thread-safe. The constructor/destructor and all public
// methods must be called on the same thread.
class Database final : public fxl::RefCountedThreadSafe<Database> {
using GroupingList = std::list<AttributeGrouping>;
public:
// This type allows iteration over the attributes in a database. An iterator
// is always initialzed with a handle range and options to skip attributes or
// groupings based on attribute type. An iterator always skips
// inactive/incomplete groupings.
//
// Modifying a database invalidates its iterators.
class Iterator final {
public:
// Returns the current attribute. Returns nullptr if the end of the handle
// range has been reached.
const Attribute* get() const;
// Advances the iterator forward. Skips over non-matching attributes if a
// type filter has been set. Has no effect if the end of the range was
// reached.
void Advance();
// If set, |next()| will only return attributes with the given |type|. No
// filter is set by default.
void set_type_filter(const UUID& type) { type_filter_ = type; }
// Returns true if the iterator cannot be advanced any further.
inline bool AtEnd() const { return grp_iter_ == grp_end_; }
private:
inline void MarkEnd() { grp_iter_ = grp_end_; }
friend class Database;
Iterator(GroupingList* list, Handle start, Handle end, const UUID* type,
bool groups_only);
Handle start_;
Handle end_;
bool grp_only_;
GroupingList::iterator grp_end_;
GroupingList::iterator grp_iter_;
uint8_t attr_offset_;
std::optional<UUID> type_filter_;
};
// Initializes this database to span the attribute handle range given by
// |range_start| and |range_end|. This allows the upper layer to segment the
// handle range into multiple contiguous regions by instantiating multiple
// Database objects.
//
// Note: This is to make it easy for the GATT layer to group service
// declarations with 16-bit UUIDs and 128-bit UUIDs separately as recommended
// by the GATT specification (see Vol 3, Part G, 3.1). By default
inline static fxl::RefPtr<Database> Create(Handle range_start = kHandleMin,
Handle range_end = kHandleMax) {
return fxl::AdoptRef(new Database(range_start, range_end));
}
// Returns an iterator that covers the handle range defined by |start| and
// |end| (inclusive). If |groups_only| is true, then the returned iterator
// will only return group declaration attributes (this allows quicker
// iteration over groupings while handling the ATT Read By Group Type
// request).
//
// If |type| is not a nullptr, it will be assigned as the iterator's type
// filter.
Iterator GetIterator(Handle start, Handle end, const UUID* type = nullptr,
bool groups_only = false);
// Creates a new attribute grouping with the given |type|. The grouping will
// be initialized to contain |attr_count| attributes (excluding the
// group declaration attribute) and |value| will be assigned as the group
// declaration attribute value.
//
// Returns a pointer to the new grouping, which can be used to populate
// attributes. Returns nullptr if the requested grouping could not be
// created due to insufficient handles.
//
// The returned pointer is owned and managed by this Database and should not
// be retained by the caller. Removing the grouping will invalidate the
// returned pointer.
AttributeGrouping* NewGrouping(const UUID& group_type, size_t attr_count,
const ByteBuffer& decl_value);
// Removes the attribute grouping that has the given starting handle. Returns
// false if no such grouping was found.
bool RemoveGrouping(Handle start_handle);
const std::list<AttributeGrouping>& groupings() const { return groupings_; }
// Finds and returns the attribute with the given handle. Returns nullptr if
// the attribute cannot be found or is part of a grouping that is inactive
// or incomplete.
const Attribute* FindAttribute(Handle handle);
// Applies all write requests in |write_queue| and reports the result in
// |callback|. All requests will be delivered to the attribute write handlers
// at once, in order, without waiting for a response on individual writes.
//
// If one or more requests result in an application protocol error,
// |callback| will be invoked with the value of the first error received and
// all subsequent results will be ignored. Otherwise, |callback| will be
// called with a success status once a reply has been received for all
// requests in the queue.
//
// This function performs any security checks even though these are expected
// to be done during the "prepare" phase. This is because the attribute
// database can change from the time writes are queued until they are
// committed. |security| should match the security properties of the link
// under which the execute write request was initiatied.
//
// Attribute value validation (such as offset and length checks) are expected
// to be done by the application that has set up the attribute. This function
// does check for the validity of a given attribute handle and whether the
// attribute supports writes.
//
// The Handle argument of |callback| is undefined if ErrorCode is
// ErrorCode::kNoError and should be ignored.
using WriteCallback = fit::function<void(Handle, ErrorCode)>;
void ExecuteWriteQueue(PeerId peer_id, PrepareWriteQueue write_queue,
const sm::SecurityProperties& security,
WriteCallback callback);
private:
FRIEND_REF_COUNTED_THREAD_SAFE(Database);
Database(Handle range_start, Handle range_end);
~Database() = default;
Handle range_start_;
Handle range_end_;
// The list of groupings is sorted by handle where each grouping maps to a
// non-overlapping handle range. Successive groupings don't necessarily
// represent contiguous handle ranges as any grouping can be removed.
//
// Note: This uses a std::list because fbl::lower_bound doesn't work with a
// LinkedList (aka fbl::DoublyLinkedList). This is only marginally
// less space efficient.
GroupingList groupings_;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(Database);
};
} // namespace att
} // namespace bt
#endif // SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_ATT_DATABASE_H_