rubble icon indicating copy to clipboard operation
rubble copied to clipboard

Implement (G)ATT on top of L2CAP

Open jonas-schievink opened this issue 5 years ago • 6 comments

GATT is not it's own protocol, but means Generic Attribute Profile (even though the spec sometimes calls it Generic Attribute Protocol - I'm not making this shit up!). We have to implement whatever parts of ATT are needed by GATT.

jonas-schievink avatar Mar 31 '19 13:03 jonas-schievink

A hard-coded primary service is now enumerated correctly. Next steps:

  • ATT needs to call into GATT to figure some things out because BLE is a giant protocol layering violation
  • An interface for exposing and defining Services and Characteristics needs to be created. Currently we just operate off of a slice of raw attributes. We need a better interfacing trait and possibly proc macros to make this simpler.
  • Figure out how to do notification/indication of changed attribute values

jonas-schievink avatar Apr 15 '19 19:04 jonas-schievink

Removing milestone since service enumeration with one hard-coded service now works.

jonas-schievink avatar Apr 26 '19 11:04 jonas-schievink

I've been using NimBLE for a few days, and think implementing a similar pattern for notifications would be a good idea, I'm going to start work on this over the next couple days and see where I get.

NimBLE ble_gap.h

NimBLE ble_gatt.h

fmckeogh avatar Jun 19 '19 06:06 fmckeogh

As promised may moons ago, here is an example layout that I think will work (hopefully this job is still relevant). I am not 100% certain of the actual parameters required, nor how they fit into the existing code. Hopefully you can make adjustments as required.

It also doesn't take into account Server vs Client, but I think Client is a subset of the traits and probably not much different.

struct Profile {}

trait GATTProfile {
    fn get_uuid() -> UUID;
    fn set_uuid(uuid: UUID);
    fn get_profile_type() -> ProfileType;
    fn get_profile_type(profie_type: ProfileType);
    fn add_services(attr: GATTService);
    fn get_services(uuid: UUID) -> & GATTService;
    fn get_services(service_type: Option<ServiceType>) -> Vec<& GATTService>;
}

#[GATTService("UUID","ServiceType")]
struct Service {}

trait GATTService {
    fn get_uuid() -> UUID;
    fn set_uuid(uuid: UUID);
    fn get_service_type() -> ServiceType;
    fn get_service_type(profie_type: ServiceType);
    fn add_characteristic(attr: GATTCharacteristic);
    fn get_characteristic(uuid: UUID) -> & GATTCharacteristic;
    fn get_characteristic() -> Vec<& GATTCharacteristic>;
}

#[GATTCharacteristic("UUID","Descriptor")]
struct Characteristic {
    value: [u8]
}

trait GATTCharacteristic {
    fn get_uuid() -> UUID;
    fn set_uuid(uuid: UUID);
    fn get_descriptor() -> String;
    fn set_descriptor(descriptor: String);
    fn get_value() -> [u8];
    fn set_value(value: [u8]);   
    fn notify(func: Option<FnOnce(value: [u8])>); 
    fn indicate(func: Option<FnOnce(value: [u8])>); 
}```

I can't say that I can help with the implementation, but I am happy to review any decisions.

tl8roy avatar May 03 '20 08:05 tl8roy

@tl8roy Sorry, I'm not entirely sure what to make of that – we can't allocate memory, for example, and if the attributes are supposed to auto-implement the traits then they would need quite a bit more info to do so. For example the service needs to know all characteristics that are present at compile time to avoid allocations, but macros cannot access implemented traits or any values really.

The reason why I arrived at the approach described in https://github.com/jonas-schievink/rubble/issues/72 is that I don't think it will be very easy (if at all possible) to make procedural macros do the job in all cases here.

jonas-schievink avatar May 03 '20 12:05 jonas-schievink

Your completely correct. I should have reread everything before posting. Let me have a think and see if I can abuse macros/types some more.

tl8roy avatar May 03 '20 22:05 tl8roy