/bt_echo

Механизм обмена данными через BLE сервис в часах Amazfit Bip

Primary LanguageC

bt_echo

Механизм обмена данными через BLE сервис в часах Amazfit Bip

Ни для кого не секрет, что отсутствие общедоступного механизма обмена информацией между часами и хостом, в виде телефона или компьютера, посредством интерфейса bluetooth LE является большим ограничением для разработки приложений, и что гораздо важнее - отсутствием возможности создавать приложения, центральной парадигмой, которых является обмен информацией с родительским хостом.

В настоящее время цикл разработки приложения заключается в следующем:

  • создание приложения на компьютере
  • формирование файла ресурсов с внедренным приложением
  • прошивка часов файлом ресурсов
  • проверка работоспособности приложения

Проблема данного цикла заключается в продолжительности процесса прошивки часов, которая занимает времени от 1 минуты. Если при создании небольшого приложения число компиляций составляет небольшую величину, то при разработке сложных и нелинейных приложений эта величина стремительно растёт. Присутствие механизма быстрой загрузки приложения в часы, позволило бы увеличить работоспособность разработчика и сделать процесс разработки более линейным и менее времязатратным. И хотя в часах существует сервис для получения отладочных сообщений, но сам процесс получения этих данных, хоть и облегчает разработку, но достаточно капризен, а полученные данные содержат не только результаты работы отладочного вывода вашей программы, но и всех отладочных сообщений операционной системы, которые произошли за последнее время.

Переходим к делу.

В часах при загрузке OS создается 10 сервисов:

  • GENERIC_ACCESS_SERVICE
  • GENERIC_ATTRIBUTE_SERVICE
  • IMMEDIATE_ALERT_SERVICE
  • DEVICE_INFORMATION_SERVICE
  • HEART_RATE_SERVICE
  • ALERT_NOTIFICATION_SERVICE
  • FIRMWARE_SERVICE
  • MIBAND_SERVICE
  • MIBAND2_SERVICE
  • ALIPAY_SERVICE

Некоторые общедоступные, с описанными интерфейсами, некоторые закрытые. Я достаточно долго копался в прошивке, чтобы обратить своё внимание на ALIPAY_SERVICE. Он зачастую упоминается как UNKNOWN_SERVICE, но это сервис для работы платёжной системы Alipay. Сразу скажу, что в данном повествовании не будут рассмотрены механизмы работы самой Alipay, нас только интересует моменты обмена информацией.

Всего два момента указывают на то, что этот сервис Alipay: первый, это отладочные сообщения в прошивке, которые правда никуда не выводятся, но тем не менее в прошивке содержатся. Второй момент, то если вы скачаете приложение Alipay для Android, то в 90 Мбайтном монстре можно найти файл classes6.dex, декомпиляция и поиск UUID’а для нашего сервиса приведут нас к классу com.alipay.android.phone.wear.ble.BLEClient.

Ладно, погнали в прошивку: Создание BLE сервисов. Да их не 10, но в некоторых создаётся не по одному. Нас интересует функция создания нашего сервиса _alipay_svc_create.

ROM:08036D28 _ble_services_init
ROM:08036D28                 PUSH    {R4,LR}
ROM:08036D2A                 BL      _devinfo_svc_create
ROM:08036D2E                 BL      _fw_svc_create
ROM:08036D32                 BL      _miband_svc_create
ROM:08036D36                 BL      _mi2band_svc_create
ROM:08036D3A                 BL      _hr_svc_create
ROM:08036D3E                 BL      _alert_notif_svc_create
ROM:08036D42                 BL      _imme_alert_svc_create
ROM:08036D46                 POP.W   {R4,LR}
ROM:08036D4A                 B.W     _alipay_svc_create

Некоторые листинги будут на Си, некоторые на ассемблере, чтобы наиболее понятно отобразить картину происходящего. Наш _alipay_svc_create()

