
BLE sob a lupa (ATTs e GATTs…)
Parte 1, visão geral
Já se passou um bom tempo desde que a primeira especificação do Bluetooth 4.0 foi publicada. Embora o tema BLE seja muito interessante, ele ainda afasta muitos desenvolvedores, devido à sua complexidade. Em meus artigos anteriores, eu analisei principalmente os níveis mais baixos: o Link Layer e o Physical Layer. Isso me permitiu evitar conceitos complicados e confusos como o protocolo de atributos (ATT) e o perfil de atributos geral (GATT). No entanto, não há como fugir: sem entendê-los, é impossível desenvolver dispositivos compatíveis. Hoje, gostaria de compartilhar esses conhecimentos com vocês. Em meu artigo, basearei-me no para iniciantes do site da Nordic. Então, vamos começar.
Por que tudo é tão complicado?
Na minha opinião, estava claro desde o início que controlar dispositivos através de smartphones era um tema muito promissor e de longo prazo. Portanto, decidiu-se estruturar tudo desde o início e da melhor maneira possível. Para que os fabricantes de vários gadgets não criassem seus próprios protocolos, que posteriormente seriam incompatíveis. Daí vem a complexidade. Já na primeira etapa, tentou-se incluir tudo o que fosse possível no protocolo BLE, independentemente de sua real utilidade no futuro. Além disso, foi prevista a possibilidade de ampliar a lista de dispositivos no futuro.
Vamos dar uma olhada na imagem que mostra o esquema do protocolo BLE. Ele é composto por várias camadas. A camada mais baixa, a camada física (PHY), é responsável pelo canal de rádio do dispositivo. O Link Layer (LL) contém toda a sequência de bytes na mensagem transmitida. Em artigos anteriores, estudamos especificamente essa camada. O Host Controller Interface (HCI) é o protocolo de comunicação entre camadas ou chipsets BLE, caso o Controller e o Host sejam implementados em chips diferentes. O Logical Link Control and Adaptation Protocol (L2CAP) é responsável pela formação de pacotes, divisão em quadros, controle de erros e montagem de pacotes. O Security Manager Protocol (SMP) lida com a criptografia dos pacotes. O perfil de acesso geral (GAP) é responsável pela troca inicial de dados entre dispositivos, para determinar ‘Quem é quem’. Isso também abrange scanning e advertising. Neste artigo, vou me concentrar nas duas partes restantes do protocolo — GATT e ATT. O GATT é um complemento do ATT, portanto, eles estão fortemente entrelaçados.

Para simplificar a narrativa, gostaria de recorrer a uma analogia. Eu a ouvi em algum lugar e gostaria de apoiá-la. Imagine um dispositivo BLE como uma estante de livros com várias prateleiras. Cada prateleira representa um tema distinto. Por exemplo, temos prateleiras de ficção, matemática, enciclopédias. Em cada prateleira, há livros sobre o tema indicado. E em alguns livros, há até marcadores de papel com anotações. Além disso, temos um pequeno catálogo em papel de todos os livros. Se você se lembra das bibliotecas escolares — é uma gaveta estreita com cartões de papel. Dentro dessa analogia, a estante é o perfil do nosso dispositivo. As prateleiras são os serviços, os livros são as características e o catálogo é a tabela de atributos. Os marcadores nos livros são os descritores, sobre os quais falarei mais detalhadamente depois.
Todos que desenvolveram dispositivos sabem que em muitos projetos existem trechos de código semelhantes. O fato é que muitos dispositivos têm funcionalidades semelhantes. Por exemplo, se os dispositivos funcionam com baterias, então o problema de carregamento e controle do nível delas será o mesmo. O mesmo se aplica aos sensores. Na verdade, a abordagem orientada a objetos na programação “oferece a capacidade de criar objetos que conectam propriedades e comportamentos em uma aliança autônoma que pode ser reutilizada várias vezes”. Na minha opinião, no BLE foi feita uma tentativa de abordagem semelhante. Um grupo da Bluetooth Special Interest Group (SIG) desenvolveu perfis. Dispositivos de diferentes fabricantes que têm perfis iguais devem funcionar sem dificuldades uns com os outros. Os perfis, por sua vez, consistem em serviços, e os serviços são compostos por características, complementadas por descritores. De forma geral, isso pode ser representado assim:

