Пакет DMRD: побайтовый разбор протокола Homebrew
Хотспот подключается к серверу, в дашборде бегут вызовы — и всё это едет по протоколу, у которого нет официальной спецификации. Homebrew (он же MMDVM-протокол) описан только исходным кодом и обрывочными вики. Разберём его так, как он выглядит в реальном трафике: что за что отвечает в каждом байте, как устроен вход по паролю и почему один и тот же протокол у разных программ немного разный.
Откуда взялся протокол и почему нет спецификации
Homebrew придумали для того, чтобы самодельный репитер мог подключиться к сети как обычный узел. Первое описание сделал DL5DI, но в вики BrandMeister прямо сказано, что эта спецификация неполна, а версия, реализованная в MMDVMHost, имеет недокументированные отличия. Поэтому единственный надёжный источник правды — код: hblink.py в HBlink3 и DMRNetwork.cpp в MMDVMHost.
Практическое следствие: если вы пишете свой сервер, ориентируйтесь на поведение живых клиентов, а не на текстовые описания. Мы в DMRhub так и делали — свой мастер писался под то, что реально присылают Pi-Star, WPSD и приложения.
Кадр DMRD: 55 байт, в которых лежит весь голос
Голосовой и данный трафик едет одним типом пакета — он начинается с ASCII-сигнатуры DMRD. Общая длина 55 байт: 20 байт заголовка и 33 байта полезной нагрузки (264 бита — ровно один DMR-бёрст).
| Байты | Поле | Что означает |
|---|---|---|
| 0–3 | Сигнатура | Всегда DMRD (0x44 0x4D 0x52 0x44) |
| 4 | Sequence | Счётчик пакетов в потоке, растёт по кругу 0–255 |
| 5–7 | RF Source | DMR ID говорящего, 3 байта — отсюда предел 16 777 215 |
| 8–10 | Destination | Номер группы или ID абонента при приватном вызове |
| 11–14 | Peer ID | ID узла, приславшего пакет: хотспота, репитера или приложения |
| 15 | Флаги | Слот, тип вызова, тип кадра — разбор ниже |
| 16–19 | Stream ID | Случайный идентификатор одной передачи от нажатия до отпускания PTT |
| 20–52 | Payload | 33 байта = 264 бита DMR-бёрста (голос AMBE или данные) |
Байт 15: четыре смысла в восьми битах
Самый плотный байт кадра. Разбирается так:
- Бит 7 (маска 0x80) — таймслот: установлен → TS2, снят → TS1.
- Бит 6 (маска 0x40) — тип вызова: установлен → приватный (unit), снят → групповой.
- Биты 5–4 (маска 0x30, сдвиг вправо на 4) — тип кадра: голос, голос с синхронизацией или данные.
- Биты 3–0 (маска 0x0F) — уточнение. Для данных: 1 — заголовок голосового вызова, 2 — терминатор. Для голоса: 0–5 — бёрсты A…F в шестикадровом суперкадре.
- Отдельная проверка: если (флаги & 0x23) == 0x23, это CSBK внутри голосового потока.
Вход в сеть: соль, SHA-256 и четыре шага
Прежде чем слать голос, узел должен войти. Обмен текстовый — все команды, кроме DMRD, это ASCII-сигнатуры с приклеенными данными.
- Пир → мастер: RPTL + свой ID (4 байта). «Здравствуйте, я такой-то».
- Мастер → пир: RPTACK + соль (4 случайных байта). Соль на каждое подключение новая — поэтому перехваченный ответ нельзя переиграть.
- Пир → мастер: RPTK + свой ID + sha256(соль ‖ пароль) в виде hex-строки. Сам пароль по сети не идёт никогда.
- Мастер: считает то же самое у себя и сравнивает. Совпало — RPTACK, не совпало — MSTNAK и разрыв.
Дальше пир присылает RPTC с конфигурацией (позывной, частоты RX/TX, мощность, координаты, высота, описание, URL) и, если нужно, RPTO с опциями. Живость поддерживается RPTPING → MSTPONG. Отключение: RPTCL от пира или MSTCL от мастера.
Почему у BlueDV «другой» Homebrew
Исторически разошлись две ветки: классический Homebrew 2015 года и вариант MMDVM 2020 года. Отличия в наборе полей конфигурации и в мелочах поведения, но их достаточно, чтобы инструменты анализа не понимали чужой диалект. Показательный пример: единственный публичный диссектор для Wireshark честно предупреждает, что работает только с протоколом MMDVMHost и не работает с «generic homebrew», которым пользуется BlueDV.
Если пишете сервер — принимайте оба варианта: отличать их можно по длине и составу пакета RPTC.
Как посмотреть это своими глазами
Ничего экзотического не нужно. На хотспоте с Pi-Star или WPSD:
- снять дамп: sudo tcpdump -i any -w dmr.pcap udp port 62031;
- забрать файл на компьютер и открыть в Wireshark;
- смотреть на первые 20 байт полезной нагрузки UDP — по таблице выше они читаются вручную за минуту.
Для автоматического разбора есть открытые описания формата в виде .ksy-схем Kaitai Struct (проект OK-DMR) — они покрывают и Homebrew 2015, и MMDVM 2020, включая нестандартные расширения. Для Python-разбора полей внутри 33-байтной нагрузки пригодится ok-dmrlib: там реализованы и помехоустойчивые коды, и разбор PDU.
Что внутри 33 байт
Полезная нагрузка — это сырой DMR-бёрст: 264 бита, из которых 216 занимает голос (три кадра AMBE+2 по 72 бита) либо данные, а середину занимает синхронизация или встроенная сигнализация. Как эти биты защищены от ошибок и почему их так странно перемешивают — отдельная тема, разобранная в статье про помехоустойчивое кодирование DMR. О том, как голос превращается в эти 72 бита, — в статье про вокодер AMBE.
Свой мастер вместо чужой сети
DMRhub — это полностью свой сервер на этом самом протоколе: хотспоты подключаются напрямую, эфир и журнал вызовов видны в панели, а маршруты вы задаёте сами. Подключить существующий хотспот — дело нескольких минут.
Источники
- HBlink3, разбор кадра DMRD и обмен RPTL/RPTK — github.com/HBLink-org/hblink3
- Homebrew repeater protocol, вики BrandMeister (порт 62030, SHA-256 с солью, оговорка о неполноте спецификации) — wiki.brandmeister.network
- MMDVMHost, реализация клиентской стороны — github.com/g4klx/MMDVMHost
- MMDVM-Dissector для Wireshark (ограничение по generic homebrew, лицензия CC BY-NC-SA) — github.com/marrold/MMDVM-Dissector
- dmr-kaitai, схемы Homebrew 2015 и MMDVM 2020 — github.com/OK-DMR/dmr-kaitai
- Homebrew/MMDVM коннектор в openSPOT2 — manuals.sharkrf.com
Настроили рацию — заходите в эфир: свой мастер, хотспоты и приложение с DMR прямо в телефоне.
Войти в портал