@torigetz

Почему не срабатывает десериализация?

Привет, разрабатываю ради академического интереса свой протокол по подобию GRPC. В качестве языка выбрал NodejS, так как на нем мне просто и приятно делать достаточно многие вещи.

Я разработал собственный формат данных, а если точно то вот такой

Тип сообщения (1 байт):
0x01 - Запрос
0x02 - Ответ

Длина метода (1 байт): Длина строки, содержащей имя вызываемого метода.

Вызываемый метод (переменная длина): Строка, содержащая имя вызываемого метода.

Количество параметров (1 байт): Целочисленное значение, указывающее количество параметров.

Параметры (переменная длина):

Тип параметра (1 байт):
0x01 - Целое число (4 байта)
0x02 - Строка
0x03 - Булево значение (1 байт)
0x04 - Объект (рекурсивно сериализуется)

Значение параметра (переменная длина):
В зависимости от типа параметра.

Завершающий байт (1 байт):
0xFF - Указывает на конец сообщения.

Вот enum с байтами
export enum MessageTypeBytes {
  REQUEST = 0x01,
  RESPONSE = 0x02,
}

export enum DataTypeBytes {
  INTEGER = 0x01,
  STRING = 0x02,
  BOOLEAN = 0x03,
  OBJECT = 0x04,
}

export enum ServiceBytes {
  EOL = 0xFF,
}


Вот так выглядит сериализатор, с ним вроде все хорошо.
import { Schema, MessageType, MessageTypeBytes, DataTypeBytes } from "@koda-rpc/common";
import { match } from "ts-pattern";
import { validateParams } from "./validation";

export interface ISerializeOptions {
  callMethod: string;
  messageType: MessageType;
  parameters: Array<unknown>;
  schema: Schema;
}

export const serialize = async ({
  callMethod,
  messageType,
  parameters,
  schema,
}: ISerializeOptions) => {
  console.log({
    messageType,
    callMethod,
    parameters,
  });

  await validateParams(
    parameters,
    schema,
    callMethod,
  );

  let buffer: Buffer = Buffer.alloc(0);

  // Тип сообщения
  let msgTypeByte = match<MessageType>(messageType)
    .with(MessageType.REQUEST, () => MessageTypeBytes.REQUEST)
    .with(MessageType.RESPONSE, () => MessageTypeBytes.RESPONSE)
    .exhaustive();
  
  buffer = Buffer.concat([buffer, Buffer.from([msgTypeByte])]);

  // Длина метода
  const methodLengthBuffer = Buffer.alloc(1);
  methodLengthBuffer.writeUint8(callMethod.length);
  buffer = Buffer.concat([buffer, methodLengthBuffer]);

  // Вызываемый метод
  buffer = Buffer.concat([buffer, Buffer.from(callMethod)]);

  // Количество параметров
  const paramsCountBuffer = Buffer.alloc(1);
  paramsCountBuffer.writeUint8(parameters.length);
  buffer = Buffer.concat([buffer, paramsCountBuffer]);

  // Параметры
  parameters.forEach(parameter => {
    if (typeof parameter === 'number') {
      buffer = Buffer.concat([buffer, Buffer.from([DataTypeBytes.NUMBER])]);
      const numberValueBuffer = Buffer.alloc(4);
      numberValueBuffer.writeUint32LE(parameter);
      buffer = Buffer.concat([buffer, numberValueBuffer])
    } else if (typeof parameter === 'string') {
      buffer = Buffer.concat([buffer, Buffer.from([DataTypeBytes.STRING])]);
      const stringLengthBuffer = Buffer.alloc(1);
      stringLengthBuffer.writeUInt8(parameter.length);
      buffer = Buffer.concat([buffer, stringLengthBuffer]);
      buffer = Buffer.concat([buffer, Buffer.from(parameter)]);
    } else if (typeof parameter === 'boolean') {
      buffer = Buffer.concat([buffer, Buffer.from([DataTypeBytes.BOOLEAN])]);
      const boolValueBuffer = Buffer.alloc(1);
      boolValueBuffer.writeUInt8(parameter ? 0x01 : 0x00);
      buffer = Buffer.concat([buffer, boolValueBuffer]);
    } else if (typeof parameter === 'object') {
      buffer = Buffer.concat([buffer, Buffer.from([DataTypeBytes.OBJECT])]);
      buffer = Buffer.concat([buffer, serializeObject(parameter)]);
    } else {
      throw new Error(`Unsupported parameter type: ${typeof parameter}`);
    }
  });

  return buffer;
};