Como exemplo, consideremos o esquema do perfil monitor de frequência cardíaca (pulseira fitness). Ele consiste em dois serviços e várias características. A partir disso, a hierarquia do perfil se torna imediatamente clara. A característica do ponto de controle zera o total de gastos calóricos.
1. O serviço de frequência cardíaca inclui três características (0x180D):
a) Característica obrigatória de frequência cardíaca (0x2A37)
b) Característica opcional de posição do sensor corporal (0x2A38)
c) Característica condicional de ponto de controle de frequência cardíaca (0x2A39)
2. Serviço de gerenciamento de bateria (0x180F):
a) Característica obrigatória do nível de carga da bateria (0x2A19)
UUID
Para que possamos nos referir de forma única aos elementos do perfil (serviços, características e descritores), precisamos numerá-los de alguma forma. Para isso, é introduzido o conceito de Identificador Único Universal (UUID) ou Universally Unique Identifier. O UUID está indicado entre parênteses em cada linha. E aqui há uma peculiaridade. Para o UUID, foi decidido usar códigos de 16 e 128 bits de comprimento. Por que, você pergunta? No protocolo BLE, tudo é regido pela economia de energia. Portanto, uma dimensão de 16 bits é bastante razoável. É improvável que no futuro próximo sejam criados mais de 65 mil serviços e características únicos. Até agora, tudo o que podíamos, já foi contado (lembre-se de onde é isso — “ele também contou você” :-)) Elementos numerados , , e você pode visualizar através dos links.
No entanto, eu acho que todos lembram a história dos 4 bytes de endereço IP na internet. No começo se pensou que seria suficiente, mas agora não conseguimos fazer a transição para endereços de 6 bytes. Para não repetir esse erro e dar espaço a mãos arteiras, a SIG decidiu imediatamente introduzir também UUIDs de 128 bits. Isso me lembra a faixa de 433MHz não licenciada, que foi deixada livre para todos os tipos de inventores de rádio. No nosso caso, o identificador de serviços e características de 128 bits foi entregue a esta liberdade. Isso significa que podemos usar praticamente qualquer valor de 128 bits para nossos serviços e dispositivos. A probabilidade de criar um UUID idêntico tende a zero.
Na verdade, os UUIDs de 16 bits curtos têm sua extensão para o valor de 128 bits. Na especificação, essa extensão é chamada de Bluetooth Base UUID e tem o valor 00000000-0000-1000-8000-00805F9B34FB. Se, por exemplo, o UUID de atributo de 16 bits tem o valor 0x1234, então o UUID equivalente de 128 bits terá o valor 00001234-0000-1000-8000-00805F9B34FB. E também é fornecida a fórmula correspondente:
128_bit_value = 16_bit_value * 2^96 + Bluetooth_Base_UUID
De onde veio esse número mágico, não sei. Se alguém entre os leitores souber — por favor, escreva nos comentários (Um usuário com o apelido Sinopteek já fez isso. Veja nos comentários). Quanto a criar UUIDs de 128 bits, na verdade, é possível utilizar um , que fará isso por você.
ATTs GATTs…
Na verdade, é aqui que começa a parte mais interessante. Lembro que o ATT se baseia na relação cliente-servidor. Agora estamos considerando a estrutura do servidor. Ele contém informações como valores de sensores, o estado do interruptor de luz, dados de localização, etc. Agora que todos os "participantes do nosso desfile" estão numerados, precisamos de alguma forma organizá-los na memória do dispositivo. Para isso, colocamos tudo em uma tabela chamada tabela de atributos. Lembre-se bem disso. Este é o coração do BLE. É isso que iremos considerar a seguir. Agora, cada linha será chamada de atributo. Esta tabela está no fundo da pilha e, geralmente, não temos acesso direto a ela. Nós a inicializamos e a acessamos, mas o que acontece internamente é um mistério para nós.
Vamos considerar a imagem da especificação, mas antes disso, quero chamar a atenção para a confusão frequente nos termos, especialmente em relação aos descritores. O papel do descritor é complementar a descrição de uma característica. Quando é necessário expandir suas capacidades, é quando se aplicam os descritores. Eles também são atributos e, assim como os serviços e características, estão localizados na tabela de atributos. Vamos detalhar isso na segunda parte do artigo. Entretanto, às vezes, os descritores são chamados pelo número da linha na tabela de atributos. É importante ter isso em mente. Para evitar confusões, usaremos o termo "ponteiro do atributo" para esses fins.

