Files
grin-web-wallet/scripts/protocol_buffers.js
2024-12-20 18:08:44 -08:00

703 lines
18 KiB
JavaScript
Executable File

// Use strict
"use strict";
// Classes
// Protocol Buffers class
class ProtocolBuffers {
// Public
// Decode
static decode(messageType, data, schema) {
// Check if schema for message type isn't known
if(messageType.toFixed() in schema === false) {
// Throw error
throw "Schema for message type isn't known.";
}
// Get message schema
var messageSchema = schema[messageType.toFixed()];
// Initialize result
var result = {};
// Go through all fields in the data
for(var i = 0; i < data["length"];) {
// Get field tag
var fieldTag = ProtocolBuffers.decodeVarint(data, i);
// Go to start of the field payload
i += ProtocolBuffers.getVarintLength(data, i);
// Check if field tag is too big
if(fieldTag > Number.MAX_SAFE_INTEGER) {
// Throw error
throw "Field tag is too big.";
}
// Get field number
var fieldNumber = fieldTag.toNumber() >>> 3;
// Get field wire type
var fieldWireType = fieldTag.toNumber() & 0b111;
// Check if field is known in the message schema
if(fieldNumber.toFixed() in messageSchema === true) {
// Check field's expected type
switch(messageSchema[fieldNumber.toFixed()]["Type"]) {
// Uint, bool, enum, or sint
case ProtocolBuffers.UINT_SCHEMA_DATA_TYPE:
case ProtocolBuffers.BOOL_SCHEMA_DATA_TYPE:
case ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE:
case ProtocolBuffers.SINT_SCHEMA_DATA_TYPE:
// Check if field wire type isn't correct
if(fieldWireType !== ProtocolBuffers.VARINT_WIRE_TYPE) {
// Throw error
throw "Field wire type isn't correct.";
}
// Break
break;
// String or bytes
case ProtocolBuffers.STRING_SCHEMA_DATA_TYPE:
case ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE:
// Check if field wire type isn't correct
if(fieldWireType !== ProtocolBuffers.LEN_WIRE_TYPE) {
// Throw error
throw "Field wire type isn't correct.";
}
// Break
break;
}
// Check if field doesn't exist in the result
if(messageSchema[fieldNumber.toFixed()]["Name"] in result === false) {
// Create field in result
result[messageSchema[fieldNumber.toFixed()]["Name"]] = [];
}
}
// Check field wire type
switch(fieldWireType) {
// Varint
case ProtocolBuffers.VARINT_WIRE_TYPE:
// Check if field is known in the message schema
if(fieldNumber.toFixed() in messageSchema === true) {
// Get value from the field payload
var value = ProtocolBuffers.decodeVarint(data, i);
// Check field's expected type
switch(messageSchema[fieldNumber.toFixed()]["Type"]) {
// Uint
case ProtocolBuffers.UINT_SCHEMA_DATA_TYPE:
// Append value to result
result[messageSchema[fieldNumber.toFixed()]["Name"]].push(value);
// Break
break;
// Bool
case ProtocolBuffers.BOOL_SCHEMA_DATA_TYPE:
// Append value as a boolean to result
result[messageSchema[fieldNumber.toFixed()]["Name"]].push(value.isZero() === false);
// Break
break;
// Enum
case ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE:
// Check if value is too big
if(value > Number.MAX_SAFE_INTEGER) {
// Throw error
throw "Value is too big.";
}
// Append value as an enum to result
result[messageSchema[fieldNumber.toFixed()]["Name"]].push(value.toNumber());
// Break
break;
// Sint
case ProtocolBuffers.SINT_SCHEMA_DATA_TYPE:
// Get if value is even
if(value.modulo(2).isZero() === true) {
// Append value as a positive number to result
result[messageSchema[fieldNumber.toFixed()]["Name"]].push(value.dividedToIntegerBy(2));
}
// Otherwise
else {
// Append value as a negative number to result
result[messageSchema[fieldNumber.toFixed()]["Name"]].push(value.plus(1).dividedToIntegerBy(-2));
}
// Break
break;
}
}
// Go to next field
i += ProtocolBuffers.getVarintLength(data, i);
// Break
break;
// I64
case ProtocolBuffers.I64_WIRE_TYPE:
// Check if field payload doesn't contain data
if(i + Common.BYTES_IN_A_UINT64 > data["length"]) {
// Throw error
throw "Field payload doesn't contain data.";
}
// Go to next field
i += Common.BYTES_IN_A_UINT64;
// Break
break;
// Len
case ProtocolBuffers.LEN_WIRE_TYPE:
// Get length from the field payload
var length = ProtocolBuffers.decodeVarint(data, i);
// Go to the field payload's data
i += ProtocolBuffers.getVarintLength(data, i);
// Check if length is too big
if(length > Number.MAX_SAFE_INTEGER) {
// Throw error
throw "Length is too big.";
}
// Check if field payload doesn't contain data
if(i + length.toNumber() > data["length"]) {
// Throw error
throw "Field payload doesn't contain data.";
}
// Check if field is known in the message schema
if(fieldNumber.toFixed() in messageSchema === true) {
// Get value from the field payload's data
var value = data.subarray(i, i + length.toNumber());
// Check field's expected type
switch(messageSchema[fieldNumber.toFixed()]["Type"]) {
// String
case ProtocolBuffers.STRING_SCHEMA_DATA_TYPE:
// Append value as a string to result
result[messageSchema[fieldNumber.toFixed()]["Name"]].push((new TextDecoder("utf-8", {"fatal": true})).decode(value));
// Break
break;
// Bytes
case ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE:
// Append value to result
result[messageSchema[fieldNumber.toFixed()]["Name"]].push(value);
// Break
break;
}
}
// Go to next field
i += length.toNumber();
// Break
break;
// I32
case ProtocolBuffers.I32_WIRE_TYPE:
// Check if field payload doesn't contain data
if(i + Common.BYTES_IN_A_UINT32 > data["length"]) {
// Throw error
throw "Field payload doesn't contain data.";
}
// Go to next field
i += Common.BYTES_IN_A_UINT32;
// Break
break;
// Default
default:
// Throw error
throw "Field wire type isn't known.";
}
}
// Return result
return result;
}
// Encode
static encode(messageType, data, schema) {
// Check if schema for message type isn't known
if(messageType.toFixed() in schema === false) {
// Throw error
throw "Schema for message type isn't known.";
}
// Get message schema
var messageSchema = schema[messageType.toFixed()];
// Initialize result
var result = new Uint8Array([]);
// Go through all values in the data
for(var name in data) {
if(data.hasOwnProperty(name) === true) {
// Initialize value found
var valueFound = false;
// Go through all fields in the message schema
for(var fieldNumber in messageSchema) {
if(messageSchema.hasOwnProperty(fieldNumber) === true) {
// Check if field is for the value
if(messageSchema[fieldNumber]["Name"] === name) {
// Set value found
valueFound = true;
// Check field's type
switch(messageSchema[fieldNumber]["Type"]) {
// Uint
case ProtocolBuffers.UINT_SCHEMA_DATA_TYPE:
// Check if value type isn't correct
if(data[name] instanceof BigNumber === false) {
// Throw error
throw "Value's type isn't correct.";
}
// Set field wire type
var fieldWireType = ProtocolBuffers.VARINT_WIRE_TYPE;
// Set field payload
var fieldPayload = ProtocolBuffers.encodeVarint(data[name]);
// Break
break;
// Bool
case ProtocolBuffers.BOOL_SCHEMA_DATA_TYPE:
// Check if value type isn't correct
if(typeof data[name] !== "boolean") {
// Throw error
throw "Value's type isn't correct.";
}
// Set field wire type
var fieldWireType = ProtocolBuffers.VARINT_WIRE_TYPE;
// Set field payload
var fieldPayload = ProtocolBuffers.encodeVarint(new BigNumber((data[name] === true) ? 1 : 0));
// Break
break;
// Enum
case ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE:
// Check if value type isn't correct
if(typeof data[name] !== "number") {
// Throw error
throw "Value's type isn't correct.";
}
// Set field wire type
var fieldWireType = ProtocolBuffers.VARINT_WIRE_TYPE;
// Set field payload
var fieldPayload = ProtocolBuffers.encodeVarint(new BigNumber(data[name]));
// Break
break;
// String
case ProtocolBuffers.STRING_SCHEMA_DATA_TYPE:
// Check if value's type isn't correct
if(typeof data[name] !== "string") {
// Throw error
throw "Value's type isn't correct.";
}
// Set field wire type
var fieldWireType = ProtocolBuffers.LEN_WIRE_TYPE;
// Set field payload
var fieldPayload = Common.mergeArrays([
// Length
ProtocolBuffers.encodeVarint(new BigNumber(data[name]["length"])),
// Data
(new TextEncoder()).encode(data[name])
]);
// Break
break;
// Bytes
case ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE:
// Check if value's type isn't correct
if(data[name] instanceof Uint8Array === false) {
// Throw error
throw "Value's type isn't correct.";
}
// Check if no data exists and data is optional
if(data[name]["length"] === 0 && "Optional" in messageSchema[fieldNumber] === true && messageSchema[fieldNumber]["Optional"] === true) {
// Clear value found
valueFound = false;
}
// Otherwise
else {
// Set field wire type
var fieldWireType = ProtocolBuffers.LEN_WIRE_TYPE;
// Set field payload
var fieldPayload = Common.mergeArrays([
// Length
ProtocolBuffers.encodeVarint(new BigNumber(data[name]["length"])),
// Data
data[name]
]);
}
// Break
break;
// Sint
case ProtocolBuffers.SINT_SCHEMA_DATA_TYPE:
// Check if value type isn't correct
if(data[name] instanceof BigNumber === false) {
// Throw error
throw "Value's type isn't correct.";
}
// Set field wire type
var fieldWireType = ProtocolBuffers.VARINT_WIRE_TYPE;
// Check if value is positive
if(data[name].isPositive() === true) {
// Set field payload
var fieldPayload = ProtocolBuffers.encodeVarint(data[name].multipliedBy(2));
}
// Otherwise
else {
// Set field payload
var fieldPayload = ProtocolBuffers.encodeVarint(data[name].multipliedBy(-2).minus(1));
}
// Break
break;
}
// Break
break;
}
}
}
// Check if value was found in message schema
if(valueFound === true) {
// Set field tag
var fieldTag = ProtocolBuffers.encodeVarint(new BigNumber((fieldNumber << 3) | fieldWireType));
// Append field tag and field payload to the result
result = Common.mergeArrays([
// Result
result,
// Field tag
fieldTag,
// Field payload
fieldPayload
]);
}
}
}
// Return result
return result;
}
// Uint schema data type
static get UINT_SCHEMA_DATA_TYPE() {
// Return uint data type
return 0;
}
// Bool schema data type
static get BOOL_SCHEMA_DATA_TYPE() {
// Return bool data type
return ProtocolBuffers.UINT_SCHEMA_DATA_TYPE + 1;
}
// Enum schema data type
static get ENUM_SCHEMA_DATA_TYPE() {
// Return enum data type
return ProtocolBuffers.BOOL_SCHEMA_DATA_TYPE + 1;
}
// String schema data type
static get STRING_SCHEMA_DATA_TYPE() {
// Return string data type
return ProtocolBuffers.ENUM_SCHEMA_DATA_TYPE + 1;
}
// Bytes schema data type
static get BYTES_SCHEMA_DATA_TYPE() {
// Return bytes data type
return ProtocolBuffers.STRING_SCHEMA_DATA_TYPE + 1;
}
// Sint schema data type
static get SINT_SCHEMA_DATA_TYPE() {
// Return sint data type
return ProtocolBuffers.BYTES_SCHEMA_DATA_TYPE + 1;
}
// Private
// Decode varint
static decodeVarint(data, offset) {
// Initialize payloads
var payloads = [];
// Loop through all bytes in the varint
do {
// Check if data doesn't contain a varint
if(offset >= data["length"]) {
// Throw error
throw "Data doesn't contain a varint.";
}
// Get if continuation bit is set
var continuationBitSet = (data[offset] & 0b10000000) !== 0;
// Append payload to list
payloads.unshift(data[offset] & 0b01111111);
// Increment offset
++offset;
} while(continuationBitSet === true);
// Create bit writer
var bitWriter = new BitWriter();
// Add padding bits to bit writer
bitWriter.setBits(0, payloads["length"] % Common.BITS_IN_A_BYTE);
// Go through all payloads
for(var i = 0; i < payloads["length"]; ++i) {
// Add payload to the bit writer
bitWriter.setBits(payloads[i], 7);
}
// Return number value of the bit writer's bytes
return new BigNumber(Common.HEX_PREFIX + Common.toHexString(bitWriter.getBytes()));
}
// Get varint length
static getVarintLength(data, offset) {
// Initialize length
var length = 0;
// Loop through all bytes in the varint
do {
// Check if data doesn't contain a varint
if(offset + length >= data["length"]) {
// Throw error
throw "Data doesn't contain a varint.";
}
// Get if continuation bit is set
var continuationBitSet = (data[offset + length] & 0b10000000) !== 0;
// Increment length
++length;
} while(continuationBitSet === true);
// Return length
return length;
}
// Encode varint
static encodeVarint(value) {
// Get value as bytes
var bytes = value.toBytes(BigNumber.BIG_ENDIAN);
// Initialize result
var result = (new Uint8Array((bytes["length"] > 1 || bytes[0] >= 0b10000000) ? Math.ceil(bytes["length"] * Common.BITS_IN_A_BYTE / 7) : bytes["length"])).fill(0);
// Initialize bit reader with the bytes
var bitReader = new BitReader(bytes);
// Go through all bytes in the result
for(var i = result["length"] - 1; i >= 0; --i) {
// Set byte's continuation bit
result[i] |= (i === result["length"] - 1) ? 0b00000000 : 0b10000000;
// Check if bytes can be encoded in one payload
if(bytes["length"] === 1 && bytes[0] < 0b10000000) {
// Set byte's payload
result[i] |= bitReader.getBits(8);
}
// Otherwise check at the ending byte and a fewer than seven bits remain
else if(i === result["length"] - 1 && bytes["length"] * Common.BITS_IN_A_BYTE % 7 !== 0) {
// Set byte's payload
result[i] |= bitReader.getBits(bytes["length"] * Common.BITS_IN_A_BYTE % 7);
}
// Otherwise
else {
// Set byte's payload
result[i] |= bitReader.getBits(7);
}
}
// Return result
return result;
}
// Varint wire type
static get VARINT_WIRE_TYPE() {
// Return varint wire type
return 0;
}
// I64 wire type
static get I64_WIRE_TYPE() {
// Return i64 wire type
return ProtocolBuffers.VARINT_WIRE_TYPE + 1;
}
// Len wire type
static get LEN_WIRE_TYPE() {
// Return len wire type
return ProtocolBuffers.I64_WIRE_TYPE + 1;
}
// Sgroup wire type
static get SGROUP_WIRE_TYPE() {
// Return sgroup wire type
return ProtocolBuffers.LEN_WIRE_TYPE + 1;
}
// Egroup wire type
static get EGROUP_WIRE_TYPE() {
// Return egroup wire type
return ProtocolBuffers.SGROUP_WIRE_TYPE + 1;
}
// I32 wire type
static get I32_WIRE_TYPE() {
// Return i32 wire type
return ProtocolBuffers.EGROUP_WIRE_TYPE + 1;
}
}
// Main function
// Set global object's Protocol Buffers
globalThis["ProtocolBuffers"] = ProtocolBuffers;