Private KeeperPrivate Keeper
Объект PK

Сжатие и двоичные данные

Все 7 методов PK для UTF-8, массивов байтов, ZLIB, GZIP и Protobuf с точными форматами входа и обратимыми примерами.

Объект PK предоставляет 7 методов для трёх разных задач: перевода текста в байты, сжатия текста и работы с сообщениями Protobuf. Здесь особенно важен точный вид данных: строка с массивом JSON, строка Base64 и обычный текст не взаимозаменяемы. Те же операции в выражениях in-Line описаны в соседнем справочнике.

Что выбрать

Текст ↔ массив байтов

stringToByteArray возвращает байты UTF-8 как строку с массивом JSON, а byteArrayToString читает такую строку обратно как UTF-8.

Сжатие своей строки

Выберите согласованную пару ZLIB или GZIP. Обе пары передают сжатые байты как Base64, но используют разные двоичные обёртки.

Обмен по описанию полей

protoBuf создаёт и разбирает сообщение по номерам и типам полей. Описание должно точно совпадать с договором другой стороны.

Только сменить запись байтов

Для перехода между Base64 и шестнадцатеричной строкой используйте методы из раздела Файлы и кодирование.

Четыре представления данных

На примере слова Привет различие выглядит так:

Текст

Привет — шесть знаков. Чтобы получить байты, необходимо выбрать кодировку; методы этой страницы используют UTF-8.

Байты UTF-8

D0 9F D1 80 D0 B8 D0 B2 D0 B5 D1 82 — двенадцать байтов, показанных в шестнадцатеричной записи.

Массив JSON

[208,159,209,128,208,184,208,178,208,181,209,130] — те же байты как десятичные числа.

Base64

0J/RgNC40LLQtdGC — печатная запись тех же байтов. Base64 не сжимает и не защищает содержимое.

ZLIB и GZIP сначала получают байты UTF-8, сжимают их и только затем записывают результат в Base64. Protobuf строит байты по описанию полей, поэтому одного исходного текста ему недостаточно.

Строка JSON — не сам массив

Методы byteArrayToString и protoBuf передают данные через строку. Используйте JSON.stringify(...): вызов PK.byteArrayToString([104, 105]) нарушает договор, а PK.byteArrayToString(JSON.stringify([104, 105])) передаёт строку "[104,105]", которую ожидает исполнитель.

Вход сначала проходит обработку in-Line

Перед преобразованием каждый из семи методов раскрывает функции и переменные in-Line внутри входной строки. Такая строка не является непрозрачными данными: разметка in-Line в тексте, JSON или поле Protobuf будет выполнена раньше сжатия, кодирования или разбора. Порядок этой обработки описан в основах языка.

Текст и массив байтов

PK.stringToByteArray(input)

Принимает текст, кодирует его в UTF-8 и возвращает строку, содержащую массив десятичных чисел JSON. Каждое число обозначает один байт от 0 до 255.

const bytesJson = PK.stringToByteArray("Привет");

// Строка, а не готовый массив JavaScript:
// [208,159,209,128,208,184,208,178,208,181,209,130]

Чтобы работать с числами как с массивом JavaScript, разберите результат явно:

const bytes = JSON.parse(PK.stringToByteArray("hi"));

if (bytes.length !== 2 || bytes[0] !== 104 || bytes[1] !== 105) {
  throw new Error("Получен неожиданный массив UTF-8");
}

Запись той же операции в in-Line показана в описании STRTOBYTES.

PK.byteArrayToString(input)

Принимает строку с массивом чисел JSON, преобразует числа в байты и читает полученный массив как UTF-8. Возвращает обычный текст.

const bytesJson = JSON.stringify([104, 101, 108, 108, 111]);
const text = PK.byteArrayToString(bytesJson);

// hello

Неверный JSON, значение вместо массива или нечисловой элемент вызывают исключение. Не передавайте дроби и числа вне диапазона байта: исполнитель не определяет для них надёжного результата.

Произвольный файл нельзя читать как текст

Случайные байты могут не образовывать допустимый UTF-8, поэтому обратное преобразование способно изменить данные. Для файла храните Base64 или шестнадцатеричную запись; byteArrayToString применяйте к результату stringToByteArray либо к заранее известным байтам UTF-8.

Запись той же операции в in-Line показана в описании BYTESTOSTR.

Проверяемая пара

const source = "Привет, мир!";
const bytesJson = PK.stringToByteArray(source);
const restored = PK.byteArrayToString(bytesJson);

if (restored !== source) {
  throw new Error("Текст не восстановлен из байтов UTF-8");
}

Пара обратима для текста: первый метод сам создаёт допустимый массив UTF-8, а второй читает его в той же кодировке.

ZLIB и GZIP

Все четыре метода выполняют цепочку текст → UTF-8 → сжатые байты → Base64 либо её обратный ход. Они сжимают текст, а не готовый массив байтов и не файл.

ZLIB

Поток содержит заголовок ZLIB, данные DEFLATE и контрольную сумму Adler-32. Передавайте его только стороне, которая ожидает именно эту обёртку.

GZIP

Поток содержит заголовок GZIP, данные DEFLATE и завершающую часть GZIP. Это один поток, а не архив с несколькими файлами и именами.

Пары нельзя смешивать

Результат ZLibCompress передавайте только в ZLibDecompress, а результат GZIPCompress — только в GZIPDecompress. Одинаковый способ сжатия DEFLATE внутри не делает обёртки взаимозаменяемыми.

Пустая строка на входе любого из четырёх методов сразу возвращает пустую строку. Это особое правило: результат не является Base64 сжатого пустого потока. При неверном Base64, чужой обёртке или повреждённых данных метод возвращает строку Error: <описание>, а не выбрасывает исключение.

У ошибки нет отдельного вида результата

Успешная распаковка также возвращает обычный текст, поэтому строка, которая сама начинается с Error: , неотличима по виду от сообщения об ошибке. Если это различие существенно, проверяйте восстановленный текст по собственному договору: например, разбирайте ожидаемый JSON и проверяйте обязательные поля.

PK.ZLibCompress(input)

Принимает текст, сжимает его потоком ZLIB и возвращает сжатые байты как однострочную Base64.

const packed = PK.ZLibCompress('{"event":"heartbeat"}');
// Например: eJyrVkotS80rUbJSykhNLCpJSk0sUaoFAFHbB40=

Точная Base64 может измениться при смене средства сжатия или его настроек. Устойчивой проверкой служит восстановленный текст, а не совпадение с образцом побайтно. Обе записи in-Line приведены в примере пары ZLIB.

PK.ZLibDecompress(input)

Принимает Base64 с потоком ZLIB, распаковывает байты и возвращает текст UTF-8.

const text = PK.ZLibDecompress(
  "eJyrVkotS80rUbJSykhNLCpJSk0sUaoFAFHbB40="
);

// {"event":"heartbeat"}

Устройство этого потока определено в описании ZLIB.

Проверяемая пара ZLIB

const source = JSON.stringify({ event: "heartbeat", attempt: 3 });
const packed = PK.ZLibCompress(source);
const restored = PK.ZLibDecompress(packed);

if (restored !== source) {
  throw new Error(`ZLIB не восстановил исходный текст: ${restored}`);
}

PK.GZIPCompress(input)

Принимает текст, сжимает его потоком GZIP и возвращает сжатые байты как однострочную Base64.

const packed = PK.GZIPCompress('{"event":"sync"}');
// Один из допустимых результатов:
// H4sIAAAAAAAAAKtWSi1LzStRslIqrsxLVqoFADsT6ZcQAAAA

Точную Base64 также не закрепляйте как ожидаемый результат: заголовок GZIP допускает служебные поля. Обе записи in-Line приведены в примере пары GZIP.

PK.GZIPDecompress(input)

Принимает Base64 с потоком GZIP, распаковывает байты и возвращает текст UTF-8.

const text = PK.GZIPDecompress(
  "H4sIAAAAAAAAAKtWSi1LzStRslIqrsxLVqoFADsT6ZcQAAAA"
);

// {"event":"sync"}

Устройство потока определено в описании GZIP.

Проверяемая пара GZIP

const source = "строка с переводом\nи русским текстом";
const packed = PK.GZIPCompress(source);
const restored = PK.GZIPDecompress(packed);

if (restored !== source) {
  throw new Error(`GZIP не восстановил исходный текст: ${restored}`);
}

PK.protoBuf(input)

Метод создаёт, разбирает или дополняет одно сообщение Protobuf по описанию полей. Он не читает файл .proto, не извлекает описание из двоичного сообщения и не угадывает типы: при каждом вызове нужно передать точные номера и типы нужных полей.

actionЧто требуетсяЧто возвращается
encodemessage_type, field_mapping, field_valuesBase64 нового сообщения
decodemessage_type, field_mapping, protobuf_base64строка с объектом JSON
updateвсе четыре свойства вышеBase64 сообщения с добавленными значениями

Значение action приводится к нижнему регистру, поэтому encode, ENCODE и Encode выбирают одно действие. Остальные имена свойств и имена полей должны совпадать точно.

Договор входной строки

Вызов принимает результат JSON.stringify(...), а не сам объект JavaScript:

const input = JSON.stringify({
  action: "encode",
  message_type: "AuthRequest",
  field_mapping: {
    AuthRequest: {
      login: { number: 1, type: "string" },
      password: { number: 2, type: "string" }
    }
  },
  field_values: {
    login: "Arthas",
    password: "Scourge"
  }
});

const packetBase64 = PK.protoBuf(input);
message_type

Имя сообщения выбирает одноимённый объект внутри field_mapping. Само имя в двоичные данные не записывается.

field_mapping

Сначала содержит имена сообщений, затем имена полей. Для каждого поля обязательны свойства number и type.

field_values

Обязателен для encode и update. Ключ должен существовать в описании выбранного сообщения; отсутствующий ключ не записывается.

protobuf_base64

Обязательная непустая Base64 для decode и update. Она должна содержать ровно одно двоичное сообщение.

Для вложенного поля типа message описание содержит объект fields. Для enum описание содержит объект values, сопоставляющий имена перечисления с целыми числами. Свойство name внутри описания поля не используется: именем служит ключ объекта.

Запись той же операции в in-Line показана в описании PROTOBUF.

Поддерживаемые типы

int32, int64

Знаковые целые числа. В field_values и разобранном результате представлены числами JSON.

uint32, uint64

Беззнаковые целые числа. Разобранный uint64 возвращается строкой, чтобы не переполнить знаковое целое программы.

bool

Принимает true, false, 1 или 0. Другое числовое значение читается как false, поэтому используйте только четыре явных значения.

float, double

Числа с плавающей точкой одинарной и двойной точности. Вход и результат представлены числами JSON.

string

Обычная строка, которая записывается и читается как UTF-8.

bytes

Двоичное поле. Входное и разобранное значения представлены Base64, а не массивом чисел.

enum

При создании принимает известное имя либо число. При разборе возвращает имя, если число найдено в values, иначе само число.

message

Вложенное сообщение с собственным объектом fields. Можно передать один объект или массив объектов для повторяющегося вложенного поля.

Полная пара: создать и разобрать

const fieldMapping = {
  AuthRequest: {
    login: { number: 1, type: "string" },
    password: { number: 2, type: "string" }
  }
};

const packetBase64 = PK.protoBuf(JSON.stringify({
  action: "encode",
  message_type: "AuthRequest",
  field_mapping: fieldMapping,
  field_values: {
    login: "Arthas",
    password: "Scourge"
  }
}));

const decodedJson = PK.protoBuf(JSON.stringify({
  action: "decode",
  message_type: "AuthRequest",
  field_mapping: fieldMapping,
  protobuf_base64: packetBase64
}));

const decoded = JSON.parse(decodedJson);

if (decoded.login !== "Arthas" || decoded.password !== "Scourge") {
  throw new Error(`Получено неожиданное сообщение: ${decodedJson}`);
}

Для указанных значений packetBase64 равен CgZBcnRoYXMSB1Njb3VyZ2U=, а decodedJson содержит {"login":"Arthas","password":"Scourge"}. Имена полей в сообщение не входят: login и password восстанавливаются только по номерам из field_mapping.

Что делает update

update сначала разбирает известные поля исходного сообщения, затем добавляет записи из field_values и заново создаёт сообщение. Это не надёжная замена прежнего значения.

const updatedBase64 = PK.protoBuf(JSON.stringify({
  action: "update",
  message_type: "AuthRequest",
  field_mapping: fieldMapping,
  field_values: { login: "Jaina" },
  protobuf_base64: "CgZBcnRoYXMSB1Njb3VyZ2U="
}));

// CgZBcnRoYXMSB1Njb3VyZ2UKBUphaW5h

При последующем decode поле login станет массивом ['Arthas', 'Jaina'], потому что новая запись добавлена после прежней.

update теряет неизвестные поля

Поля, которых нет в field_mapping, пропускаются при разборе и не попадают в новый результат. Известное поле добавляется повторно вместо удаления прежнего. Не используйте update как обычную замену, если нужно сохранить неизвестные данные или получить единственное значение поля.

Границы и ошибки

  • Номер поля должен совпадать с описанием другой стороны. Допустимы номера от 1 до 536870911, кроме зарезервированного промежутка 19000–19999; исполнитель сам не проверяет всё это правило.
  • Поддерживаются одиннадцать типов, перечисленных выше. Названия sint32, sint64, fixed32, fixed64, sfixed32 и sfixed64 во внутреннем исполнителе обработаны неполно или несовместимо, поэтому в публичный договор не входят.
  • Повторяющиеся известные поля при decode собираются в массив. При encode массив поддерживается только для вложенного типа message; массивы простых строк, чисел и bytes не принимаются. Упакованные повторяющиеся числа не поддерживаются.
  • Неизвестное поле при decode пропускается, а отсутствующее известное поле не дополняется значением по умолчанию.
  • Неверный JSON, отсутствующее обязательное свойство, неизвестное действие, неверный тип значения или повреждённая Base64 вызывают исключение. В отличие от методов сжатия, protoBuf не возвращает строку Error: ....

Двоичная запись номеров и типов полей объяснена в официальном описании Protobuf.

Сначала получите точное описание

Для внешней службы возьмите исходный файл .proto или её точное описание и перенесите номера и типы только нужных полей. AuthRequest на этой странице объясняет форму вызова, но не является общей схемой для чужого узла.