diff --git a/package-lock.json b/package-lock.json index 94fd2a57c..b27a6b9d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "roslib", - "version": "2.0.0-alpha1", + "version": "2.0.0-alpha6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "roslib", - "version": "2.0.0-alpha1", + "version": "2.0.0-alpha6", "license": "BSD-2-Clause", "dependencies": { "@xmldom/xmldom": "^0.9.8", "bson": "^7.0.0", "buffer": "^6.0.3", - "cbor-js": "^0.1.0", + "cbor2": "^2.0.1", "eventemitter3": "^5.0.1", "fast-png": "^7.0.1", "uuid": "^13.0.0", @@ -328,6 +328,15 @@ "node": ">=18" } }, + "node_modules/@cto.af/wtf8": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@cto.af/wtf8/-/wtf8-0.0.2.tgz", + "integrity": "sha512-ATm4UQiKrdm5GnU6BvIwUDN+LDEtt23zuzKFpnfDT59ULAd0aMYm/nSFzbSO02garLcXumRC13PzNfa7BsfvSg==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/@es-joy/jsdoccomment": { "version": "0.76.0", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.76.0.tgz", @@ -3255,10 +3264,17 @@ "node": ">=6" } }, - "node_modules/cbor-js": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/cbor-js/-/cbor-js-0.1.0.tgz", - "integrity": "sha1-yAzmEg84fo+qdDcN/aIdlluPx/k=" + "node_modules/cbor2": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cbor2/-/cbor2-2.0.1.tgz", + "integrity": "sha512-9bE8+tueGxONyxpttNKkAKKcGVtAPeoSJ64AjVTTjEuBOuRaeeP76EN9BbmQqkz1ZeTP0QPvksNBKwvEutIUzQ==", + "license": "MIT", + "dependencies": { + "@cto.af/wtf8": "0.0.2" + }, + "engines": { + "node": ">=20" + } }, "node_modules/ccount": { "version": "2.0.1", diff --git a/package.json b/package.json index 056458c93..e3347b28a 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@xmldom/xmldom": "^0.9.8", "bson": "^7.0.0", "buffer": "^6.0.3", - "cbor-js": "^0.1.0", + "cbor2": "^2.0.1", "eventemitter3": "^5.0.1", "fast-png": "^7.0.1", "uuid": "^13.0.0", diff --git a/src/core/transport/Transport.ts b/src/core/transport/Transport.ts index 7dd5baac9..5f4ad0dda 100644 --- a/src/core/transport/Transport.ts +++ b/src/core/transport/Transport.ts @@ -11,8 +11,7 @@ import { isRosbridgePngMessage, } from "../../types/protocol.ts"; import { deserialize } from "bson"; -import CBOR from "cbor-js"; -import typedArrayTagger from "../../util/cborTypedArrayTags.ts"; +import { decode } from "cbor2"; import decompressPng from "../../util/decompressPng.ts"; /** @@ -221,7 +220,7 @@ export abstract class AbstractTransport * It is one technique for compressing JSON data. */ private handleCborMessage(cbor: ArrayBuffer) { - const data: unknown = CBOR.decode(cbor, typedArrayTagger); + const data: unknown = decode(new Uint8Array(cbor)); if (isRosbridgeMessage(data)) { this.handleRosbridgeMessage(data); } else { diff --git a/src/types/cbor-js.d.ts b/src/types/cbor-js.d.ts deleted file mode 100644 index c6f548aca..000000000 --- a/src/types/cbor-js.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -declare module "cbor-js" { - function encode(data: unknown): ArrayBuffer; - - function decode( - data: ArrayBufferLike, - tagger: (data: Uint8Array, tag: number) => unknown, - ): unknown; -} diff --git a/src/util/cborTypedArrayTags.ts b/src/util/cborTypedArrayTags.ts deleted file mode 100644 index ef7166e03..000000000 --- a/src/util/cborTypedArrayTags.ts +++ /dev/null @@ -1,137 +0,0 @@ -const UPPER32 = Math.pow(2, 32); - -let warnedPrecision = false; -function warnPrecision() { - if (!warnedPrecision) { - warnedPrecision = true; - console.warn( - "CBOR 64-bit integer array values may lose precision. No further warnings.", - ); - } -} - -/** - * Unpack 64-bit unsigned integer from byte array. - * @param bytes - */ -function decodeUint64LE(bytes: Uint8Array) { - warnPrecision(); - - const byteLen = bytes.byteLength; - const offset = bytes.byteOffset; - const arrLen = byteLen / 8; - - const buffer = bytes.buffer.slice(offset, offset + byteLen); - const uint32View = new Uint32Array(buffer); - - const arr = new Array(arrLen); - for (let i = 0; i < arrLen; i++) { - const si = i * 2; - const lo = uint32View[si]; - const hi = uint32View[si + 1]; - if (lo === undefined || hi === undefined) { - throw new Error("Invalid byte array"); - } - arr[i] = lo + UPPER32 * hi; - } - - return arr; -} - -/** - * Unpack 64-bit signed integer from byte array. - * @param bytes - */ -function decodeInt64LE(bytes: Uint8Array) { - warnPrecision(); - - const byteLen = bytes.byteLength; - const offset = bytes.byteOffset; - const arrLen = byteLen / 8; - - const buffer = bytes.buffer.slice(offset, offset + byteLen); - const uint32View = new Uint32Array(buffer); - const int32View = new Int32Array(buffer); - - const arr = new Array(arrLen); - for (let i = 0; i < arrLen; i++) { - const si = i * 2; - const lo = uint32View[si]; - const hi = int32View[si + 1]; - if (lo === undefined || hi === undefined) { - throw new Error("Invalid byte array"); - } - arr[i] = lo + UPPER32 * hi; - } - - return arr; -} - -/** - * Unpack typed array from byte array. - * @param bytes - * @param ArrayType - Desired output array type - */ -function decodeNativeArray( - bytes: Uint8Array, - ArrayType: TypedArrayConstructor, -) { - const byteLen = bytes.byteLength; - const offset = bytes.byteOffset; - const buffer = bytes.buffer.slice(offset, offset + byteLen); - return new ArrayType(buffer); -} - -type TypedArrayConstructor = - | Uint8ArrayConstructor - | Uint16ArrayConstructor - | Uint32ArrayConstructor - | Int8ArrayConstructor - | Int16ArrayConstructor - | Int32ArrayConstructor - | Float32ArrayConstructor - | Float64ArrayConstructor; - -/** - * Supports a subset of draft CBOR typed array tags: - * - * - * Only supports little-endian tags for now. - */ -const nativeArrayTypes: Record = { - 64: Uint8Array, - 69: Uint16Array, - 70: Uint32Array, - 72: Int8Array, - 77: Int16Array, - 78: Int32Array, - 85: Float32Array, - 86: Float64Array, -}; - -/** - * We can also decode 64-bit integer arrays, since ROS has these types. - */ -const conversionArrayTypes: Record number[]> = { - 71: decodeUint64LE, - 79: decodeInt64LE, -}; - -/** - * Handle CBOR typed array tags during decoding. - * @param data - * @param tag - */ -export default function cborTypedArrayTagger( - data: Uint8Array, - tag: number, -) { - const arrayType = nativeArrayTypes[tag]; - if (arrayType) { - return decodeNativeArray(data, arrayType); - } - if (tag in conversionArrayTypes) { - return conversionArrayTypes[tag]?.(data); - } - return data; -} diff --git a/test/cbor.test.ts b/test/cbor.test.ts index b6166ed98..4258828b2 100644 --- a/test/cbor.test.ts +++ b/test/cbor.test.ts @@ -1,6 +1,5 @@ import { describe, it, expect } from "vitest"; -import CBOR from "cbor-js"; -import cborTypedArrayTagger from "../src/util/cborTypedArrayTags.ts"; +import { decode } from "cbor2"; /** Convert hex string to ArrayBuffer. */ function hexToBuffer(hex: string) { @@ -11,13 +10,13 @@ function hexToBuffer(hex: string) { const arr = tokens.map(function (t) { return parseInt(t, 16); }); - return new Uint8Array(arr).buffer; + return new Uint8Array(arr); } describe("CBOR Typed Array Tagger", function () { it("should convert tagged Uint16Array", function () { const data = hexToBuffer("d84546010002000300"); - const msg = CBOR.decode(data, cborTypedArrayTagger); + const msg = decode(data); if (!(msg instanceof Uint16Array)) { throw new Error("Expected Uint16Array"); @@ -30,7 +29,7 @@ describe("CBOR Typed Array Tagger", function () { it("should convert tagged Uint32Array", function () { const data = hexToBuffer("d8464c010000000200000003000000"); - const msg = CBOR.decode(data, cborTypedArrayTagger); + const msg = decode(data); if (!(msg instanceof Uint32Array)) { throw new Error("Expected Uint32Array"); @@ -41,24 +40,24 @@ describe("CBOR Typed Array Tagger", function () { expect(msg[2]).to.equal(3); }); - it("should convert tagged Uint64Array", function () { + it("should convert tagged BigUint64Array", function () { const data = hexToBuffer( "d8475818010000000000000002000000000000000300000000000000", ); - const msg = CBOR.decode(data, cborTypedArrayTagger); + const msg = decode(data); - if (!Array.isArray(msg)) { - throw new Error("Expected Array"); + if (!(msg instanceof BigUint64Array)) { + throw new Error("Expected BigUint64Array"); } expect(msg).to.have.lengthOf(3); - expect(msg[0]).to.equal(1); - expect(msg[1]).to.equal(2); - expect(msg[2]).to.equal(3); + expect(msg[0]).to.equal(BigInt(1)); + expect(msg[1]).to.equal(BigInt(2)); + expect(msg[2]).to.equal(BigInt(3)); }); it("should convert tagged Int8Array", function () { const data = hexToBuffer("d8484301fe03"); - const msg = CBOR.decode(data, cborTypedArrayTagger); + const msg = decode(data); if (!(msg instanceof Int8Array)) { throw new Error("Expected Int8Array"); @@ -71,7 +70,7 @@ describe("CBOR Typed Array Tagger", function () { it("should convert tagged Int16Array", function () { const data = hexToBuffer("d84d460100feff0300"); - const msg = CBOR.decode(data, cborTypedArrayTagger); + const msg = decode(data); if (!(msg instanceof Int16Array)) { throw new Error("Expected Int16Array"); @@ -84,7 +83,7 @@ describe("CBOR Typed Array Tagger", function () { it("should convert tagged Int32Array", function () { const data = hexToBuffer("d84e4c01000000feffffff03000000"); - const msg = CBOR.decode(data, cborTypedArrayTagger); + const msg = decode(data); if (!(msg instanceof Int32Array)) { throw new Error("Expected Int32Array"); @@ -95,24 +94,24 @@ describe("CBOR Typed Array Tagger", function () { expect(msg[2]).to.equal(3); }); - it("should convert tagged Int64Array", function () { + it("should convert tagged BigInt64Array", function () { const data = hexToBuffer( "d84f58180100000000000000feffffffffffffff0300000000000000", ); - const msg = CBOR.decode(data, cborTypedArrayTagger); + const msg = decode(data); - if (!Array.isArray(msg)) { - throw new Error("Expected Array"); + if (!(msg instanceof BigInt64Array)) { + throw new Error("Expected BigInt64Array"); } expect(msg).to.have.lengthOf(3); - expect(msg[0]).to.equal(1); - expect(msg[1]).to.equal(-2); - expect(msg[2]).to.equal(3); + expect(msg[0]).to.equal(BigInt(1)); + expect(msg[1]).to.equal(BigInt(-2)); + expect(msg[2]).to.equal(BigInt(3)); }); it("should convert tagged Float32Array", function () { const data = hexToBuffer("d8554ccdcc8c3fcdcc0cc033335340"); - const msg = CBOR.decode(data, cborTypedArrayTagger); + const msg = decode(data); if (!(msg instanceof Float32Array)) { throw new Error("Expected Float32Array"); @@ -127,7 +126,7 @@ describe("CBOR Typed Array Tagger", function () { const data = hexToBuffer( "d85658189a9999999999f13f9a999999999901c06666666666660a40", ); - const msg = CBOR.decode(data, cborTypedArrayTagger); + const msg = decode(data); if (!(msg instanceof Float64Array)) { throw new Error("Expected Float64Array"); @@ -140,7 +139,7 @@ describe("CBOR Typed Array Tagger", function () { it("should be able to unpack two typed arrays", function () { const data = hexToBuffer("82d8484308fe05d84d460100feff0300"); - const msg = CBOR.decode(data, cborTypedArrayTagger); + const msg = decode(data); if (!Array.isArray(msg)) { throw new Error("Expected Array"); diff --git a/test/transport.test.ts b/test/transport.test.ts index 9e0cb9003..d8eccc465 100644 --- a/test/transport.test.ts +++ b/test/transport.test.ts @@ -8,7 +8,7 @@ import type { RosbridgeMessage, RosbridgePngMessage, } from "../src/types/protocol.ts"; -import CBOR from "cbor-js"; +import { encode } from "cbor2"; import * as fastpng from "fast-png"; import * as bson from "bson"; import * as ws from "ws"; @@ -287,8 +287,9 @@ describe("Transport", () => { }); it("should handle CBOR message", async () => { - const successMessage = CBOR.encode({ op: "test" }); - const failureMessage = CBOR.encode({ foo: "bar" }); + // CBOR data comes as ArrayBuffer from WebSocket, not Uint8Array + const successMessage = encode({ op: "test" }).buffer; + const failureMessage = encode({ foo: "bar" }).buffer; // -- SUCCESS -- //