Все наверное знают, что Bluetooth очень сложный протокол.
И хотошо бы из него выкинуть все ненужное и оставить только нужное, но к сожалиению, он развивался итеративно и поддерживал обратную совместимость...
90% фич вам будут явно не нужны. Остановимся на одной из важных фич.
Это процесс "спаривания" с телефоном.
Нужно наверное немного рассказать о терменологии...
В блютус есть 4 "роли" устройства:
PERIPHERAL или в простонародии Slave (этим будет BlueNRG-1)
BROADCASTER (только передает, например Bluetooth beacon)
CENTRAL или в простонародии Master (Android v7.0)
OBSERVER (только принимает)
Так какие есть возможности при проектировании устройства, связанные со "спариванием"?
В примерах я буду пользоваться примерами кода для BLueNRG-1, однако, значения используемые в этих вызовах прачтиески 1 к 1 уходят в пакеты GAP протокола.
Поэтому можно заменить на аналогичные вызовы другого чипа. Важен сам принцип.
Для начала возьмем пример из BlueNRG1 DK 2.5.0:
\Project\BLE_Examples\BLE_Chat
Нужно переключить конфигурацию на "Server"
И настроить IDE, о чем было в других постах.
Если в таком виде пример запустить, то спариться с андроидом у вас не получится.
В примере отсутствуют вызовы:
aci_gap_set_io_capability()
aci_gap_set_authentication_requirement()
Так же отсутствуют Event callbacks:
void aci_gap_pass_key_req_event(uint16_t Connection_Handle)
void aci_gap_pairing_complete_event(uint16_t Connection_Handle,
uint8_t Status,
uint8_t Reason)
void aci_gap_numeric_comparison_value_event(uint16_t Connection_Handle,
uint32_t Numeric_Value)
void aci_gap_keypress_notification_event(uint16_t Connection_Handle,
uint8_t Notification_Type)
Все эти вызовы, а так-же несколько других, и определяют как будет происходить процесс спаривания с телефоном.
На данный момент есть 4 принципиально различных способа спаривания с телефоном:
JustWorks
Тут нет ни авторизации ни защиты от перехвата, безопасность на нуле.
Нажимаем на кнопку "спариться" в телефоне и сразу спариваемся. Пин даже не запрашивается.
Как такой вариант получить?
Очень просто:
aci_gap_set_io_capability(IO_CAP_NO_INPUT_NO_OUTPUT);
aci_gap_set_authentication_requirement(0x00 /*No-bonding mode*/, 0x00/*MITM protection not required*/...)
Тут очень важен второй аргумент MITM (Man-In-The-Middle) т.е мы согласны на то что нас могут подслушать и вклинится в разговор и т.д.
Интересно, что до сих пор такую "авторизацию" поддерживает Андроид.
Fixed PIN
Этот вариант наверное самый распространенный среди производителей девайсов.
У девайса есть фиксированный пин, он выдается на бумаге или написам на самом устройстве.
aci_gap_set_io_capability(IO_CAP_DISPLAY_ONLY);
aci_gap_set_authentication_requirement(0x01 /*bonding mode*/, 0x01 /*MITM protection required*/..., 0x00 /*Use_Fixed_Pin*/, 123456)
а)Нужно обязательно сказать что у нас есть возможность вывода информации, даже если такой возможности у вас нету, вы ведь вывели пин на бумагу...
б)Bonding mode тут стоит в единицу, чтобы BlueNRG1 запомнило долговременный ключ во флеш памяти, тогда долго можно не спейриваться снова (кто-то знает как долго?).
в) MITM теперь включен и мы получаем компромис между безопасностью и юзабилити.
На телефоне в таком режиме будет показываться popup где запросят ввести пин код. Интересно, что телефон позволяет вводить и буквы тоже, однако в BlueNRG-1 API используется Unsigned int до 999999. Кто-то знает как это работает с другими девайсами? Очевидно что в протоколе есть какое-то расширение для этого..
Android Generated Passphrase
Похоже на предыдущий вариант, но код генерируется мобильным устройством, и у вас должна быть возможность ввести такой же на BT устройстве.
Очевидно что в реальном устройстве для этого понадобится клавиатура..
Что очень неприятно. В примере можно воспользоваться UART и дописать получение пина в функции Process_InputData
aci_gap_set_io_capability(IO_CAP_KEYBOARD_ONLY);
aci_gap_set_authentication_requirement(0x01 /*bonding mode*/, 0x01 /*MITM protection required*/..., 0x01 /*Use Dynamic Pin*/, 0)
Теперь все немного сложнее.
Андроид покажет пользователю диалог, в котором он сгенерировал пин.
В этот момент на устройстве вас вызовут в
void aci_gap_pass_key_req_event(uint16_t Connection_Handle)
Далее, например по UART, вам нужно получить показаный на телефоне пин код.
И вызвать в течении 15 секунд функцию:
aci_gap_pass_key_resp(Connection_Handle, 876543);
Где 876543 код, который показал Андроид.
BlueNRG1 Generated Passphrase
Похоже на предыдущий вариант, но код генерируется теперь BlueNRG1, и его нужно вводить в андроиде.
aci_gap_set_io_capability(IO_CAP_DISPLAY_ONLY);
aci_gap_set_authentication_requirement(0x01 /*bonding mode*/, 0x01 /*MITM protection required*/..., 0x01 /*Use Dynamic Pin*/, 0)
В этом случае вас тоже вызовут в процессе обмена ключами: aci_gap_pass_key_req_event
Но теперь вам нужно уже самому сгенерировать ключ из 6 цифр и вызвать с ним функцию aci_gap_pass_key_resp, практически сразу.
BLE v4.2 IO_CAP_DISPLAY_YES_NO
Это один из самых продвинутых способов спаривания на сегодняшний день.
Для такого способа, на устройстве должен быть дисплей, или какой-то способ передать цифровую информацию, например миграние светодиода, или односегментный индикатор.
Если вообще без дисплея, тогда мы получаем разновидность JUSTWORKS, когда спаривание разрешено например по нажатию кнопки на устройстве.
Ну и самое главное это две кнопки (как утверждает стандарт) но можно наверное обойтись и одной.
aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO);
aci_gap_set_authentication_requirement(0x01 /*bonding mode*/, 0x01 /*MITM protection required*/..., 0x01 /*Use Dynamic Pin*/, 0)
Теперь внимание! Будет вызыватся другой коллбек:
void aci_gap_numeric_comparison_value_event(uint16_t Connection_Handle,
uint32_t Numeric_Value)
Numeric_Value, которое вы тут получите будет таким же, которое покажет Андроид.
Его нужно тоже показать пользователю, при этом пользователь обязан сравнить 6 цифр и нажать кнопку YES.
если все в порядке.
По нажатию этой кнопки вы должны будете вызвать
aci_gap_numeric_comparison_value_confirm_yesno(Connection_Handle, 0x01);
После этого, спаривание успешно завершится.
BLE v4.0..v4.2 OOB Pairing
Это наверное самое современное спаривание, доступно в андроид, начиная с v7.
Идея тут такая, можно забрать у BlueNRG1 его пин код (v4.1), либо какие-то криптозначения Random/Confirm (v.4.2)
И разместить эти данные в чипе NFC, либо высветив QR код.
Как именно они дожны быть закодированы чтобы телефон их распоснал я не знаю, не пробовал,
но забрать эти данные можно вызвав вот эту функцию:
/**
* @brief This command is sent by the User to get (i.e. to extract from the Stack) the OOB data generated by the Stack itself.
* @param OOB_Data_Type OOB Data type
* Values:
- 0x00: TK (LP v.4.1)
- 0x01: Random (SC v.4.2)
- 0x02: Confirm (SC v.4.2)
* @param[out] Address_Type Identity address type.
* Values:
- 0x00: Public Identity Address
- 0x01: Random (static) Identity Address
* @param[out] Address Public or Random (static) address of this device
* @param[out] OOB_Data_Len Length of OOB Data
* @param[out] OOB_Data Local Pairing Data intended to the remote device to be sent via OOB.
* @retval Value indicating success or error code.
*/
tBleStatus aci_gap_get_oob_data(uint8_t OOB_Data_Type,
uint8_t *Address_Type,
uint8_t Address[6],
uint8_t *OOB_Data_Len,
uint8_t OOB_Data[16]);
Я так понимаю ее нужно вызывать после каждой инциализации соединения.
Документации к сожалению нету, как и примеров использования.
Вообщем как данные получены, мы их размещаем в NFC чипе, который считывает телефон.
Далее в телефоне есть аналог функции BlueNRG1, которую он вызовет:
tBleStatus aci_gap_set_oob_data(uint8_t Device_Type,
uint8_t Address_Type,
uint8_t Address[6],
uint8_t OOB_Data_Type,
uint8_t OOB_Data_Len,
uint8_t OOB_Data[16]);
T.e. телефон эти данные поставит в свой стек, и таким способом произойдет обмен секретами.
Далее, когда происходит Advertising в GAP протоколе, там есть специальный флаг, который показывает есть ли у нас данные полученные Out Of Band.
Если они есть хотя бы у одного из участников (v4.2) Они используются автоматически, и пин не запрашивается.
Ну и наоборот тоже работает.
если у нас есть приемник NFC мы должны вызвать aci_gap_set_oob_data по получению данных, BT Стек проставит флаг, что у него есть OOB данные при инициализации спаривания.
Таким способом можно спариться просто подойдя поближе к устройству.