Então, um atributo é um valor discreto que possui as seguintes propriedades associadas a ele:
1. Ponteiro do atributo (Attribute Handle) — é o índice na tabela correspondente ao atributo
2. Tipo de atributo (Attribute Type) — é o UUID que descreve seu tipo
3. Valor do atributo (Attribute Value) — são os dados indexados pelo ponteiro do atributo
4. Permissões do atributo (Attribute Permissions) — é a parte do atributo que contém permissões que não podem ser lidas ou gravadas usando o protocolo de atributos
Como entender tudo isso? O ponteiro do atributo é, por assim dizer, seu número em nossa tabela.
Ele permite que o cliente se refira a um atributo em solicitações de leitura ou gravação. Podemos numerar nossas linhas (atributos) de 0x0001 a 0xFFFF. Em nossa associação com uma estante de livros, isso se refere ao número do cartão no catálogo físico. Assim como em um catálogo de biblioteca, os cartões são dispostos em ordem crescente de número. O número de cada linha subsequente deve ser maior que o anterior. Assim como na biblioteca, às vezes alguns cartões se perdem, e em nossa numeração de linhas também podem haver lacunas. Isso é permitido. O importante é que eles estejam em ordem crescente.
O tipo de atributo determina o que esse atributo representa. Assim como na linguagem C,
onde existem variáveis booleanas, numéricas e strings, aqui também. Pelo tipo de atributo, podemos entender
com o que estamos lidando e como devemos trabalhar com esse atributo. Abaixo, vamos considerar alguns tipos específicos de atributos. Por exemplo, 'declaração de serviço' (0x2800), 'declaração de características' (0x2803), 'declaração de descritor' (0x2902).
O valor do atributo é, de fato, seu próprio valor, desculpe pela tautologia. Se o tipo do atributo é uma string, o valor do atributo pode ser, por exemplo, o slogan 'Hello World !!!'. Se o tipo do atributo é 'declaração de serviço', então seu valor é o próprio serviço. Às vezes, isso é uma informação sobre onde encontrar outros atributos e suas propriedades.
As permissões dos atributos permitem que o servidor entenda se o acesso para leitura ou gravação está permitido.
Observe que essas permissões se aplicam apenas ao valor do atributo, e não ao ponteiro, tipo e ao próprio campo de permissões. Ou seja, se a gravação do atributo estiver permitida, podemos mudar, por exemplo, a string 'Hello World !!!' para 'Good morning'. Mas não podemos proibir a gravação de uma nova string ou mudar o tipo do atributo e designar a string como 'declaração de serviço'. Ao se conectar ao servidor, o cliente solicita seus atributos. Isso permite que o cliente saiba o que o servidor pode oferecer. Porém, não é obrigatório ler e gravar valores.
Como isso aparece
A concepção do GATT é agrupar atributos em uma tabela de atributos de maneira muito específica e lógica. Vamos examinar mais de perto o perfil de frequência cardíaca mostrado abaixo. A coluna mais à esquerda desta tabela é opcional. Ela simplesmente descreve o que é essa linha (atributo). Todas as outras colunas já nos são familiares.

