
BLE sotto la lente (ATT e GATT…)
Parte 1, panoramica
È passato un po' di tempo da quando è stata rilasciata la prima specifica per Bluetooth 4.0. Sebbene il tema del BLE sia molto interessante, continua a scoraggiare molti sviluppatori a causa della sua complessità. Negli articoli precedenti, ho trattato principalmente i livelli più bassi, il Link Layer e il Physical Layer. Questo ha permesso di evitare concetti complicati e confusi come il protocollo degli attributi (ATT) e il profilo generale degli attributi (GATT). Tuttavia, non ci sono alternative: per sviluppare dispositivi compatibili è impossibile ignorarli. Oggi mi piacerebbe condividere con voi queste conoscenze. Nella mia articolo, farò riferimento a per principianti dal sito di Nordic. Quindi, iniziamo.
Perché è tutto così complicato?
Dal mio punto di vista, era subito chiaro che la gestione dei dispositivi tramite smartphone è un tema molto promettente e di lungo termine. Perciò è stata decisa una strutturazione immediata e massima. I produttori di vari gadget non dovrebbero inventare protocolli propri che in seguito risulterebbero incompatibili. Da qui la complessità. Già nella prima fase, nel protocollo BLE si è cercato di includere tutto ciò che era possibile. Non importa se sarà utile in seguito o meno. Inoltre, è stata prevista la possibilità di ampliare la lista dei dispositivi in futuro.
Diamo un'occhiata all'immagine che illustra lo schema del protocollo BLE. Esso è composto da diversi strati. Il primo strato, il livello fisico (PHY), è responsabile del canale radio del dispositivo. Il Link Layer (LL) contiene tutta la sequenza di byte nel messaggio trasmesso. Nelle articoli precedenti, abbiamo studiato proprio questo. L'Host Controller Interface (HCI) è il protocollo di scambio tra gli strati o i chip BLE, nel caso in cui Controller e Host siano implementati su chip differenti. La creazione dei pacchetti, la suddivisione in frame, il controllo degli errori e l'assemblaggio dei pacchetti sono gestiti dal Logical Link Control and Adaptation Protocol (L2CAP). La sicurezza dei pacchetti è garantita dal Security Manager Protocol (SMP). Il Generic Access Profile (GAP) è responsabile dello scambio iniziale di dati tra dispositivi, per determinare «Chi è chi». Esso comprende anche la scansione e la pubblicità. In questo articolo mi soffermerò sulle due parti rimanenti del protocollo: GATT e ATT. GATT è un'estensione di ATT, pertanto sono strettamente interrelati.

Per semplificare la narrazione, vorrei usare un'analogia. L'ho sentita da qualche parte e vorrei sostenerla. Immaginate un dispositivo BLE come una libreria con diverse mensole. Ogni mensola rappresenta un tema diverso. Ad esempio, abbiamo mensole di fantascienza, matematica, enciclopedie. Su ogni mensola ci sono libri relativi al tema specifico. E in alcuni libri ci sono anche segnalibri con annotazioni. Inoltre, abbiamo un piccolo catalogo cartaceo di tutti i libri. Se ricordate le biblioteche scolastiche — è un cassetto stretto con schede cartacee. Con questa analogia, la libreria rappresenta il profilo del nostro dispositivo. Le mensole sono i servizi, i libri sono le caratteristiche e il catalogo è la tabella degli attributi. I segnalibri nei libri sono i descrittori, di cui parlerò più avanti in modo più dettagliato.
Chi sviluppa dispositivi sa che in molti progetti ci sono sezioni di codice simili. Infatti, molti dispositivi hanno funzionalità comparabili. Ad esempio, se i dispositivi funzionano a batteria, il problema della ricarica e del monitoraggio del loro livello sarà identico. Lo stesso vale per i sensori. In sostanza, l'approccio orientato agli oggetti nella programmazione «offre la possibilità di creare oggetti che legano insieme proprietà e comportamenti in un'unione autonoma, che può poi essere riutilizzata multiple volte». A mio avviso, nel BLE è stata fatta un tentativo simile. Il gruppo Bluetooth Special Interest Group (SIG) ha sviluppato i profili. I dispositivi di diversi produttori, che hanno profili identici, devono funzionare senza difficoltà tra loro. I profili, a loro volta, sono composti da servizi e i servizi da caratteristiche, arricchite da descrittori. In generale, questo può apparire così:

Ad esempio, consideriamo lo schema del profilo di un monitor della frequenza cardiaca (braccialetto fitness). Esso è composto da due servizi e diverse caratteristiche. Da questo schema, la gerarchia del profilo è subito chiara. La caratteristica del punto di controllo azzera il conteggio totale delle calorie.
1. Il servizio della frequenza cardiaca include tre caratteristiche (0x180D):
a) Caratteristica obbligatoria della frequenza cardiaca (0x2A37)
b) Caratteristica opzionale della posizione del sensore corporeo (0x2A38)
c) Caratteristica condizionale del punto di controllo della frequenza cardiaca (0x2A39)
2. Servizio di gestione della batteria (0x180F):
a) Caratteristica obbligatoria del livello di carica della batteria (0x2A19)
UUID
Affinché possiamo fare riferimento in modo univoco agli elementi del profilo (servizi, caratteristiche e descrittori), è necessario numerarli in qualche modo. A questo scopo si introduce il concetto di Identificatore Unico Universale (UUID). Tra parentesi in ogni riga è indicato proprio l'UUID. C'è però una particolarità. Per l'UUID si è deciso di utilizzare un codice lungo 16 e 128 bit. Perché, vi chiederete? Nel protocollo BLE tutto è subordinato al risparmio energetico. Pertanto, una dimensione di 16 bit è del tutto ragionevole. È improbabile che nel prossimo futuro vengano creati più di 65.000 servizi e caratteristiche unici. Fino ad ora, tutto ciò che era possibile è già stato conteggiato (ricordate da dove viene — "lui ha contato anche voi" :-)) Elementi numerati , , e potete visualizzarli tramite i link.
Tuttavia, credo che tutti ricordino la storia dei 4 byte degli indirizzi IP su Internet. Inizialmente si pensava che fossero sufficienti, ma ora non riusciamo a passare agli indirizzi a 6 byte. Per evitare di ripetere questo errore e dare libero sfogo alle mani impertinenti dei fai-da-te, SIG ha deciso di introdurre anche UUID a 128 bit. Personalmente, mi ricorda lo spettro non autorizzato dei 433 MHz, che è stato messo nelle mani di vari inventori nei canali radio. Nel nostro caso, è stato messo a disposizione un identificatore a 128 bit per servizi e caratteristiche. Ciò significa che possiamo utilizzare praticamente qualsiasi valore a 128 bit per i nostri servizi e dispositivi. La probabilità di inventare un UUID identico è comunque prossima allo zero.
In realtà, gli UUID brevi a 16 bit hanno una loro estensione fino a un valore a 128 bit. Nelle specifiche, questa estensione è chiamata Bluetooth Base UUID ed è rappresentata da 00000000-0000-1000-8000-00805F9B34FB. Se, ad esempio, l'UUID a 16 bit di un attributo ha un valore di 0x1234, il corrispondente UUID a 128 bit avrà il valore 00001234-0000-1000-8000-00805F9B34FB. Viene persino fornita la formula corrispondente:
128_bit_value = 16_bit_value * 2^96 + Bluetooth_Base_UUID
Non so da dove derivi questo numero magico. Se qualcuno dei lettori lo sa, per favore scrivetelo nei commenti (L'utente con il nickname Sinopteek lo ha già fatto. Vedi i commenti). Per quanto riguarda la creazione di UUID a 128 bit, puoi sfruttare un , che farà questo per te.
ATT e GATT...
Ora inizia la parte più interessante. Ricordo che ATT si basa sulla relazione client-server. Attualmente stiamo esaminando il dispositivo del server. Esso contiene informazioni come i valori dei sensori, lo stato dell'interruttore della luce, i dati sulla posizione, ecc. Ora che tutti i "partecipanti al nostro parata" sono numerati, dobbiamo in qualche modo posizionarli nella memoria del dispositivo. Per fare questo, li inseriamo in una tabella chiamata tabella degli attributi. Ricordate bene questo. È il cuore del BLE. Sarà lui il nostro argomento di discussione in seguito. Ora chiameremo ogni riga un attributo. Questa tabella si trova in profondità nello stack e, di solito, non abbiamo accesso diretto a essa. La inizializziamo e la consultiamo, ma ciò che avviene al suo interno è nascosto a noi.
Consideriamo l'immagine dalla specifica, ma prima di tutto voglio sottolineare una confusione comune nei termini, in particolare nei descrittori. Il ruolo del descrittore è quello di integrare la descrizione di una caratteristica. Quando è necessario ampliare le sue funzionalità, si utilizzano i descrittori. Essi sono anche attributi e, insieme ai servizi e alle caratteristiche, si trovano nella tabella degli attributi. Li esamineremo in dettaglio nella seconda parte dell'articolo. Tuttavia, a volte i descrittori vengono confusi con il numero di riga nella tabella degli attributi. È importante tenerne conto. Per evitare confusioni, utilizzeremo per tali scopi il termine "indicatore dell'attributo".

Dunque, l'attributo è un valore discreto che ha le seguenti proprietà associate:
1. Indicatore dell'attributo (Attribute Handle) — è l'indice della tabella corrispondente all'attributo
2. Tipo di attributo (Attribute Type) — è un UUID che descrive il suo tipo
3. Valore dell'attributo (Attribute Value) — sono i dati indicizzati dall'indicatore dell'attributo
4. Permessi degli attributi (Attribute Permissions) — è la parte dell'attributo, i permessi che non possono essere letti o scritti utilizzando il protocollo degli attributi
Come interpretare tutto ciò? Un puntatore dell'attributo è, in un certo senso, il suo numero nella nostra tabella.
Permette al cliente di fare riferimento all'attributo nelle richieste di lettura o scrittura. Possiamo numerare le nostre righe (attributi) da 0x0001 a 0xFFFF. Nella nostra analogia con la libreria, questo è il numero della scheda nel catalogo cartaceo. Analogamente, come nel catalogo della biblioteca, le schede sono disposte in ordine crescente di numero. Il numero di ogni riga successiva deve essere maggiore di quello precedente. Come in biblioteca, a volte alcune schede si perdono, così anche noi possiamo avere spazi nella numerazione delle righe. Questo è consentito. L'importante è che vadano in ordine crescente.
Il tipo di attributo determina cosa rappresenta questo attributo. Analogamente al linguaggio C,
dove ci sono variabili booleani, numeriche e stringhe, così anche qui. Dal tipo di attributo capiamo
con cosa abbiamo a che fare e come dobbiamo procedere con questo attributo. Di seguito esamineremo alcuni tipi specifici di attributi. Ad esempio, "dichiarazione di servizio" (0x2800), "dichiarazione delle caratteristiche" (0x2803), "dichiarazione del descrittore" (0x2902).
Il valore di un attributo è essenzialmente il suo valore, scusate la tautologia. Se il tipo di attributo è una stringa, il valore dell'attributo può essere ad esempio uno slogan come «Hello World !!!». Se il tipo di attributo è una «dichiarazione del servizio», allora il suo valore corrisponde al servizio stesso. A volte, può fornire informazioni su dove trovare altri attributi e le loro proprietà.
Le autorizzazioni degli attributi consentono al server di comprendere se l'accesso in lettura o scrittura è consentito.
Si prega di notare che queste autorizzazioni si applicano solo al valore dell'attributo, e non al puntatore, al tipo e al campo delle autorizzazioni stesse. Cioè, se la scrittura dell'attributo è consentita, possiamo cambiare ad esempio la stringa «Hello World !!!» in «Good morning». Ma non possiamo vietare la scrittura di una nuova stringa o cambiare il tipo di attributo designando la stringa come «dichiarazione del servizio». Quando un cliente si rivolge al server, richiede i suoi attributi. Questo consente al cliente di capire cosa può fornire il server. Anche se non è necessario leggere e registrare i valori.
Come appare
Il concetto di GATT consiste nel raggruppare gli attributi in una tabella degli attributi in un ordine molto specifico e logico. Esaminiamo più da vicino il profilo della frequenza cardiaca riportato di seguito. La colonna più a sinistra di questa tabella non è obbligatoria. Essa ci descrive semplicemente di cosa tratta questa riga (attributo). Tutte le altre colonne ci sono già familiari.

Nella parte superiore di ogni gruppo abbiamo sempre l'attributo di dichiarazione del servizio. Il suo tipo è sempre uguale a 0x2800, e il puntatore dipende da quanti attributi sono già presenti nella tabella. Le sue autorizzazioni sono sempre disponibili solo in lettura, senza alcuna verifica di autenticità o autorizzazione. Parleremo di questi concetti più avanti. Il valore è un altro UUID, che definisce di che servizio si tratta. Nella Tabella, il valore è uguale a 0x180D, che è definito dal Bluetooth SIG come servizio della frequenza cardiaca.
Dopo l'annuncio del servizio, segue l'annuncio della caratteristica. Forma è simile a quella dell'annuncio del servizio. Il suo UUID ha sempre il valore 0x2803, e le autorizzazioni sono sempre disponibili solo in lettura senza alcuna verifica di autenticità o autorizzazione. Esaminiamo il campo Valore dell'Attributo, che include alcuni dati. Esso contiene sempre un puntatore, un UUID e un insieme di proprietà. Questi tre elementi descrivono l'annuncio successivo del valore della caratteristica. Il puntatore indica naturalmente il luogo dell'annuncio del valore della caratteristica nella tabella degli attributi. L'UUID descrive che tipo di informazione o valore possiamo aspettarci. Ad esempio, un valore di temperatura, lo stato di un interruttore della luce o un altro valore arbitrario. Infine, le proprietà descrivono come è possibile interagire con il valore caratteristico.
Qui ci attende un altro ostacolo. Esso è legato alle autorizzazioni degli attributi e alle proprietà delle caratteristiche. Esaminiamo l'immagine delle proprietà del campo bittale dalla specifica.

Come puoi vedere, ci sono anche campi che permettono lettura e scrittura. Potresti chiederti perché abbiamo permessi di lettura/scrittura per l'attributo e la proprietà
della caratteristica del valore? Non dovrebbero forse essere sempre gli stessi? Il fatto è che le proprietà per il valore della caratteristica sono, in realtà, solo raccomandazioni per il cliente, utilizzate nei livelli GATT e applicativi. Sono solo indicazioni su cosa il cliente può aspettarsi dall'attributo di dichiarazione della caratteristica. Approfondiamo il tema. Quali tipi di permessi esistono per l'attributo?
1. Permessi di accesso:
— lettura
— scrittura
— lettura e scrittura
2. Permesso di autenticazione:
— autenticazione richiesta
— autenticazione non richiesta
3. Permesso di autorizzazione:
— autorizzazione richiesta
— autorizzazione non richiesta
La principale differenza tra i permessi degli attributi e le proprietà delle caratteristiche è che i primi si riferiscono ai server, mentre le seconde ai client. Un server può avere il permesso di leggere il valore di una caratteristica, ma ci potrebbe essere una richiesta di autenticazione o autorizzazione. Pertanto, quando un client richiede le proprietà di una caratteristica, scopriremo che la lettura è consentita. Tuttavia, al tentativo di lettura, otterremo un errore. Quindi si può affermare con sicurezza che i permessi hanno priorità rispetto alle proprietà. Non possiamo conoscere quali permessi ha un attributo dal lato del client.
Descrittore
Torniamo alla nostra tabella. Dopo aver dichiarato il valore di una caratteristica, sono possibili le seguenti dichiarazioni degli attributi:
1. Nuova dichiarazione della caratteristica (ci possono essere molte caratteristiche nel servizio)
2. Nuova dichiarazione del servizio (nella tabella possono essercene molte)
3. Dichiarazione del descrittore
Nel caso della caratteristica di misurazione della frequenza cardiaca, nella nostra tabella, l'annuncio del valore della caratteristica è accompagnato dall'annuncio del descrittore. Il descrittore è un attributo con informazioni aggiuntive sulla caratteristica. Esistono diversi tipi di descrittori. Di questo parleremo in dettaglio nella seconda parte di questo articolo. Ora ci occuperemo solo del descrittore di configurazione delle caratteristiche del cliente (Client Characteristic Configuration Descriptor - CCCD). Ha un UUID pari a 0x2902. Con questo descrittore, il cliente ha la possibilità di attivare sul server la visualizzazione o la notifica. La differenza tra le due è piccola, ma c'è. La notifica non richiede conferma di ricezione da parte del cliente. La visualizzazione, invece, lo richiede, anche se avviene a livello GATT, senza raggiungere il livello dell'applicazione. Perché così, vi chiederete? Sfortunatamente, non lo so. Posso solo dire che gli esperti di Nordic consigliano di utilizzare la notifica. Tanto più che il controllo dell'integrità del pacchetto (utilizzando CRC) avviene in entrambi i casi.
Conclusione
Alla fine dell'articolo, vorrei dire ciò che segue. L'ultima tabella è un po' confusa. Tuttavia, mi sono concentrato su di essa perché è presentata in , su cui mi baso. Nella seconda parte del mio articolo, intendo approfondire le specifiche di BlueTooth 4.0. Qui ci attendono diagrammi e illustrazioni più accurati. Nella terza parte, vorrei analizzare il log ottenuto utilizzando il programma Wireshark da uno dei gadget e vedere "dal vivo" tutta la teoria che stiamo studiando.
Un membro del Gruppo Compagnie
Vladimir Pechersky
Fonte: habr.com
