UNPKG

typia

Version:

Superfast runtime validators with only one line

236 lines (235 loc) • 7.16 kB
import { ProtobufWire } from "@typia/interface"; import { Singleton } from "@typia/utils"; //#region src/internal/_ProtobufReader.ts var _ProtobufReader = class { constructor(buf) { this.buf = buf; this.ptr = 0; this.view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength); this.end = buf.length; } index() { return this.ptr; } size() { return this.end; } uint32() { return this.varint32() >>> 0; } int32() { return this.varint32(); } sint32() { const value = this.varint32(); return value >>> 1 ^ -(value & 1); } uint64() { return this.varint64(); } int64() { return BigInt.asIntN(64, this.varint64()); } sint64() { const value = this.varint64(); return value >> BigInt(1) ^ -(value & BigInt(1)); } bool() { return this.varint32() !== 0; } float() { return this.view.getFloat32(this.take(4), true); } double() { return this.view.getFloat64(this.take(8), true); } bytes() { return this.atomic(() => { const length = this.uint32(); const from = this.take(length); return this.buf.subarray(from, from + length); }); } string() { const bytes = this.bytes(); const decoder = utf8.get(); try { return decoder.decode(bytes); } catch { throw error("invalid UTF-8 string."); } } fork() { return this.atomic(() => { const previous = this.end; const length = this.uint32(); this.validate(length); this.end = this.ptr + length; return previous; }); } close(previous) { if (this.ptr !== this.end) throw error("buffer overflow."); this.end = previous; } /** Advance by exactly `length` bytes, for every length including zero. */ skip(length) { this.take(length); } /** * Advance by exactly one varint, which carries no length prefix. * * A varint terminates at its first byte without the continuation bit, and may * occupy no more than {@link VARINT_MAX_BYTES}. Its tenth byte is bounded * twice over, by {@link lastVarintByte}: it may neither continue into an * eleventh byte nor carry payload above bit 63. Skipping is where that is * easiest to get wrong, because this path discards the bytes it reads and so * never notices a value it could not have represented. Relaxing either bound * here would accept a varint that `varint32` and `varint64` reject, making * the limit depend on which method happens to consume the value. */ skipVarint() { this.atomic(() => { for (let i = 1; i < VARINT_MAX_BYTES; ++i) if ((this.u8() & 128) === 0) return; this.lastVarintByte(); }); } skipType(wireType) { this.atomic(() => { switch (wireType) { case ProtobufWire.VARIANT: this.skipVarint(); break; case ProtobufWire.I64: this.skip(8); break; case ProtobufWire.LEN: this.skip(this.uint32()); break; case ProtobufWire.START_GROUP: while ((wireType = this.uint32() & 7) !== ProtobufWire.END_GROUP) this.skipType(wireType); break; case ProtobufWire.I32: this.skip(4); break; default: throw error(`invalid wire type ${wireType} at offset ${this.ptr}.`); } }); } varint32() { let loaded; let value; value = (loaded = this.u8()) & 127; if (loaded < 128) return value; value |= ((loaded = this.u8()) & 127) << 7; if (loaded < 128) return value; value |= ((loaded = this.u8()) & 127) << 14; if (loaded < 128) return value; value |= ((loaded = this.u8()) & 127) << 21; if (loaded < 128) return value; value |= ((loaded = this.u8()) & 15) << 28; if (loaded < 128) return value; if (this.u8() < 128) return value; if (this.u8() < 128) return value; if (this.u8() < 128) return value; if (this.u8() < 128) return value; this.lastVarintByte(); return value; } varint64() { let loaded; let value; value = (loaded = this.u8n()) & BigInt(127); if (loaded < BigInt(128)) return value; value |= ((loaded = this.u8n()) & BigInt(127)) << BigInt(7); if (loaded < BigInt(128)) return value; value |= ((loaded = this.u8n()) & BigInt(127)) << BigInt(14); if (loaded < BigInt(128)) return value; value |= ((loaded = this.u8n()) & BigInt(127)) << BigInt(21); if (loaded < BigInt(128)) return value; value |= ((loaded = this.u8n()) & BigInt(127)) << BigInt(28); if (loaded < BigInt(128)) return value; value |= ((loaded = this.u8n()) & BigInt(127)) << BigInt(35); if (loaded < BigInt(128)) return value; value |= ((loaded = this.u8n()) & BigInt(127)) << BigInt(42); if (loaded < BigInt(128)) return value; value |= ((loaded = this.u8n()) & BigInt(127)) << BigInt(49); if (loaded < BigInt(128)) return value; value |= ((loaded = this.u8n()) & BigInt(127)) << BigInt(56); if (loaded < BigInt(128)) return value; value |= BigInt(this.lastVarintByte()) << BigInt(63); return BigInt.asUintN(64, value); } /** * Read the only byte whose varint payload is limited to bit 63. * * Two distinct faults meet on this byte, and each is named for what it is. A * continuation bit means the varint would occupy an eleventh byte, which no * 64-bit value can reach. Any other payload bit means the varint terminates * within its ten bytes but carries a value bit above 63. Reporting the second * as a length fault would tell the caller something untrue about a payload * that is exactly ten bytes long. */ lastVarintByte() { const loaded = this.u8(); if ((loaded & 128) !== 0) throw error(`varint exceeds ${VARINT_MAX_BYTES} bytes.`); if (loaded > 1) throw error(`varint exceeds ${VARINT_MAX_BITS} bits.`); return loaded; } u8() { return this.view.getUint8(this.take(1)); } u8n() { return BigInt(this.u8()); } take(length) { this.validate(length); const from = this.ptr; this.ptr += length; return from; } atomic(closure) { const index = this.ptr; const end = this.end; try { return closure(); } catch (thrown) { this.ptr = index; this.end = end; throw thrown; } } validate(length) { if (Number.isSafeInteger(length) === false || length < 0 || length > this.size() - this.ptr) throw error("buffer overflow."); } }; /** * Builds every fault this reader raises, so each one names typia as its source. * * Every decode error a caller can see must be attributable, and the prefix is * what attributes it. Composing it here rather than at each throw keeps a new * fault from silently shipping without it. */ const error = (message) => /* @__PURE__ */ new Error(`Error on typia.protobuf.decode(): ${message}`); /** * The most bytes a varint may occupy: a 64-bit value in seven-bit groups. * * Every path reads no further than its tenth byte and accepts only bit 63 from * that byte. This constant keeps the loop-driven skip path on the same limit. */ const VARINT_MAX_BYTES = 10; /** * The widest value a varint may carry: the 64-bit Protocol Buffer wire domain. * * The tenth byte reaches only bit 63, so a payload bit above it belongs to no * representable value. This names the overflow fault apart from the length * one. */ const VARINT_MAX_BITS = 64; const utf8 = new Singleton(() => new TextDecoder("utf-8", { fatal: true, ignoreBOM: true })); //#endregion export { _ProtobufReader }; //# sourceMappingURL=_ProtobufReader.mjs.map