trouble-host 0.7.0

An async Rust BLE host
Documentation
# GATT Callback-Based Architecture Design

## Overview

This document describes the new callback-based GATT architecture for trouble-host, replacing the current storage-based approach.

## Core Principles

1. **No value storage in AttributeTable** - Characteristic values are NOT stored in the attribute table
2. **Async dispatch functions** - Each characteristic has associated async callback functions for read/write operations
3. **Per-connection CCCD state** - Notification/indication subscriptions are maintained per-connection
4. **Lifetime elimination** - Remove `'values` lifetime from `AttributeTable` and `AttributeServer`
5. **Type safety** - Callbacks can be type-safe through generics or trait implementations

## Architecture Components

### 1. Attribute Definition

Attributes now contain:
- UUID
- Handle
- Properties (read, write, notify, indicate)
- **Callback references** instead of data storage

```rust
pub(crate) enum AttributeData {
    Service {
        uuid: Uuid,
    },
    Characteristic {
        props: CharacteristicProps,
        handler: &'static dyn CharacteristicHandler,
    },
    Declaration {
        props: CharacteristicProps,
        handle: u16,
        uuid: Uuid,
    },
    Cccd {
        // CCCD values are stored in CccdTables, not here
        // This is just a placeholder for the CCCD descriptor itself
    },
}
```

### 2. Handler Traits

Async traits for handling GATT operations:

```rust
/// Handler for characteristic read operations
pub trait ReadHandler {
    /// Handle a read request
    /// Returns the data to send back, or an error code
    async fn on_read(&self, connection: &Connection<'_, impl PacketPool>, offset: usize, data: &mut [u8]) -> Result<usize, AttErrorCode>;
}

/// Handler for characteristic write operations
pub trait WriteHandler {
    /// Handle a write request
    async fn on_write(&mut self, connection: &Connection<'_, impl PacketPool>, offset: usize, data: &[u8]) -> Result<(), AttErrorCode>;
}

/// Combined handler trait
pub trait CharacteristicHandler {
    /// Called when characteristic is read
    fn on_read(&self, connection: &Connection<'_, impl PacketPool>, offset: usize, data: &mut [u8]) -> impl Future<Output = Result<usize, AttErrorCode>>;

    /// Called when characteristic is written
    fn on_write(&self, connection: &Connection<'_, impl PacketPool>, offset: usize, data: &[u8]) -> impl Future<Output = Result<(), AttErrorCode>>;
}
```

### 3. Callback Dispatch Flow

```
Client Request (ATT PDU)
AttributeServer::process()
Find attribute by handle
Extract handler from AttributeData
Invoke handler.on_read() or handler.on_write()
    ↓ (async)
Handler executes user logic
Return result
AttributeServer formats ATT response
Send response to client
```

### 4. Notification/Indication Flow

```rust
// User code triggers notification
characteristic.notify(connection, &value).await?;
// Check CCCD state for this connection
if server.should_notify(connection, cccd_handle) {
    // Format and send ATT notification PDU
    send_notification(connection, handle, value).await
}
```

### 5. CCCD State Management

- `CccdTables` remains largely unchanged - stores per-connection subscription state
- CCCD reads: Return current subscription state for the connection
- CCCD writes: Update subscription state for the connection
- `should_notify()` / `should_indicate()`: Query subscription state

### 6. Macro-Generated Code

The `#[gatt_server]` macro generates:

```rust
// Before (storage-based):
#[characteristic(uuid = "...", read, write, notify)]
level: u8,

// After (callback-based):
#[characteristic(uuid = "...", read, write, notify)]
level: CharacteristicHandle<u8>,

// Generated handler implementation:
struct LevelHandler {
    // User can add fields here for state
}

impl CharacteristicHandler for LevelHandler {
    async fn on_read(&self, conn: &Connection, offset: usize, data: &mut [u8]) -> Result<usize, AttErrorCode> {
        // User implements this
        todo!()
    }

    async fn on_write(&self, conn: &Connection, offset: usize, data: &[u8]) -> Result<(), AttErrorCode> {
        // User implements this
        todo!()
    }
}
```

## Key Changes from Current Architecture

### AttributeTable
- **Remove**: `'values` lifetime parameter
- **Remove**: Storage buffers (`&'d mut [u8]`)
- **Remove**: `AttributeData::Data` and `AttributeData::ReadOnlyData`
- **Add**: Callback/handler references in `AttributeData::Characteristic`

### AttributeServer
- **Remove**: `'values` lifetime parameter
- **Change**: `process()` becomes async to await handler callbacks
- **Change**: Read/write handlers invoke callbacks instead of accessing storage
- **Keep**: CCCD management (mostly unchanged)

### GattEvent Processing
- **Change**: Event processing becomes async
- **Change**: `accept()` awaits callback execution
- **Keep**: Event structure (Read/Write/Other) for user interception

## Benefits

1. **No storage overhead** - No need to allocate buffers for characteristic values
2. **Async operations** - Handlers can perform I/O (flash writes, sensor reads, etc.)
3. **Flexible state management** - User controls where/how values are stored
4. **Simplified lifetimes** - Eliminates complex `'values` lifetime
5. **Type safety** - Can use generics for type-safe handlers

## Migration Path

Users need to:
1. Change characteristic fields from values to handles
2. Implement handler traits for each characteristic
3. Remove storage buffer allocations
4. Update read/write logic to use async handlers

## Open Questions

1. Should handlers be trait objects (`&dyn`) or generic parameters?
   - Trait objects: More flexible, slight overhead
   - Generics: Zero-cost, but more complex types

2. Should user code still see `GattEvent`s or should they be hidden?
   - Visible: Allows user to intercept/reject before handler
   - Hidden: Simpler API, handler is mandatory

3. How to handle descriptor reads/writes?
   - Same callback system as characteristics
   - Or special-case common descriptors?

4. Should we provide default handler implementations?
   - For simple in-memory storage
   - For read-only constants