Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix JS array types #369

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 103 additions & 43 deletions javascript/core/sparkplug-payload/lib/sparkplugbpayload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ function setValue (type: number, value: UserValue, object: IMetric | IPropertyVa
case 24:
(object as IMetric).bytesValue = encodeInt32Array(value as Array<number>);
break;
case 25:
(object as IMetric).bytesValue = encodeInt64Array(value as Array<number>);
break;
case 26:
(object as IMetric).bytesValue = encodeUInt8Array(value as Array<number>);
break;
Expand All @@ -174,6 +177,9 @@ function setValue (type: number, value: UserValue, object: IMetric | IPropertyVa
case 28:
(object as IMetric).bytesValue = encodeUInt32Array(value as Array<number>);
break;
case 29:
(object as IMetric).bytesValue = encodeUInt64Array(value as Array<number>);
break;
case 30:
(object as IMetric).bytesValue = encodeFloatArray(value as Array<number>);
break;
Expand Down Expand Up @@ -242,12 +248,16 @@ function getValue<T extends UserValue> (type: number | null | undefined, object:
return decodeInt16Array((object as IMetric).bytesValue!) as T;
case 24:
return decodeInt32Array((object as IMetric).bytesValue!) as T;
case 25:
return decodeInt64Array((object as IMetric).bytesValue!) as T;
case 26:
return decodeUInt8Array((object as IMetric).bytesValue!) as T;
case 27:
return decodeUInt16Array((object as IMetric).bytesValue!) as T;
case 28:
return decodeUInt32Array((object as IMetric).bytesValue!) as T;
case 29:
return decodeUInt64Array((object as IMetric).bytesValue!) as T;
case 30:
return decodeFloatArray((object as IMetric).bytesValue!) as T;
case 31:
Expand Down Expand Up @@ -374,12 +384,16 @@ function encodeType(typeString: string): number {
return 23;
case "INT32ARRAY":
return 24;
case "INT64ARRAY":
return 25;
case "UINT8ARRAY":
return 26;
case "UINT16ARRAY":
return 27;
case "UINT32ARRAY":
return 28;
case "UINT64ARRAY":
return 29;
case "FLOATARRAY":
return 30;
case "DOUBLEARRAY":
Expand Down Expand Up @@ -445,12 +459,16 @@ function decodeType (typeInt: number | null | undefined): TypeStr {
return "Int16Array";
case 24:
return "Int32Array";
case 25:
return "Int64Array";
case 26:
return "UInt8Array";
case 27:
return "UInt16Array";
case 28:
return "UInt32Array";
case 29:
return "UInt64Array";
case 30:
return "FloatArray";
case 31:
Expand Down Expand Up @@ -900,6 +918,28 @@ function decodeUInt32Array(array: Uint8Array | null) {
return unpackValues(array, 'I');
}

function encodeInt64Array(array: any[]) {
return packValues(array, 'l');
}

function decodeInt64Array(array: Uint8Array | null) {
if (array === null) {
return null;
}
return unpackValues(array, 'l');
}

function encodeUInt64Array(array: any[]) {
return packValues(array, 'L');
}

function decodeUInt64Array(array: Uint8Array | null) {
if (array === null) {
return null;
}
return unpackValues(array, 'L');
}

function encodeFloatArray(array: any[]) {
return packValues(array, 'f');
}
Expand All @@ -922,66 +962,84 @@ function decodeDoubleArray(array: Uint8Array | null) {
return unpackValues(array, 'd');
}

function unpackValues(packed_bytes: Uint8Array, format_specifier: string): number[] {
function unpackValues(packed_bytes: Uint8Array, format_specifier: string): (number | Long)[] {
const data_view = new DataView(packed_bytes.buffer, packed_bytes.byteOffset, packed_bytes.byteLength);
const decodeFunc = {
'b': data_view.getInt8.bind(data_view),
'B': data_view.getUint8.bind(data_view),
'h': data_view.getInt16.bind(data_view, 0, true),
'H': data_view.getUint16.bind(data_view, 0, true),
'i': data_view.getInt32.bind(data_view, 0, true),
'I': data_view.getUint32.bind(data_view, 0, true),
'f': data_view.getFloat32.bind(data_view, 0, true),
'd': data_view.getFloat64.bind(data_view, 0, true),
}[format_specifier];
if (!decodeFunc) {
throw new Error(`Unsupported format specifier: ${format_specifier}`);
}
const values = [];
const typeSize = getTypeSize(format_specifier);
for (let i = 0; i < packed_bytes.length / typeSize; i++) {
values.push(decodeFunc(i * typeSize));
for (let byteOffset = 0; byteOffset < packed_bytes.length; byteOffset += typeSize) {
switch (format_specifier) {
case 'b':
values.push(data_view.getInt8(byteOffset));
break;
case 'B':
values.push(data_view.getUint8(byteOffset));
break;
case 'h':
values.push(data_view.getInt16(byteOffset, true));
break;
case 'H':
values.push(data_view.getUint16(byteOffset, true));
break;
case 'i':
values.push(data_view.getInt32(byteOffset, true));
break;
case 'I':
values.push(data_view.getUint32(byteOffset, true));
break;
case 'l':
values.push(Long.fromString(data_view.getBigInt64(byteOffset, true).toString(), false));
break;
case 'L':
values.push(Long.fromString(data_view.getBigUint64(byteOffset, true).toString(), true));
break;
case 'f':
values.push(data_view.getFloat32(byteOffset, true));
break;
case 'd':
values.push(data_view.getFloat64(byteOffset, true));
break;
default:
throw new Error(`Unsupported format specifier: ${format_specifier}`);
}
}
return values;
}

function packValues(values: any[], format_specifier: string): Uint8Array {
const dataView = new DataView(new ArrayBuffer(values.length * getTypeSize(format_specifier)));
let byteOffset = 0;
for (let i = 0; i < values.length; i++) {
const typeSize = getTypeSize(format_specifier);
const dataView = new DataView(new ArrayBuffer(values.length * typeSize));
for (let i = 0, byteOffset = 0; i < values.length; i++, byteOffset += typeSize) {
const value = values[i];
switch (format_specifier) {
case 'b':
dataView.setInt8(byteOffset, value);
byteOffset += 1;
break;
case 'B':
dataView.setUint8(byteOffset, value);
byteOffset += 1;
break;
case 'h':
dataView.setInt16(byteOffset, value, true);
byteOffset += 2;
break;
case 'H':
dataView.setUint16(byteOffset, value, true);
byteOffset += 2;
break;
case 'i':
dataView.setInt32(byteOffset, value, true);
byteOffset += 4;
break;
case 'I':
dataView.setUint32(byteOffset, value, true);
byteOffset += 4;
break;
case 'l':
dataView.setBigInt64(byteOffset, BigInt(value), true);
break;
case 'L':
dataView.setBigUint64(byteOffset, BigInt(value), true);
break;
case 'f':
dataView.setFloat32(byteOffset, value, true);
byteOffset += 4;
break;
case 'd':
dataView.setFloat64(byteOffset, value, true);
byteOffset += 8;
break;
default:
throw new Error(`Unsupported format specifier: ${format_specifier}`);
Expand All @@ -992,14 +1050,16 @@ function packValues(values: any[], format_specifier: string): Uint8Array {

function getTypeSize(format_specifier: string): number {
const sizeMap: {[key: string]: number} = {
'b': 1,
'B': 1,
'h': 2,
'H': 2,
'i': 4,
'I': 4,
'f': 4,
'd': 8,
'b': Int8Array.BYTES_PER_ELEMENT,
'B': Uint8Array.BYTES_PER_ELEMENT,
'h': Int16Array.BYTES_PER_ELEMENT,
'H': Uint16Array.BYTES_PER_ELEMENT,
'i': Int32Array.BYTES_PER_ELEMENT,
'I': Uint32Array.BYTES_PER_ELEMENT,
'l': BigInt64Array.BYTES_PER_ELEMENT,
'L': BigUint64Array.BYTES_PER_ELEMENT,
'f': Float32Array.BYTES_PER_ELEMENT,
'd': Float64Array.BYTES_PER_ELEMENT,
};
const size = sizeMap[format_specifier];
if (!size) {
Expand All @@ -1018,31 +1078,31 @@ function encodeBooleanArray(booleanArray: boolean[]): Uint8Array {
for (let i = 0; i < booleanArray.length; i++) {
const value = booleanArray[i];
const byteIndex = Math.floor(i / 8);
const bitIndex = i % 8;
const bitIndex = 7 - i % 8;
packedBytes[byteIndex] |= (value ? 1 : 0) << bitIndex;
}

// return the packed bytes preceded by a 4-byte integer representing the number of boolean values
const lengthBytes = new Uint8Array(new Uint32Array([booleanArray.length]).buffer);
const result = new Uint8Array(lengthBytes.length + packedBytes.length);
result.set(lengthBytes);
result.set(packedBytes, lengthBytes.length);
const result = new Uint8Array(4 + packedBytes.length);
const data_view = new DataView(result.buffer);
data_view.setUint32(0, booleanArray.length, true); // set the first 4 bytes
result.set(packedBytes, 4);

return result;
}

function decodeBooleanArray(packedBytes: Uint8Array): boolean[] {
const data_view = new DataView(packedBytes.buffer, packedBytes.byteOffset, packedBytes.byteLength);
// extract the length of the boolean array from the first 4 bytes of the packed bytes
const lengthBytes = packedBytes.slice(0, 4);
const length = new Uint32Array(lengthBytes.buffer)[0];
const length = data_view.getUint32(0, true);

// create a boolean array of the appropriate length
const booleanArray = new Array<boolean>(length);

// iterate over each bit in the packed bytes and set the corresponding boolean value in the boolean array
for (let i = 0; i < length; i++) {
const byteIndex = Math.floor(i / 8);
const bitIndex = i % 8;
const bitIndex = 7 - i % 8;
const mask = 1 << bitIndex;
booleanArray[i] = (packedBytes[byteIndex + 4] & mask) !== 0;
}
Expand Down