序列化算法无法正确读取数据

问题描述 投票:0回答:1

为了学术兴趣,我正在开发自己的类似于 GRPC 的协议。我选择 NodejS 作为语言,因为它对我来说做很多事情既简单又愉快。

我自己开发了数据格式,准确来说是这样的

消息类型(1字节):

0x01 - 请求 0x02 - 答案

方法长度(1字节):

包含要调用的方法名称的字符串的长度。

被调用的方法(可变长度):

包含要调用的方法名称的字符串。

参数数量(1字节):

表示参数数量的整数值。

参数(可变长度):

参数类型(1字节):

  • 0x01 - 整数(4 字节)
  • 0x02 - 字符串
  • 0x03 - 布尔值(1 个字节)
  • 0x04 - 对象(递归序列化)

参数值(可变长度):

取决于参数类型。

尾随字节(1 个字节):

  • 0xFF - 表示消息结束。

我开发了序列化/反序列化算法,但是反序列化抛出错误

这是我要序列化的数据结构

{
  "messageType": "request",
  "callMethod": "PetService.createPet",
  "parameters": [
    {
      "id": 12,
      "name": "john",
      "owner": {
        "id": 1,
        "name": "beria"
      }
    },
    14
  ]
}

这是我指定字节的枚举代码

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(JSON.stringify({
    messageType,
    callMethod,
    parameters,
  }, null, 2));

  await validateParams(
    parameters,
    schema,
    callMethod,
  );

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

  // Message type
  let msgTypeByte = match<MessageType>(messageType)
    .with(MessageType.REQUEST, () => MessageTypeBytes.REQUEST)
    .with(MessageType.RESPONSE, () => MessageTypeBytes.RESPONSE)
    .exhaustive();
  
  buffer = Buffer.concat([buffer, Buffer.from([msgTypeByte])]);

  // Method length
  const methodLengthBuffer = Buffer.alloc(1);
  methodLengthBuffer.writeUint8(callMethod.length);
  buffer = Buffer.concat([buffer, methodLengthBuffer]);

  // Called method
  buffer = Buffer.concat([buffer, Buffer.from(callMethod)]);

  // Parameters count (in symbols)
  const paramsCountBuffer = Buffer.alloc(1);
  paramsCountBuffer.writeUint8(parameters.length);
  buffer = Buffer.concat([buffer, paramsCountBuffer]);

  // Parameters
  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 };
}

序列化效果很好,产生这种格式

01145065 74536572 76696365 2E637265 61746550 65740204 02696400 0C000000 046E616D 6502046A 6F686E05 6F776E65 72040269 64000100 0000046E 616D6502 05626572 6961000E 000000

但是反序列化算法会抛出错误

Error: Unsupported value type: 0

我对二进制格式的世界相当陌生,所以请帮助我。我只是无法取得工作成果。如果你能准确地告诉我在哪里以及如何修复它,那就太好了 =(

node.js
1个回答
0
投票

在实际的序列化输出中,与您在问题中提到的内容略有不同:

01 14 50 65 74 53 65 72 76 69 63 65 2e 63 72 65 61 74 65 50 65 74 02 04 02 69 64 00 01 0c 00 00 00 04 6e 61 6d 65 02 04 6a 6f 68 6e 05 6f 77 6e 65 72 04 02 69 64 00 01 01 00 00 00 04 6e 61 6d 65 02 05 62 65 72 69 61 01 0e 00 00 00

根据您提供的输出,原因是这只是一个不正确的序列化,因为整数值类型使用

00
而不是
01

在实际输出中,问题源于缺乏对象框架机制,例如键计数前缀或边界标记后缀,如

ServiceBytes.EOL
(您没有使用),但针对对象。换句话说,即使遇到最后一个键
deserializeObject
01 0e 00 00 00
函数也会继续将尾部
[0].owner.name
解析为对象。这导致
0e
被解释为键,
00
被解释为值类型。

© www.soinside.com 2019 - 2024. All rights reserved.