Specification
Формат пакета INTD v1
Формат открыт намеренно: конфиденциальность и целостность дают шифрование и подпись запроса, а не неизвестность разметки. Фиксированная структура и CRC32 позволяют серверу дёшево отбрасывать мусор до записи в хранилище.
Контейнер
| Смещение | Размер | Поле | Значение |
|---|---|---|---|
| 0 | 4 | MAGIC | 0x49 0x4E 0x54 0x44 («INTD») |
| 4 | 1 | VERSION | 0x01 |
| 5 | 1 | FLAGS | 0x00, зарезервировано |
| 6 | 8 | TIMESTAMP | uint64 BE, unix мс — база для дельт |
| 14 | 4 | EVENTS_COUNT | uint32 BE |
| 18 | 4 | BLOB_LENGTH | uint32 BE, длина EVENTS_BLOB |
| 22 | N | EVENTS_BLOB | события подряд |
| 22+N | 4 | CRC32 | uint32 BE, CRC-32/IEEE от EVENTS_BLOB |
Итоговый размер ровно 26 + N байт; лишние байты — ошибка.
Событие
| Размер | Поле | Значение |
|---|---|---|
| 1 | TYPE | 0x01 pageview, 0x02 search |
| 4 | TS_DELTA | uint32 BE, мс после TIMESTAMP |
| 2 + var | DOMAIN | uint16 длина ≤ 255, UTF-8 |
| 2 + var | URL | uint16 длина ≤ 2048, UTF-8 |
| 2 + var | QUERY | uint16 длина ≤ 500, 0 если нет |
| 1 + var | LANG | uint8 длина ≤ 20, BCP 47 |
| 1 | DEVICE_TYPE | 0x00 unknown, 0x01 desktop, 0x02 mobile, 0x03 tablet, 0x04 bot |
Строки обрезаются по границе символа UTF-8. Сервер отклоняет пакет, если длина больше лимита, строка не является корректным UTF-8, число событий не совпадает с заголовком или CRC32 не сходится.
Что делает сервер
- Домен пересчитывается из URL (eTLD+1), значение клиента не используется.
- Из URL и запроса вырезаются токены, пароли, e-mail и номера карт.
- События старше 7 дней и из будущего (больше 10 минут) отбрасываются.
- Страна определяется по IP соединения, идентификатор установки хешируется отдельно для каждого расширения.
Шифрование и подпись
sessionKey = random(32)
iv = random(12)
ciphertext = AES-256-GCM(sessionKey, iv, bucket)
ephemeral = X25519 — новая пара на каждый пакет
shared = X25519(ephemeral.private, extension.publicKey)
derivedKey = HKDF-SHA256(IKM = shared, salt = iv, info = "intentd-bucket-v1", 32)
encryptedKey = AES-256-GCM(derivedKey, iv, sessionKey) // 48 байт X-Signature = hex(HMAC-SHA256(secret,
"intentd-v2|bucket|" + X-Timestamp + "|" + X-API-Key + "|" + hex(SHA-256(body)))) Схема не даёт прямой секретности при последующей компрометации приватного ключа расширения и не защищает от владельца копии бандла: от переупаковки защищают сессии на каждую установку, проверка ID расширения и проверки на сервере.
Тестовые векторы
// Одно событие search: база 1758000000000, дельта 7,
// домен a.io, URL https://a.io/?q=x, запрос x, язык en, desktop
494e5444 01 00 0000019950f72c00 00000001 00000025
02 00000007 0004 612e696f 0011 68747470733a2f2f612e696f2f3f713d78 0001 78 02 656e 01
cee33ee2 HKDF-SHA256(IKM = 0x11 × 32, salt = 0x22 × 12, info = "intentd-bucket-v1", L = 32)
=> 4a359b8818fc073f6f81a3cb4acd9065302e285fbef0d35d2ff71fba107e6056