🧊

Node.jsでBufferを読み取る

プロトコルの実装でよく出てくるやつのまとめです。

Node.jsの`Buffer`を通して得た、オクテットバイトストリームってやつを対象に。

普通に`Buffer`のメソッドを使うこともあるし、特定のバイトからビットを取り出すこともあるはずで、そのバリエーションのメモ。

もうバリエーションはないかもしれないけど、また見つけたら追記する。

バイト単位で読み取る

一番シンプルな読み出しのパターン。

const first1Byte = buf[0];

というようにインデックスで対象の1バイトが取れる。

その他は、APIのリストにある`readXxx()`系のメソッドを使う。

// 1バイトなのでBEもLEも関係ない
const byte0_1 = buf.readUInt8(0);

const byte0_2 = buf.readUInt16BE(0);
const byte2_4 = buf.readUInt16BE(2);

const byte0_4 = buf.readUInt32BE(0);

という具合で、読み取り位置をオフセットで指定する。
まだ実装経験が浅いけど、おそらく`readUIntXXBE()`系のメソッドしか使わなさそう。

Buffer | Node.js v18.3.0 Documentation

4byteより大きい読み取り

`readUIntXXBE()`系のメソッドは、32bit = 4byteまでしか用意されてない。

そこで使うのが`readUIntBE()`・・ではあるが、これも最大で6byteまでしか読み取れない。

const byte0_2 = buf.readUIntBE(0, 2);
const byte0_6 = buf.readUIntBE(0, 6);

const byte0_7 = buf.readUIntBE(0, 7); // RangeError!

それ以上を一気に読み取りたい場合どうするか。

6byteより大きい読み取り

まあほとんどの場合はそれを数値ではなく、その中身をそのまま使いたいとかなはず。

const byte0_8 = buf.slice(0, 8);

const last2Byte = buf.slice(-2);

それ用のメソッドが`slice()`です。

その後は`toString()`するか、そのまま使うことが多いはず。

ビット単位で読み取る

ここから先は、バイトではなくビットが欲しい場合。
ビットが欲しい = `1`か`0`が知りたいということになる。

先頭1bitの値を得る

const byte = buf[0];
const first1Bit = (byte & 0x80) >>> 7;
  • 16進数の`0x80`は、10進数で`128`で、2進数で`10000000`
    • 先頭だけが`1`の8bit
  • それを`&`で演算すると、同じ桁が`1`のところだけ`1`が残る
    • 先頭は`1`なら`1`が残る、他の桁は`0`になる
  • それを`>>> 7`、つまり右から7桁捨てる
    • 残るのは2進数で`1`か`0`、10進数でも`1`か`0`
  • なので先頭1bitが`0`か`1`が取れる

という仕組み。

うしろ4bitの値を得る

さっきの応用といえば応用。

const byte = buf[0];
const last4Bit = byte & 0x0f;
  • 16進数の`0x0f`は、10進数で`15`、2進数で`1111`
    • つまり8bitでかくと`00001111`
  • それを`&`で演算すると、上4桁は`0000`に固定できる
    • 下4桁は`1`なら`1`になるし、`0`なら`0`のまま残る
  • 最終的に、2進数で`1111`から`0000`の範囲の値が取れる
    • = 10進数の`15`から`0`の範囲の値

つまり欲しいビットの桁だけを`1`にしたやつで`&`演算すれば、その値がわかるということ。

たとえばうしろ7bitが欲しいなら、2進数の`01111111`つまり16進数の`0x7f`で`&`すればいい。

  • うしろ1bit: `& 0x01`
  • うしろ2bit: `& 0x03`
  • うしろ3bit: `& 0x07`
  • うしろ4bit: `& 0x0f`
  • うしろ5bit: `& 0x1f`
  • うしろ6bit: `& 0x3f`
  • うしろ7bit: `& 0x7f`

という感じ。