All files / json-pack/src/rpc RpcMessageDecoder.ts

82.71% Statements 67/81
63.88% Branches 23/36
100% Functions 2/2
96.96% Lines 64/66

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 9634x   34x 34x                 34x 34x   34x   2694x 2694x 2694x 2694x 2694x 2694x 1861x 1858x   1858x 1858x 1858x 1858x 1857x 1857x 1857x 1857x 1857x 833x 832x 832x 832x 813x 813x 813x   813x 5x 5x 5x 5x   813x 813x 19x 18x 18x     18x 7x 7x 7x 7x 7x 11x 11x 11x   18x   1x     1x     3x       3x         4528x 4528x 4528x 4528x 4527x 4527x 4527x 4527x 4527x 4527x      
import {Reader} from '@jsonjoy.com/buffers/lib/Reader';
import {RpcMsgType, RpcReplyStat, RpcAcceptStat, RpcRejectStat} from './constants';
import {RpcDecodingError} from './errors';
import {
  RpcOpaqueAuth,
  RpcCallMessage,
  RpcAcceptedReplyMessage,
  RpcRejectedReplyMessage,
  type RpcMessage,
  RpcMismatchInfo,
} from './messages';
 
const EMPTY_BUFFER = new Uint8Array(0);
const EMPTY_READER = new Reader(EMPTY_BUFFER);
 
export class RpcMessageDecoder {
  public decodeMessage(reader: Reader): RpcMessage | undefined {
    const startPos = reader.x;
    try {
      Iif (reader.size() < 8) return undefined;
      const xid = reader.u32();
      const msgType = reader.u32();
      if (msgType === RpcMsgType.CALL) {
        if (reader.size() < 20) return (reader.x = startPos), undefined;
        const rpcvers = reader.u32();
        // if (rpcvers !== RPC_VERSION) throw new RpcDecodingError(`Unsupported RPC version: ${rpcvers}`);
        const prog = reader.u32();
        const vers = reader.u32();
        const proc = reader.u32();
        const cred = this.readOpaqueAuth(reader);
        Iif (!cred) return (reader.x = startPos), undefined;
        const verf = this.readOpaqueAuth(reader);
        Iif (!verf) return (reader.x = startPos), undefined;
        const params = reader.size() > 0 ? reader.cut(reader.size()) : undefined;
        return new RpcCallMessage(xid, rpcvers, prog, vers, proc, cred, verf, params);
      } else if (msgType === RpcMsgType.REPLY) {
        Iif (reader.size() < 4) return (reader.x = startPos), undefined;
        const replyStat = reader.u32();
        if (replyStat === RpcReplyStat.MSG_ACCEPTED) {
          const verf = this.readOpaqueAuth(reader);
          Iif (!verf || reader.size() < 4) return (reader.x = startPos), undefined;
          const acceptStat = reader.u32();
          let mismatchInfo: RpcMismatchInfo | undefined;
          if (acceptStat === RpcAcceptStat.PROG_MISMATCH) {
            Iif (reader.size() < 8) return (reader.x = startPos), undefined;
            const low = reader.u32();
            const high = reader.u32();
            mismatchInfo = new RpcMismatchInfo(low, high);
          }
          const results = reader.size() > 0 ? reader.cut(reader.size()) : undefined;
          return new RpcAcceptedReplyMessage(xid, verf, acceptStat, mismatchInfo, results);
        } else if (replyStat === RpcReplyStat.MSG_DENIED) {
          Iif (reader.size() < 4) return (reader.x = startPos), undefined;
          const rejectStat = reader.u32();
          let mismatchInfo: RpcMismatchInfo | undefined;
          let authStat: number | undefined;
          if (rejectStat === RpcRejectStat.RPC_MISMATCH) {
            Iif (reader.size() < 8) return (reader.x = startPos), undefined;
            const low = reader.u32();
            const high = reader.u32();
            mismatchInfo = new RpcMismatchInfo(low, high);
            Iif (!mismatchInfo) return (reader.x = startPos), undefined;
          } else if (rejectStat === RpcRejectStat.AUTH_ERROR) {
            Iif (reader.size() < 4) return (reader.x = startPos), undefined;
            authStat = reader.u32();
          }
          return new RpcRejectedReplyMessage(xid, rejectStat, mismatchInfo, authStat);
        } else {
          throw new RpcDecodingError('Invalid reply_stat');
        }
      } else {
        throw new RpcDecodingError('Invalid msg_type');
      }
    } catch (err) {
      Iif (err instanceof RangeError) {
        reader.x = startPos;
        return undefined;
      }
      throw err;
    }
  }
 
  private readOpaqueAuth(reader: Reader): RpcOpaqueAuth | undefined {
    Iif (reader.size() < 8) return undefined;
    const flavor = reader.u32();
    const length = reader.u32();
    if (length > 400) throw new RpcDecodingError('Auth body too large');
    const paddedLength = (length + 3) & ~3;
    Iif (reader.size() < paddedLength) return undefined;
    const body = length > 0 ? reader.cut(length) : EMPTY_READER;
    const padding = paddedLength - length;
    if (padding > 0) reader.skip(padding);
    return new RpcOpaqueAuth(flavor, body);
  }
}