Na parte superior de cada grupo, sempre temos o atributo de declaração do serviço. Seu tipo é sempre igual a 0x2800, e o ponteiro depende de quantos atributos já estão presentes na tabela. Suas permissões são sempre apenas para leitura, sem qualquer verificação de autenticidade ou autorização. Vamos falar sobre esses conceitos mais tarde. O valor é mais um UUID, que define qual é o serviço. Na tabela, o valor é igual a 0x180D, que é definido pelo Bluetooth SIG como o serviço de frequência cardíaca.
Após a declaração do serviço, segue a declaração da característica. Sua forma é semelhante à declaração do serviço. Seu UUID sempre tem o valor 0x2803, e as permissões também estão sempre disponíveis apenas para leitura, sem qualquer verificação de autenticidade ou autorização. Vamos olhar o campo Attribute Value, que inclui alguns dados. Ele sempre contém um ponteiro, um UUID e um conjunto de propriedades. Estes três elementos descrevem a subsequente declaração do valor da característica. O ponteiro naturalmente indica a localização da declaração do valor da característica na tabela de atributos. O UUID descreve que tipo de informação ou valor podemos esperar. Por exemplo, um valor de temperatura, o estado de um interruptor de luz ou qualquer outro valor arbitrário. E finalmente, as propriedades que descrevem como interagir com o valor da característica.
Aqui nos deparamos com mais uma armadilha. Ela está relacionada às permissões dos atributos e às propriedades das características. Vamos olhar a imagem das propriedades do campo de bits da especificação.

Como podem ver, também existem campos que oferecem possibilidades de leitura e gravação. Você pode se perguntar por que temos permissões de leitura/gravação para o atributo e a propriedade.
Leitura/escrita para o valor da característica? Eles não devem ser sempre iguais? O fato é que as propriedades para o valor da característica são, na verdade, apenas recomendações para o cliente, utilizadas nas camadas GATT e aplicadas. São apenas dicas sobre o que o cliente pode esperar do atributo da declaração de característica. Vamos explorar isso mais a fundo. Que tipos de permissões existem para o atributo?
1. Permissões de acesso:
— leitura
— escrita
— leitura e escrita
2. Permissão de autenticação:
— autenticação necessária
— autenticação não necessária
3. Permissão de autorização:
— autorização necessária
— autorização não necessária
A principal diferença entre as permissões dos atributos e as propriedades das características é que as primeiras se referem aos servidores, enquanto as segundas se referem aos clientes. Um servidor pode ter permitido a leitura do valor da característica, mas pode haver uma exigência de autenticação ou autorização. Portanto, ao solicitar as propriedades da característica pelo cliente, teremos que a leitura é permitida. Mas ao tentar a leitura, receberemos um erro. Assim, pode-se afirmar com certeza que as permissões têm prioridade sobre as propriedades. Nós, do lado do cliente, não podemos obter informações sobre quais permissões o atributo possui.
Descritor
Vamos voltar à nossa tabela. Após a declaração do valor da característica, são possíveis as seguintes declarações de atributos:
1. Nova declaração de característica (pode haver muitas características no serviço)
2. Nova declaração de serviço (pode haver muitas na tabela)
3. Declaração de descritor
No caso da característica de medição da frequência cardíaca, em nossa tabela, a declaração do valor da característica é acompanhada pela declaração do descritor. O descritor é um atributo com informações adicionais sobre a característica. Existem vários tipos de descritores. Sobre eles, falaremos em detalhes na segunda parte deste artigo. Agora, vamos nos concentrar apenas no descritor de configuração de características do cliente (Client Characteristic Configuration Descriptor - CCCD). Ele tem um UUID igual a 0x2902. Com este descritor, o cliente tem a capacidade de ativar a indicação ou notificação no servidor. A diferença entre eles é pequena, mas ainda assim existe. A notificação não requer confirmação de recebimento por parte do cliente. A indicação requer isso, embora aconteça no nível GATT, sem chegar ao nível do aplicativo. Por que isso, você pode perguntar? Infelizmente, isso é algo que não sei. Apenas direi que os especialistas da Nordic recomendam o uso da notificação. Especialmente porque a verificação de integridade do pacote (usando CRC) ocorre em ambos os casos.
Conclusão
No final do artigo, eu gostaria de falar sobre o seguinte. A última tabela é um pouco confusa. Contudo, eu a inclui porque está apresentada em , na qual me baseio. Na segunda parte do meu artigo, pretendo me aprofundar nas especificações do BlueTooth 4.0. Lá nos esperam esquemas e diagramas mais precisos. Na terceira parte, quero analisar o log obtido através do programa Wireshark de um dos gadgets e ver "ao vivo" toda a teoria que estamos estudando.
Funcionário do Grupo de Empresas
Vladimir Pechersky
Fonte: habr.com
