typia
Version:
Superfast runtime validators with only one line
236 lines (235 loc) • 7.16 kB
JavaScript
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