const serializeObject = (obj: object): Buffer => {
  let buffer = Buffer.alloc(0);

  Object.entries(obj).forEach(([key, value]) => {
    // Длина ключа
    const keyLengthBuffer = Buffer.alloc(1);
    keyLengthBuffer.writeUInt8(key.length);
    buffer = Buffer.concat([buffer, keyLengthBuffer]);
    // Ключ
    buffer = Buffer.concat([buffer, Buffer.from(key)]);
    
    if (typeof value === 'number') {
      buffer = Buffer.concat([buffer, Buffer.from([DataTypeBytes.NUMBER])]);
      const numberValueBuffer = Buffer.alloc(4);
      numberValueBuffer.writeInt32LE(value);
      buffer = Buffer.concat([buffer, numberValueBuffer]);
    } else if (typeof value === 'string') {
      buffer = Buffer.concat([buffer, Buffer.from([DataTypeBytes.STRING])]);
      const stringLengthBuffer = Buffer.alloc(1);
      stringLengthBuffer.writeUInt8(value.length);
      buffer = Buffer.concat([buffer, stringLengthBuffer]);
      buffer = Buffer.concat([buffer, Buffer.from(value)]);
    } else if (typeof value === 'boolean') {
      buffer = Buffer.concat([buffer, Buffer.from([DataTypeBytes.BOOLEAN])]);
      const boolValueBuffer = Buffer.alloc(1);
      boolValueBuffer.writeUInt8(value ? 0x01 : 0x00);
      buffer = Buffer.concat([buffer, boolValueBuffer]);
    } else if (typeof value === 'object') {
      buffer = Buffer.concat([buffer, Buffer.from([DataTypeBytes.OBJECT])]);
      buffer = Buffer.concat([buffer, serializeObject(value)]);
    } else {
      throw new Error(`Unsupported value type: ${typeof value}`);
    }
  });

  return buffer;
}


А вот так выглядит десериализатор
import { DataTypeBytes, MessageType, MessageTypeBytes, Schema } from "@koda-rpc/common";
import { validateParams } from "./validation";
import { match } from "ts-pattern";
import { IDeserializedData } from "./types";

interface IDeserializeOptions {
  buffer: Buffer;
  messageType: MessageType;
  schema: Schema;
}

export const deserialize = async ({
  buffer,
  schema,
}: IDeserializeOptions): Promise<IDeserializedData> => {
  // Распаковываем данные с использованием zlib
  const uncompressedBuffer = buffer;

  let index = 0;

  // Читаем тип сообщения
  const msgTypeByte = uncompressedBuffer[index++] as MessageTypeBytes;
  const msgType = match<MessageTypeBytes>(msgTypeByte)
    .with(MessageTypeBytes.REQUEST, () => MessageType.REQUEST)
    .with(MessageTypeBytes.RESPONSE, () => MessageType.RESPONSE)
    .otherwise(() => '');

  // Читаем длину метода
  const methodLength = uncompressedBuffer[index++];
  // Читаем вызываемый метод
  const callMethod = uncompressedBuffer.slice(index, index + methodLength).toString();
  index += methodLength;

  // Читаем количество параметров
  const parametersCount = uncompressedBuffer[index++];
  const parameters: unknown[] = [];

  // Читаем параметры
  for (let i = 0; i < parametersCount; i++) {
    // Читаем тип параметра
    const parameterType = uncompressedBuffer[index++];
    if (parameterType === DataTypeBytes.NUMBER) {
      const numberValue = uncompressedBuffer.readInt32LE(index);
      parameters.push(numberValue);
      index += 4;
    } else if (parameterType === DataTypeBytes.STRING) {
      const stringLength = uncompressedBuffer[index++];
      const stringValue = uncompressedBuffer.slice(index, index + stringLength).toString();
      parameters.push(stringValue);
      index += stringLength;
    } else if (parameterType === DataTypeBytes.BOOLEAN) {
      const booleanValue = uncompressedBuffer[index++] === 0x01;
      parameters.push(booleanValue);
    } else if (parameterType === DataTypeBytes.OBJECT) {
      const { parsedObject, newIndex } = deserializeObject(uncompressedBuffer.slice(index));
      parameters.push(parsedObject);
      index += newIndex;
    } else {
      throw new Error(`Unsupported parameter type: ${parameterType}`);
    }
  }

  // Проверяем наличие завершающего байта
  const endByte = uncompressedBuffer[index];
  if (endByte !== 0xFF) {
    throw new Error('Invalid end byte');
  }

  await validateParams(
    parameters,
    schema,
    callMethod,
  );

  return {
    messageType: msgType as MessageType,
    callMethod,
    parameters,
  };
}

const deserializeObject = (buffer: Buffer): { parsedObject: object; newIndex: number } => {
  let index = 0;
  const parsedObject: { [key: string]: any } = {};

  while (index < buffer.length) {
    // Читаем длину ключа
    const keyLength = buffer[index++];
    // Читаем ключ
    const key = buffer.slice(index, index + keyLength).toString();
    index += keyLength;

    // Читаем тип значения
    const valueType = buffer[index++];
    if (valueType === DataTypeBytes.NUMBER) {
      const numberValue = buffer.readInt32LE(index);
      parsedObject[key] = numberValue;
      index += 4;
    } else if (valueType === DataTypeBytes.STRING) {
      const stringLength = buffer[index++];
      const stringValue = buffer.slice(index, index + stringLength).toString();
      parsedObject[key] = stringValue;
      index += stringLength;
    } else if (valueType === DataTypeBytes.BOOLEAN) {
      const booleanValue = buffer[index++] === 0x01;
      parsedObject[key] = booleanValue;
    } else if (valueType === DataTypeBytes.OBJECT) {
      const { parsedObject: nestedObject, newIndex } = deserializeObject(buffer.slice(index));
      parsedObject[key] = nestedObject;
      index += newIndex;
    } else {
      throw new Error(`Unsupported value type: ${valueType}`);
    }
  }

  return { parsedObject, newIndex: index };
}


