/**
* Protobuf binary reader for ArkTS
*
* Implements varint/zigzag and fixed-width primitives, plus length-delimited
* string/bytes. Maintains cursor position for sequential decoding.
*
* Version 2.0 - 增强功能:
* - 递归深度保护(防止栈溢出攻击)
* - view() 方法(用于获取未知字段的原始数据)
*/
import { decodeUTF8 } from './util'
export class Reader {
private b: Uint8Array
pos: number
len: number
// 递归深度保护
private recursionDepth: number = 0
private readonly maxRecursionDepth: number = 100
constructor(buf: Uint8Array) {
this.b = buf
this.pos = 0
this.len = buf.length
}
/**
* 增加递归深度
* @throws 如果超过最大递归深度
*/
incrementRecursionDepth(): void {
this.recursionDepth++
if (this.recursionDepth > this.maxRecursionDepth) {
throw new Error(`Message nesting depth exceeds limit: ${this.maxRecursionDepth}`)
}
}
/**
* 减少递归深度
*/
decrementRecursionDepth(): void {
this.recursionDepth--
}
/**
* 获取当前递归深度
*/
getRecursionDepth(): number {
return this.recursionDepth
}
/** Read unsigned varint32 (returns uint32) */
uint32(): number {
let x = 0, s = 0
while (true) { const b = this.b[this.pos++]; x |= (b & 0x7F) << s; if ((b & 0x80) === 0) break; s += 7 }
return x >>> 0
}
/** Read boolean (varint) */
bool(): boolean { return this.uint32() !== 0 }
/** Read int32 (two's complement) */
int32(): number { return this.uint32() | 0 }
/** Read sint32 (zigzag) */
sint32(): number { const v = this.uint32(); return (v >>> 1) ^ (-(v & 1)) }
/** Read signed varint64 as bigint */
int64(): bigint {
let x = 0n, s = 0n
while (true) { const b = BigInt(this.b[this.pos++]); x |= (b & 0x7Fn) << s; if ((b & 0x80n) === 0n) break; s += 7n }
const signBit = 1n << 63n
return (x & signBit) !== 0n ? (x - (1n << 64n)) : x
}
/** Read sint64 (zigzag) as bigint */
sint64(): bigint { const v = this.int64(); return (v >> 1n) ^ (-(v & 1n)) }
/** Read signed varint64 as number (may lose precision) */
int64Number(): number { const v = this.int64(); const max = BigInt(Number.MAX_SAFE_INTEGER); return v <= max ? Number(v) : Number(v) }
/** Read unsigned varint64 as bigint */
uint64(): bigint {
let x = 0n, s = 0n
while (true) { const b = BigInt(this.b[this.pos++]); x |= (b & 0x7Fn) << s; if ((b & 0x80n) === 0n) break; s += 7n }
return x
}
/** Read unsigned varint64 as number (may lose precision) */
uint64Number(): number { const v = this.uint64(); const max = BigInt(Number.MAX_SAFE_INTEGER); return v <= max ? Number(v) : Number(v) }
/** Read fixed32 little-endian */
fixed32(): number { const v = new DataView(this.b.buffer, this.b.byteOffset + this.pos, 4).getUint32(0, true); this.pos += 4; return v >>> 0 }
/** Read sfixed32 little-endian */
sfixed32(): number { const v = new DataView(this.b.buffer, this.b.byteOffset + this.pos, 4).getInt32(0, true); this.pos += 4; return v | 0 }
/** Read fixed64 little-endian */
fixed64(): bigint { const dv = new DataView(this.b.buffer, this.b.byteOffset + this.pos, 8); const lo = BigInt(dv.getUint32(0, true)); const hi = BigInt(dv.getUint32(4, true)); this.pos += 8; return (hi << 32n) | lo }
/** Read sfixed64 little-endian */
sfixed64(): bigint { const dv = new DataView(this.b.buffer, this.b.byteOffset + this.pos, 8); const lo = BigInt(dv.getInt32(0, true)); const hi = BigInt(dv.getInt32(4, true)); this.pos += 8; return (hi << 32n) | (lo & 0xFFFFFFFFn) }
/** Read float32 little-endian */
float(): number { const v = new DataView(this.b.buffer, this.b.byteOffset + this.pos, 4).getFloat32(0, true); this.pos += 4; return v }
/** Read float64 little-endian */
double(): number { const v = new DataView(this.b.buffer, this.b.byteOffset + this.pos, 8).getFloat64(0, true); this.pos += 8; return v }
/** Read length-delimited UTF-8 string */
string(): string {
const l = this.uint32()
const s = this.b.subarray(this.pos, this.pos + l)
this.pos += l
return decodeUTF8(s)
}
/** Read length-delimited bytes */
bytes(): Uint8Array {
const l = this.uint32()
const s = this.b.subarray(this.pos, this.pos + l)
this.pos += l
return s
}
/** Skip unknown field by wire type */
skipType(wt: number): void {
switch (wt) {
case 0: this.uint32(); break
case 1: this.pos += 8; break
case 2: { const l = this.uint32(); this.pos += l; break }
case 5: this.pos += 4; break
default: throw new Error('invalid wire type')
}
}
/**
* 获取原始字节数据(用于保存未知字段)
*
* 返回 [start, end) 范围的字节数据(不包含 end)
*
* @param start 起始位置(包含)
* @param end 结束位置(不包含)
* @returns 原始字节切片
*
* 使用示例:
* ```typescript
* const start = reader.pos
* reader.skipType(wireType)
* const unknownFieldData = reader.view(start, reader.pos)
* ```
*/
view(start: number, end: number): Uint8Array {
return this.b.subarray(start, end)
}
}