ROM:08053EC4 _alipay_svc_create
ROM:08053EC4                 PUSH    {R4,LR}
ROM:08053EC6
ROM:08053EC6                 LDR     R3, =_alipay_svc_struct ; structAddress
ROM:08053EC8                 MOVS    R2, #11         ; not used
ROM:08053ECA                 MOVS    R1, #4          ; totalStructItems
ROM:08053ECC                 LDR     R0, =_alipay_svc_var ; a1
ROM:08053ECE                 BL      _ble_add_custom_service
ROM:08053ED2
ROM:08053ED2                 LDR     R4, =_alipay_svc_var
ROM:08053ED4                 CBNZ    R0, _isError
ROM:08053ED6
ROM:08053ED6                 LDRH    R0, [R4]        ; start_hdl
ROM:08053ED8                 MOVS    R1, #0          ; perm
ROM:08053EDA                 BL      _attm_svc_set_permission
ROM:08053EDE
ROM:08053EDE _isError                                
ROM:08053EDE                 LDRH    R0, [R4]
ROM:08053EE0                 ADDS    R0, R0, #4
ROM:08053EE2                 STRH    R0, [R4,#2]
ROM:08053EE4
ROM:08053EE4                 LDR     R0, =(_alipay_svc_connect+1)
ROM:08053EE6                 STR     R0, [R4,#4]
ROM:08053EE8
ROM:08053EE8                 MOVS    R0, #0
ROM:08053EEA                 STR     R0, [R4,#8]
ROM:08053EEC
ROM:08053EEC                 LDR     R0, =(_alipay_svc_gattc_write_cmd_confirm+1)
ROM:08053EEE                 STR     R0, [R4,#0xC]
ROM:08053EF0
ROM:08053EF0                 POP.W   {R4,LR}
ROM:08053EF4
ROM:08053EF4                 LDR     R0, =_alipay_svc_var
ROM:08053EF6                 B.W     _attm_add_svc_to_storage

Итак, смысл заключается в создании кастомного BLE сервиса с помощью api функций общедоступной SDK для используемого в часах чипа. Сервис, все его характеристики и атрибуты описываются через структуру, в данном случае _alipay_svc_struct. Если изучить SDK, то в ней присутствуют примеры, в которых используются такие же методы создания сервиса.

Долго ходить не будем. Cервис создается по описанным структурам, все хэндлы полученных сервисов сохраняются в структуре _alipay_svc_var, туда же запихиваются ссылки на процедуры, которые вызываются при подключении к сервису, к отключению от сервиса и для подтверждения записи в сервис данных _alipay_svc_gattc_write_cmd_confirm.

После инициализации, сервис добавляется в общее хранилище сервисов.

После инициализации доступ к сервису отключают. При подключении к сервису, доступ к сервису включают, это реализовано в процедуре _alipay_svc_connect. Процедура, которая выполняется после отключения от сервиса может отсутствовать, как в данном случае. Нас очень интересует процедура _alipay_svc_gattc_write_cmd_confirm (0x080319C4):

signed int __fastcall alipay_svc_gattc_write_cmd_confirm(int a1, unsigned __int16 *p_handle)
{
  unsigned __int16 *_p_handle; // r4
  int _handle; // r0
  signed int _err; // r5
  int cmd; // r1
  int _ticks; // r0

  _p_handle = p_handle;
  _handle = *p_handle;
  _err = 0;
  cmd = _handle - alipay_svc_var;
  if ( cmd == 2 )
  {
    gattc_write_cmd_confirm(_handle, 0, *(_p_handle + 6));
    if ( check_app_state(0x200000000i64) )
    {
      alipay_put_msg((_p_handle + 4), _p_handle[1]);
    }
    else
    {
      _ticks = get_tick_count(0);
      log_printf(5, "[%d] [WARN] recv alipay msg while not binding\r\n", _ticks);
    }
  }
  else if ( cmd == 3 )
  {
    gattc_write_cmd_confirm(_handle, 0, *(_p_handle + 6));
    sub_804332C(_p_handle);
  }
  else
  {
    _err = 2;
  }
  return _err;
}

Когда хост шлёт данные сервису, какой-либо его характеристик, то в зависимости от характеристики, эти данные должны подтверждаться, либо не должны подтверждаться. Для нашего сервиса они должны подтверждаться, и делается это процедурой gattc_write_cmd_confirm. Самое интересное начинается далее.

Проверяемое состояние системы через check_app_state, описывается битовым полем, в данном случае 0x200000000, судя по ошибке, которая выдаётся в log этот бит описывает признак binding’а чего-то к чему-то. Ну, вернее я подозреваю, что телефон с установленной программой Alipay обменивается всяческой секретной информацией с часами, после чего часы решают, что они прибиндены, но я это не проверял и всё это неточно.

Если записать данные в сервис, то мы ничего не узнаем и не увидим, данные просто провалились внутрь сервиса. Мы даже ошибки не увидим, потому как все логи сервиса alipay идут в /dev/null, мне пришлось сначала пропатчить прошивку, чтобы сообщения валились в стандартный сборщик логов через log_printf(0x0804E6E0), а не через alipay_log (0x080150C8).

После патча, я не с первого раза нашел это сообщение в логах.

[110006] [WARN] recv alipay msg while not binding

Оно выдаётся процедурой alipay_svc_gattc_write_cmd_confirm если процедуру не устраивает состояние системы
проверяемое через check_app_state. Я некоторое время потратил, чтобы выяснить как можно ввести систему в такое состояние, но погрузившись в вермишель ассемблерного кода и затем вынырнув, я решил идти проверенным путём – пропатчить прошивку, чтобы она эту проверку не делала.

Сказано – сделано.

Пропатчил, прошил, подключился, передал данные, скачал логи. Смотрим.

[1] [WARN] recv alipay msg while not binding
[5] [WARN] recv alipay msg while not binding

Всё равно ошибка? Ан нет, я просто использовал строку вывода ошибки, для отображения количества данных полученных от хоста, в поле где должны показываться тики.

Итак, данные процедура получает, а затем далее скармливает ее процедуре alipay_put_msg (0x 08058730), в параметрах, которой указатель на буфер данных и размер данных.

int __fastcall alipay_put_msg(int data, int len)
{
  int _len; // r4
  int _data; // r6
  int _err; // r0
  int _buf; // r0
  int v6; // r0
  int __buf; // [sp+0h] [bp-18h]

  _len = len;
  _data = data;
  _err = 0;
  if ( alipay_queue_50 && len )
  {
    _buf = pvPortMalloc(len + 8);
    __buf = _buf;
    *_buf = _len;
    *(_buf + 4) = _buf + 8;
    memcpy(_buf + 8, _data, _len);
    _err = xQueueGenericSend(alipay_queue_50, &__buf, 0);
    if ( _err != 1 )
    {
      v6 = get_tick_count(_err);
      log_printf(5, "[%d] [Err] alipay task put msg fail\r\n", v6);
      vPortFree(__buf);
      _err = 0;
    }
  }
  return _err;
}

Процедура эта проверяет наличие очереди для обмена данными, выделяет память запихивает туда служебку и наши данные, пихает всё в очередь alipay_queue_50 и завершается.

А что за очередь, спросите вы?

Отвечаем, в данном случае очередь используется для обмена между процессами. Процесс обработке BLE сообщений кинул в очередь данные и какой-то другой процесс может их оттуда получить. Ищем какой.

void __cdecl alipay_task_create()
{
  if ( !alipay_queue_50 )
  {
    alipay_queue_50 = xQueueGenericCreate(50, 4, 0);
    if ( !alipay_queue_50 )
    {
      _R0 = 80;
      __asm { MSR.W   BASEPRI, R0 }
      __dsb(0xFu);
      __isb(0xFu);
      syslog("alipay_task_init", 69);
      while ( 1 )
        ;
    }
  }
  if ( !alipay_task_handle )
  {
    xTaskCreate(alipay_task_handler, "ALIPAY_TASK", 512, 0, 1, &alipay_task_handle);
    if ( !alipay_task_handle )
    {
      _R0 = 80;
      __asm { MSR.W   BASEPRI, R0 }
      __dsb(0xFu);
      __isb(0xFu);
      syslog("alipay_task_init", 80);
      while ( 1 )
        ;
    }
  }
}

Очередь alipay_queue_50 создает задачка/процесс alipay_task, вернее её инициализатор. Если пойти поискать, кто вызывает alipay_task_create(), можно обнаружить необычное.

Задачу пускает не система, а приложение alipay.

Основное тело задачи крутится в alipay_task_handler(), смотрим на него.

void alipay_task_handler()
{
  int _buf; // [sp+0h] [bp-8h]

  _buf = 0;
  alipay_set_msg_handler_address(alipay_msg_queue_handler);
  while ( 1 )
  {
    xQueueGenericReceive(alipay_queue_50, &_buf, -1, 0);
    alipay_msg_queue_handler_run(*(_buf + 4), *_buf);
    vPortFree(_buf);
  }
}

Данная задача в вечном цикле ждёт данные в очереди, после их получения запускает обработчик эти сообщений, который заведомо установила в самом своём начале:

alipay_set_msg_handler_address(alipay_msg_queue_handler);

А далее пойдёт моё любимое в программах, хотя предыдущий вызов процедуры вам уже сделал подсказку.

ROM:0803C084 ; void __cdecl alipay_msg_queue_handler_run(unsigned __int8 *data, unsigned __int8 len)
ROM:0803C084 _alipay_msg_queue_handler_run           ; CODE XREF: _alipay_task_handler+24↓p
ROM:0803C084                 LDR     R2, =_alipay_ble_msg_handler ; get address and run
ROM:0803C086                 LDR     R2, [R2]
ROM:0803C088                 CMP     R2, #0
ROM:0803C08A                 BEQ     _exit
ROM:0803C08C                 BX      R2
ROM:0803C08E ; ---------------------------------------------------------------------------
ROM:0803C08E
ROM:0803C08E _exit                                   ; CODE XREF: _alipay_msg_queue_handler_run+6↑j
ROM:0803C08E                 BX      LR

Получение адреса обработчика из ОЗУ, и если он не 0, то запуск. Люблю такое. Такое легко патчить и таким легко управлять.

Собрали все мысли в кучу и выдали:

Пишем свою приложуху на часы, которая создаёт задачу через alipay_task_create(), после этого устанавливает свой обработчик через alipay_set_msg_handler_address(). Кстати надо заодно пропатчить, чтобы alipay_task_handler() не устанавливал стандартный обработчик, т.к. неизвестно чем он там занимается и может взорвать задачу её запуска. Поэтому патчим установку обработчика на 0, ибо при его запуске эта проверка на 0 существует.

Спустя несколько минут / часов я увидел в логах следующее:

[2] [WARN] recv alipay msg while not binding
[custom_msg_handler] len=2 data=35 35

Да, я еще не отключил отладочное сообщение об ошибке, где ошибки нет.

Второе сообщение шлёт уже мой обработчик

void custom_msg_handler(byte *data, int len)
{
    log_printf(5, "[custom_msg_handler] len=%d data=", len);
    for(int i=0; i<len; i++)
        log_printf(5, "%02x ", *(data+i));
}

На данном этапе мы уже получаем данные от хоста. Дальше дело пошло быстрее.

Есть такая функция send_host_app_data(), которая есть даже в libbip, если посмотреть кто её вызывает,
то можно попасть на процедуру alipay_send_host_data() в аргументах которой указатель на данные и длина данных. Вообщем по-мотивам данного эссе было написано приложение, которая принимает данные от хоста и тут же обратно их шлёт.

Результат работы можно увидеть на картинке:

alt-текст

Подключаемся к часам.

  • open #00

Подключаемся к сервису Alipay (0x3802)

  • set #09

Подписываемся на оповещения от характеристики (0x4a02)

  • subs #00

Записываем данные в характеристику

  • w #00 55

(Примечание) Тут непонятно почему в первый раз не срабатывает оповещение.

Читаем данные из характеристики

  • r #00

Дальше выясняем что максимальное количество данных, которое можно послать равно 20 байт. Ну и видим, как приходят оповещения об изменении данных.

Есть все предпосылки, чтобы внедрять работу с данным механизмом в BipOS тем самым решить хотя бы часть проблем описанных в введении. Понятно, что это начало пути и появятся организационные вопросы по организации единого протокола, чтобы не было коллизий между приложениями, но это уже другая проблема.

Скажу, что всё выше изложенное это только моё ограниченное видение мира, вполне возможно я где-то не прав в своих выводах. Если будут вопросы или поправки, обращайтесь.

Код программы можно скачать из текущего репозитария

Ссылки:

Копаемая прошивка 1.1.5.36 (latin)

DA1450 SDK найдёте здесь

BLE Console

Патчер прошивки и патч-файлы