diff --git a/src/util/protobuf.ts b/src/util/protobuf.ts index aae5347d..f97d7b02 100644 --- a/src/util/protobuf.ts +++ b/src/util/protobuf.ts @@ -1,4 +1,8 @@ import parseRawProto from 'rawprotoparse'; +import { gunzipSync, inflateSync } from 'zlib'; + +const GZIP_MAGIC_BYTES = '1f8b'; +const DEFLATE_MAGIC_BYTES = new Set(['7801', '785e', '789c', '78da', '7820', '787d', '78bb', '78f9']); export function isProbablyProtobuf(input: Uint8Array) { // Protobuf data starts with a varint, consisting of a field @@ -38,12 +42,24 @@ export const extractProtobufFromGrpc = (input: Buffer) => { const protobufMessasges: Buffer[] = []; while (input.length > 0) { - if (input.readInt8() != 0) { - throw new Error("Compressed gRPC messages not yet supported") + const compressionFlag = input.readInt8(); + const length = input.readInt32BE(1); + let message = input.slice(5, 5 + length); + + if (compressionFlag != 0) { + // TODO: In theory we should check consistency of actual content encoding + // with 'grpc-encoding' header, but we do not have access to it in this context + const magicBytes = message.toString('hex', 0, 2); + if (magicBytes == GZIP_MAGIC_BYTES) { + message = gunzipSync(message); + } else if (DEFLATE_MAGIC_BYTES.has(magicBytes)) { + message = inflateSync(message); + } else { + throw new Error(`Unknown gRPC compression scheme, magic bytes = '${magicBytes}'`); + } } - const length = input.readInt32BE(1); - protobufMessasges.push(input.slice(5, 5 + length)); + protobufMessasges.push(message); input = input.subarray(5 + length); }