完全に理解した(ぉ

2025/12/09

何様

で、でた~、テレビの保護シール貼ったまま奴~。…ごきげんよう。

[Javascript] Protocol Buffers (Protobuf) のデコード


.proto 不使用版。
基本的に下記プロジェクトの流用で、一部問題点があるので修正していく。

https://github.com/pawitp/protobuf-decoder

修正1. Javascriptのbyte型の扱い


Javascriptのbyteが取りうる範囲は -128~127 の符号つきだが、1箇所だけ符号なしとして扱っている箇所がある。
varintUtils.jsfunction decodeVarint() 内。
おそらく非ASCII文字が含まれない場合は問題にならないのだろう。

  // fix
  //} while (byte >= 0x80);
  } while ((byte & 0x80) !== 0);

別解としては、変数に1バイト格納する際に符号なしに変換しておく。

    // fix
    //byte = buffer[offset++];
    byte = buffer[offset++] & 0xff;

byte[] ではなく Uint8Array を渡してもよいかもしれないが、自分は別の箇所への影響を考慮し断念した。

修正2. *.jsx の改造


*.jsx ファイルはHTML出力用なので、各関数の戻り値をたとえばオブジェクトの値そのものに変更する。
これにより、全体として配列や連想配列の階層からなるオブジェクトが取得される。



修正3. バイト列が文字列かどうかの判定


Protobufのバイト列がオブジェクトか文字列かは本来、定義がないと判断できない。
流用元ではいきなりオブジェクトとして解釈を試み、その成否で判定しているように見える。

function getProtobufPart(part) {
  switch (part.type) {
    ...
    case TYPES.LENDELIM:
      // TODO: Support repeated field
      let decoded = decodeProto(part.value);
      if (part.value.length > 0 && decoded.leftOver.length === 0) {
        return [, "protobuf"];
      } else {
        decoded = decodeStringOrBytes(part.value);
        return [, decoded.type];
      }
    ...

しかしこれは例外発生でエラーとなる。
そこで、バイト列がUTF-8文字列として解釈可能かどうかをチェックする形に変更する。
以下はGoogle Apps Script向けの実装例。

function canDecodeToString_(value) {
  var i = 0;
  while (i < value.length) {
    const b = value[i] & 0xff;
    if (!isBitSet_(b, 7)) {
      if (b == 0x09 || b == 0x0a || b == 0x0d || b >= 0x20) {
        i++;
      } else {
        return false;
      }
    } else if (isBitSet_(b, 6) && i + 1 < value.length && is10xxxxxx_(value[i + 1] & 0xff)) {
      if (!isBitSet_(b, 5)) {
        i += 2;
      } else if (i + 2 < value.length && is10xxxxxx_(value[i + 2] & 0xff)) {
        if (!isBitSet_(b, 4)) {
          i += 3;
        } else if (i + 3 < value.length && is10xxxxxx_(value[i + 3] & 0xff)) {
          i += 4;
        } else {
          return false;
        }
      } else {
        return false;
      }
    } else {
      return false;
    }
  }
  return true;
}

const isBitSet_ = (b, bit) => (b & (1 << bit)) !== 0;  

const is10xxxxxx_ = (b) => isBitSet_(b, 7) && !isBitSet_(b, 6);

もちろんこれではチェックをすり抜けるオブジェクトが出てくるかもしれないが、自分用としては許容する。

使い方


decodeProto() だけでは最上位の階層しかデコードされていない状態。
続けて ProtobufDisplay() を通すことで下位の階層まで処理される。

  const obj = ProtobufDisplay(decodeProto(bytes));

decodeProto() 内部で再帰的に呼び出すよう改造してもよいかもしれない。

このブログを検索

ソーシャル/購読

X  Threads  note

RSS Feedly Inoreader

Threads

ブログ アーカイブ