Туда-обратно я гоняю вот такой формат
{
  "messageType": "request",
  "callMethod": "PetService.createPet",
  "parameters": [
    {
      "id": 12,
      "name": "john",
      "owner": {
        "id": 1,
        "name": "beria"
      }
    },
    14
  ]
}


И вот так выглядят сериализованные данные
01145065 74536572 76696365 2E637265 61746550 65740204 02696400 0C000000 046E616D 6502046A 6F686E05 6F776E65 72040269 64000100 0000046E 616D6502 05626572 6961000E 000000


ОДНАКО, мой десериализатор выдает ошибку
Error: Unsupported value type: 0

Помогите плз решить
  • Вопрос задан
  • 90 просмотров
Решения вопроса 1
@torigetz Автор вопроса
Вся проблема заключалась в том, что у меня не было никаких точек остановки десериализации объекта. Для решения данной проблемы было принято решение после объявления объекта - хранить байт с количеством ключей. Тогда метод deserializeObject точно знает, когда ему остановиться.

Обновленный код сериализации объекта
const serializeObject = (obj: object): Buffer => {
  let buffer = Buffer.alloc(0);

  const entries = Object.entries(obj);
  entries.forEach(([key, value]) => {
    // Количество ключей
    const keysCountBuffer = Buffer.alloc(1);
    keysCountBuffer.writeUint8(entries.length);
    buffer = Buffer.concat([buffer, keysCountBuffer]);
    // Длина ключа
    const keyLengthBuffer = Buffer.alloc(1);
    keyLengthBuffer.writeUInt8(key.length);
    buffer = Buffer.concat([buffer, keyLengthBuffer]);
    // Ключ
    buffer = Buffer.concat([buffer, Buffer.from(key)]);
    
    if (typeof value === 'number') {
      buffer = Buffer.concat([buffer, Buffer.from([DataTypeBytes.NUMBER])]);
      const numberValueBuffer = Buffer.alloc(4);
      numberValueBuffer.writeInt32LE(value);
      buffer = Buffer.concat([buffer, numberValueBuffer]);
    } else if (typeof value === 'string') {
      buffer = Buffer.concat([buffer, Buffer.from([DataTypeBytes.STRING])]);
      const stringLengthBuffer = Buffer.alloc(1);
      stringLengthBuffer.writeUInt8(value.length);
      buffer = Buffer.concat([buffer, stringLengthBuffer]);
      buffer = Buffer.concat([buffer, Buffer.from(value)]);
    } else if (typeof value === 'boolean') {
      buffer = Buffer.concat([buffer, Buffer.from([DataTypeBytes.BOOLEAN])]);
      const boolValueBuffer = Buffer.alloc(1);
      boolValueBuffer.writeUInt8(value ? 0x01 : 0x00);
      buffer = Buffer.concat([buffer, boolValueBuffer]);
    } else if (typeof value === 'object') {
      buffer = Buffer.concat([buffer, Buffer.from([DataTypeBytes.OBJECT])]);
      buffer = Buffer.concat([buffer, serializeObject(value)]);
    } else {
      throw new Error(`Unsupported value type: ${typeof value}`);
    }
  });

  return buffer;
}


Обновленный код десериализации объекта
const deserializeObject = (buffer: Buffer): { parsedObject: object; newIndex: number } => {
  let index = 0;
  const parsedObject: { [key: string]: any } = {};

  // Читаем количество ключей
  const keysCount = buffer[index++];
  for (let keyNum = 0; keyNum < keysCount; keyNum++, index++){
    // Читаем длину ключа
    const keyLength = buffer[index++];
    // Читаем ключ
    const key = buffer.slice(index, index + keyLength).toString();
    index += keyLength;

    // Читаем тип значения
    const valueType = buffer[index++];
    if (valueType === DataTypeBytes.NUMBER) {
      const numberValue = buffer.readInt32LE(index);
      parsedObject[key] = numberValue;
      index += 4;
    } else if (valueType === DataTypeBytes.STRING) {
      const stringLength = buffer[index++];
      const stringValue = buffer.slice(index, index + stringLength).toString();
      parsedObject[key] = stringValue;
      index += stringLength;
    } else if (valueType === DataTypeBytes.BOOLEAN) {
      const booleanValue = buffer[index++] === 0x01;
      parsedObject[key] = booleanValue;
    } else if (valueType === DataTypeBytes.OBJECT) {
      const { parsedObject: nestedObject, newIndex } = deserializeObject(buffer.slice(index));
      parsedObject[key] = nestedObject;
      index += newIndex;
    } else {
      throw new Error(`Unsupported value type: ${valueType}`);
    }
  }

  return { parsedObject, newIndex: index - 1 };
}


Уважаемая администрация, пожалуйста, не удаляйте вопрос. Вдруг кому поможет в будущем с сериализацией/десериализацией в бинари
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы