Skip to content
This repository has been archived by the owner on May 18, 2022. It is now read-only.

build.rs-based service declarations #72

Open
jonas-schievink opened this issue Aug 16, 2019 · 5 comments
Open

build.rs-based service declarations #72

jonas-schievink opened this issue Aug 16, 2019 · 5 comments
Labels
area: (G)ATT Code Area: Attribute Protocol or the Generic Attribute Profile built on top of ATT status: needs code An implementation or a bugfix need to be written type: enhancement A new feature or an improvement for an existing one
Milestone

Comments

@jonas-schievink
Copy link
Owner

jonas-schievink commented Aug 16, 2019

In order to make defining services and characteristics easy, Rubble will likely want to provide a build.rs-based API that uses builder structs to define the provided services and characteristics. We can then generate highly size-optimized code containing the necessary attribute definitions, and can also enforce that all included services are present at build time.

Things to figure out:

  • How to do access callbacks? Many attributes just return fixed data, but for some, having a callback invoked is very much required for functionality.
  • Is there any value in having Encryption, Authentication, and Authorization as separate flags or can we collapse them?

I did some brainstorming about how to store the data efficiently:

Click to expand
const UUID16_COUNT: usize = 3;
const UUID128_COUNT: usize = 0;
const ATTR_COUNT: usize = 3;
const POD_BYTES: usize = 8;

struct CompressedData {
    /// 16-bit UUIDs used by the attributes.
    uuid16s: [u16; UUID16_COUNT],
    /// 128-bit UUIDs used by the attributes.
    uuid128s: [Uuid; UUID128_COUNT],
    /// Plain old data bytes used as attribute values.
    pod: [u8; POD_BYTES],
    /// Compressed list of attributes.
    attributes: [CompressedAttr; ATTR_COUNT],
}

/// A compressed representation of an attribute.
///
/// An attribute consists of:
/// * 16-bit attribute handle
/// * 16- or 128-bit UUID for the attribute type
/// * Permission flags:
///   * Access Permissions (Read-only, Write-only, or Read+Write)
///   * Encryption Permissions (required or not required)
///   * Authentication Permissions (required or not required)
///   * Authorization Permissions (required or not required)
/// * A value of up to 512 Bytes
///
/// **Handle**: Since the spec requires handles to start at `0x0001` and increment sequentially,
/// we don't store them at all, and instead generate them on-the-fly.
///
/// **Type**: Storing an enum that can be a 16- or 128-bit UUID is very wasteful since it would use
/// 17 Bytes (16 Bytes for a 128-bit UUID and 1 Byte for the discriminant). So instead we store the
/// actual UUIDs outside the attribute and only put a small index and a bit indicating the type of
/// UUID in the attribute. This has the added benefit of deduplicating the storage of UUIDs that are
/// used multiple times.
///
/// **Permissions**: These can be stored in a total of 5 bits without doing anything clever.
///
/// **Value**: This is complicated. Many attributes have read-only values that never change and
/// should be stored in flash only (eg. meta-attributes providing GATT info). On the other hand,
/// some attributes will want to use RAM-backed data storage since the value may change at runtime.
/// Some attributes will have custom callbacks that need to be invoked when they're accessed.
///
/// One thing about the value we can definitely try to compress: The length. It is limited to 512
/// Bytes (for whatever reason), so it can be encoded in only 9 bits.
struct CompressedAttr {
    att_type: u8,
    flags: CompressedFlags,

    /// Least-significant 8 bits of the 9-bit length value. The MSb is stored in the `flags`.
    length_lsb: u8,

    /// If this attribute stores "plain old data", this is an offset into the `pod` field of
    /// `CompressedData`. Else this is the address of an access handler (TODO figure out how to do
    /// this exactly).
    value: u32,
}

bitflags! {
    /// Per-attribute bit flags in the compressed representation.
    struct CompressedFlags: u8 {
        /// If set, this attribute's type is specified by a full 128-bit UUID and needs to be
        /// looked up in `uuid128s` instead of `uuid16s`.
        const UUID128 = (1 << 0);

        const READABLE = (1 << 1);

        const WRITABLE = (1 << 2);

        const REQUIRE_ENCRYPTION = (1 << 3);

        const REQUIRE_AUTHENTICATION = (1 << 4);

        const REQUIRE_AUTHORIZATION = (1 << 5);

        /// Whether the attribute value is "plain old data".
        ///
        /// If this is set, `value` is an offset into the `pod` array of `CompressedData`. If it is
        /// unset, `value` is the address of an access callback.
        const IS_POD = (1 << 6);

        /// Most significant bit of the 9-bit length value.
        const LENGTH_MSB = (1 << 7);
    }
}

static ATTRIBUTES: CompressedData = CompressedData {
    uuid16s: [0x2800, 0x2803, 0x2A19],
    uuid128s: [],
    pod: [
        // "Primary Service" value
        0x0F, 0x18, // 0x180F = "Battery Service"
        // "Characteristic" value
        0x02, // properties: READ = 0x02
        0x03, 0x00, // handle = 0x0003
        0x19, 0x2A, // UUID = 0x2A19 (Battery Level)
        // "Battery Level" value
        48, // 48%
    ],
    attributes: [
        CompressedAttr {
            att_type: 0, // 0x2800 = "Primary Service"
            flags: CompressedFlags::READABLE | CompressedFlags::IS_POD,
            length_lsb: 2,
            value: 0,
        },
        CompressedAttr {
            att_type: 1, // 0x2803 = "Characteristic"
            flags: CompressedFlags::READABLE,
            length_lsb: 5,
            value: 2,
        },
        CompressedAttr {
            att_type: 2, // 0x2A19 = "Battery Level"
            flags: CompressedFlags::READABLE,
            length_lsb: 1,
            value: 7,
        },
    ],
};

Playground Braindump

@jonas-schievink jonas-schievink added type: enhancement A new feature or an improvement for an existing one status: needs code An implementation or a bugfix need to be written area: (G)ATT Code Area: Attribute Protocol or the Generic Attribute Profile built on top of ATT labels Aug 16, 2019
@jonas-schievink jonas-schievink added this to the 0.1.0 milestone Sep 23, 2019
@thalesfragoso
Copy link
Contributor

@jonas-schievink Have you had any idea on how to integrate a callback mechanism with resources policies like the one used in RTFM ? Or even reentrancy problems when using the usual interrupt::free Mutex.

@jonas-schievink
Copy link
Owner Author

I think we can mostly let the user handle this if it becomes necessary. The callbacks would be called from ATT only, which is driven by the lower-priority part of the stack, so Rubble itself doesn't require any locking as long as the callbacks own their resources.

I suppose we could also thread some sort of user-defined context through the Responder and ATT and pass it to the callbacks. We'll see if this becomes necessary once callbacks are implemented, I guess.

@o080o
Copy link

o080o commented May 26, 2020

I took a stab at this, looking at @tl8roy impl from #29 and @jonas-schievink original brainstorm in this issue.

here's a playground link: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=58502f4e1189a115d895c9b27be56448

(note, this is still a bit of a sketch, as it likely doesn't implement all the things we need on services + characteristics - I only tackled some of the hard part of having a list of things without mem allocations, and using callbacks for dynamic values.)

I'm not sure if we need a build.rs script to make this api work. This could be built in some init() function by the user, and map directly to Attribute structs either in ther constructors, or through some .build() function that traverses the tree. I used tuples and traits to essentially build a linked-list of different types of services+chararcteristics. This a) should be easy to write macros for, and b) lets the compiler know ahead of time the size, and types used, and allocate memory on the stack, rather than using Vec<> or the like. From this list-of-list data structure, we should be able to map to the actual Attribute structs relatively easily, though I haven't implemented this.

The only things that worries me, is that Attribute in the att module stores a static [u8] as it's "value". I did a bit of digging to find that it's main role is to be written into an outgoing buffer using the ByteWriter struct from the bytes module. To support dynamic values, I think we should implement the ToByte class directly for attributes (or have some method like .to_bytes(...)), so that we can have them dynamically generated values at runtime, and avoid the reference lifetime frustration of trying to have things return references from closures (which we will likely want for dynamic values).

As for the mapping between these types and Attribute, I would probably need to understand more of the spec, or at least nail down some of my assumptions after seeing the examples in the project. It seems to me like one characteristic == one attribute, and one service == one attribute + [characteristic attributes], and one profile == one 'primary service' attribute + [service attributes]? If so, we can likely allocate the Attribute as a field in each Service/Attribute struct, so at the end, a profile would already have a list-like group of attributes. (likely, another tuple linked-list struct, since we can't have generic array sizes, but we can have generic type signatures :D)

@jonas-schievink
Copy link
Owner Author

@o080o You linked to the same gist that's already linked above

@o080o
Copy link

o080o commented May 27, 2020

@jonas-schievink woops, fixed that.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: (G)ATT Code Area: Attribute Protocol or the Generic Attribute Profile built on top of ATT status: needs code An implementation or a bugfix need to be written type: enhancement A new feature or an improvement for an existing one
Projects
None yet
Development

No branches or pull requests

3 participants