Спецификация socket-протокола
Всё необходимое, чтобы встроить поддержку Entrixy «Internet» в свой контроллер: описание WebSocket-сообщений, E2EE и тест-векторы.
device_key для серийного производства — §10. Референс: esp32-ws-example (MIT).
1. Транспорт и конверт сообщений
- Endpoint:
wss://entrixy.com/ws(hostentrixy.com, порт 443, path/ws). В открытой модели хост берётся из ключа/настройки — см. /open. - TLS: обычный WSS. Subprotocol не задаётся.
- Кадры: текстовые (UTF-8) JSON. Дискриминатор — поле
type. - Реконнект: фиксированный интервал 5 с. RX-watchdog: нет входящих кадров 90 с → рестарт.
2. Подключение и аутентификация
Первым кадром контроллер представляется:
→ device_hello
{ "type":"device_hello",
"device_key": "<32 hex>",
"device_secret": "<32 hex>",
"e2ee": true|false } // есть ли валидный ownerSecret (§5)
device_key/device_secret — две 32-символьные hex-половины «кода устройства» (опц. префикс ENTX-DEV:). Сервер сверяет sha256(device_secret) с хэшем по device_key.
❮ device_ok { "type":"device_ok", "hb_interval":300 } // успех; hb_interval сек (10..3600)
← error { "type":"error", "reason":"auth" } // провал → сервер закрывает
3. Открытие (базовый режим, без E2EE)
❮ device_command
{ "type":"device_command", "action":"open", "command_id":"<id>", "number_id":<n> }
// action:"close" — для бистабильного привода
Контроллер даёт импульс на реле (по умолчанию 1000 мс) и отвечает:
→ device_status
{ "type":"device_status", "command_id":"<id>",
"level":"success"|"warning"|"danger"|"info",
"message":"...", "final":true
[, "position":"open"|"closed"|"unknown" ] }
level вне белого списка → трактуется как info.
4. Heartbeat и живость
Сервер не пингует устройства (fan-out не масштабируется). Живость держит устройство:
→ { "type":"ping" } ← { "type":"pong" } // устройство шлёт каждые ~30 с
Молчащее дольше ~90 с устройство сервер отключает. Обратный ping от сервера → устройство отвечает pong. Для бистабильного привода — object-state heartbeat: спонтанный device_status без command_id с position каждые hb_interval сек. Сервер может переустановить: ❮ { "type":"hb_interval", "seconds":<N> }.
5. E2EE: провизионинг ownerSecret
E2EE делает так, что сервер не может подделать открытие — команду подписывает приложение. ownerSecret (32 байта) в прошивку не зашивается: приложение владельца передаёт его по USB-serial:
PROVISION <64 hex> → сохранить ownerSecret в NVS, вернуть fingerprint WIPE → стереть (базовый режим) STATUS → показать fingerprint fingerprint = HMAC-SHA256(ownerSecret, "fp")[0..3] (hex)
После PROVISION/WIPE контроллер переподключается с обновлённым e2ee. Провизенный контроллер отвергает обычный device_command — только §6.
6. E2EE: challenge-response открытие
При e2ee=true вместо device_command — challenge-response. Свежесть даёт одноразовый nonce, не время:
1. ← sock_challenge_req { "type":"sock_challenge_req", "command_id":"<id>" }
2. → sock_challenge { "type":"sock_challenge", "command_id":"<id>", "nonce":"<b64 16B>" }
3. ← sock_fire { "type":"sock_fire", "command_id","number_id",
"token":"<b64>", "owner_sig":"<b64>", "guest_id":<n>,
"nonce":"<b64>", "proof":"<b64>" }
4. → device_status success
| Кто | token | Проверка |
|---|---|---|
| Владелец | пустой | proof == HMAC-SHA256(ownerSecret, nonce)[0..15] |
| Гость | 3 байта | сверить owner_sig, вывести guest_key, сверить proof |
token (3B) = [guest_did 2B LE][perms 1B] // capability, без TTL owner_sig = HMAC-SHA256(ownerSecret, token)[0..15] guest_key = HKDF-SHA256(salt=null, ikm=ownerSecret, info="guest"||guest_did(2B LE), L=32) proof = HMAC-SHA256(guest_key, nonce)[0..15]
Бинарные поля — base64. nonce 16 байт, подписи усечены до 16. HKDF salt=null = 32 нулевых байта.
7. Отзыв гостя
❮ sock_revoke { "type":"sock_revoke", "number_id","guest_id","version","revoked", "sig":"<b64>" }
→ sock_revoke_ack
sig = HMAC-SHA256(ownerSecret, "rev" || guest_id(2B LE) || version(4B LE) || revoked(1B))[0..15]
Контроллер применяет только при version больше сохранённого (монотонно — сервер не подделает и не воскресит доступ).
8. Крипто-примитивы
- HMAC-SHA256 — подписи; усечение = первые 16 байт.
- HKDF-SHA256 (RFC 5869),
salt=null→ 32 нулевых байта. - В socket-открытии AES/GCM нет — только HMAC/HKDF. (AES-256-GCM — в отдельной подсистеме шифрования гостевых бандлов, не здесь.)
Деривация guest_key и подписи идентичны гостевой ветке BLE — крипто переиспользуется.
9. Тест-векторы (E2EE)
Known-answer векторы. Бинарные поля даны и hex, и base64 (в кадрах — base64).
ownerSecret (32B) = a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf fingerprint = b358977a = HMAC(ownerSecret,"fp")[0..3] nonce (16B) = 000102030405060708090a0b0c0d0e0f b64 = AAECAwQFBgcICQoLDA0ODw==
Owner fire (token пустой)
proof (16B) = 3fc0619c684a8261d06c1501ae4e726a = HMAC(ownerSecret, nonce)[0..15] proof b64 = P8BhnGhKgmHQbBUBrk5yag==
Guest fire (guest_did=0x0042, perms=0x01)
token (3B) = 420001 b64 = QgAB owner_sig (16B) = 850a9f31fb708b176f4ed62a43acf0ad = HMAC(ownerSecret, token)[0..15] b64 = hQqfMftwixdvTtYqQ6zwrQ== guest_key (32B) = 614da70a34890a807a25f7c5f271e530434c478f144370a2b9efaf0c58cd85c1 = HKDF(salt=null, ownerSecret, info="guest"||did_LE, L=32) proof (16B) = a8a383ae56ac8a0182df2a275e220e83 = HMAC(guest_key, nonce)[0..15] b64 = qKODrlasigGC3yonXiIOgw==
Revoke (guest_id=0x0042, version=3, revoked=1)
sig input = "rev"||guest_id_LE(2)||version_LE(4)||revoked(1) = 72657642000300000001 sig (16B) = 9433b96ddbb599f6c72bf17c0c9825fe b64 = lDO5bdu1mfbHK/F8DJgl/g==
10. Получение device_key (для OEM)
Чтобы сервер узнавал устройство и понимал, чьи это ворота, нужна пара device_key/device_secret, известная серверу и привязанная к аккаунту владельца.
- Прототип / штучно — сегодня: владелец создаёт объект «Устройство» в приложении, получает пару, конфигуратор запекает её в прошивку. Достаточно, чтобы поднять и проверить весь протокол.
- Серийное производство: модель «claim» — устройство с завода «ничьё», покупатель привязывает его к своему аккаунту по коду/QR. Партии кодов и наклейки выпускаются в кабинете производителя: подаёте заявку, после одобрения генерируете партию
device_key, получаете CSV с кодами и готовые QR-наклейки. Вопросы по условиям — hello@entrixy.com.
Рабочая реализация обеих сторон — esp32-ws-example (MIT). Собрать .bin — в браузерном конфигураторе. Как это в открытой архитектуре — /open.