UNPKG

rosbag

Version:

`rosbag` is a node.js & browser compatible module for reading [rosbag](http://wiki.ros.org/rosbag) binary data files.

531 lines (485 loc) 16.6 kB
// Copyright (c) 2018-present, Cruise LLC // This source code is licensed under the Apache License, Version 2.0, // found in the LICENSE file in the root directory of this source tree. // You may not use this file except in compliance with the License. import { MessageReader } from "./MessageReader"; import { MessageWriter } from "./MessageWriter"; import { parseMessageDefinition } from "./parseMessageDefinition"; const getStringBuffer = (str: string) => { const data = Buffer.from(str, "utf8"); const len = Buffer.alloc(4); len.writeInt32LE(data.byteLength, 0); return Buffer.concat([len, data]); }; describe("MessageWriter", () => { describe("simple type", () => { const testNum = <T>(type: string, size: number, expected: T, cb: (buffer: Buffer) => number) => { const buffer = Buffer.alloc(size); cb(buffer); // BigInts are not JSON serializable, so we need to stringify them manually const message = { foo: typeof expected === "bigint" ? `${expected.toString()}n` : expected }; it(`writes message ${JSON.stringify(message)} containing ${type}`, () => { const typeName = "foo_msgs/Bar"; const writer = new MessageWriter(parseMessageDefinition(`${type} foo`, typeName), typeName); expect( writer.writeMessage({ foo: expected, }) ).toEqual(buffer); }); }; testNum("int8", 1, -3, (buffer) => buffer.writeInt8(-3, 0)); testNum("uint8", 1, 13, (buffer) => buffer.writeInt8(13, 0)); testNum("int16", 2, -21, (buffer) => buffer.writeInt16LE(-21, 0)); testNum("uint16", 2, 21, (buffer) => buffer.writeUInt16LE(21, 0)); testNum("int32", 4, -210010, (buffer) => buffer.writeInt32LE(-210010, 0)); testNum("uint32", 4, 210010, (buffer) => buffer.writeUInt32LE(210010, 0)); testNum("float32", 4, 5.5, (buffer) => buffer.writeFloatLE(5.5, 0)); testNum("float64", 8, 1.7976931348623157e308, (buffer) => buffer.writeDoubleLE(1.7976931348623157e308, 0)); testNum("int64", 8, Number.MAX_SAFE_INTEGER, (buffer) => buffer.writeBigInt64LE(BigInt(Number.MAX_SAFE_INTEGER), 0) ); testNum("int64", 8, BigInt(Number.MAX_SAFE_INTEGER), (buffer) => buffer.writeBigInt64LE(BigInt(Number.MAX_SAFE_INTEGER), 0) ); testNum("uint64", 8, Number.MAX_SAFE_INTEGER, (buffer) => buffer.writeBigUInt64LE(BigInt(Number.MAX_SAFE_INTEGER), 0) ); testNum("uint64", 8, BigInt(Number.MAX_SAFE_INTEGER), (buffer) => buffer.writeBigUInt64LE(BigInt(Number.MAX_SAFE_INTEGER), 0) ); it("writes strings", () => { const typeName = "foo_msgs/Bar"; const writer = new MessageWriter(parseMessageDefinition("string name", typeName), typeName); const buff = getStringBuffer("test"); expect(writer.writeMessage({ name: "test" })).toEqual(buff); }); it("writes JSON", () => { const typeName = "custom_type/User"; const writer = new MessageWriter( parseMessageDefinition("#pragma rosbag_parse_json\nstring dummy", typeName), typeName ); const buff = getStringBuffer('{"foo":123,"bar":{"nestedFoo":456}}'); expect( writer.writeMessage({ dummy: { foo: 123, bar: { nestedFoo: 456 } }, }) ).toEqual(buff); const writerWithNestedComplexType = new MessageWriter( parseMessageDefinition( `#pragma rosbag_parse_json string dummy Account account ============ MSG: custom_type/Account string name uint16 id `, typeName ), typeName ); expect( writerWithNestedComplexType.writeMessage({ dummy: { foo: 123, bar: { nestedFoo: 456 } }, account: { name: '{"first":"First","last":"Last"}}', id: 100 }, }) ).toEqual(Buffer.concat([buff, getStringBuffer('{"first":"First","last":"Last"}}'), Buffer.from([100, 0x00])])); const writerWithTrailingPragmaComment = new MessageWriter( parseMessageDefinition( `#pragma rosbag_parse_json string dummy Account account #pragma rosbag_parse_json ============ MSG: custom_type/Account string name uint16 id `, typeName ), typeName ); expect( writerWithTrailingPragmaComment.writeMessage({ dummy: { foo: 123, bar: { nestedFoo: 456 } }, account: { name: '{"first":"First","last":"Last"}}', id: 100 }, }) ).toEqual(Buffer.concat([buff, getStringBuffer('{"first":"First","last":"Last"}}'), Buffer.from([100, 0x00])])); }); it("writes time", () => { const typeName = "foo_msgs/Bar"; const writer = new MessageWriter(parseMessageDefinition("time right_now", typeName), typeName); const buff = Buffer.alloc(8); const now = new Date(); now.setSeconds(31); now.setMilliseconds(0); const seconds = Math.round(now.getTime() / 1000); buff.writeUInt32LE(seconds, 0); buff.writeUInt32LE(1000000, 4); now.setMilliseconds(1); expect( writer.writeMessage({ right_now: { nsec: 1000000, sec: seconds, }, }) ).toEqual(buff); }); }); describe("array", () => { it("writes variable length string array", () => { const typeName = "foo_msgs/Bar"; const writer = new MessageWriter(parseMessageDefinition("string[] names", typeName), typeName); const buffer = Buffer.concat([ // variable length array has int32 as first entry Buffer.from([0x03, 0x00, 0x00, 0x00]), getStringBuffer("foo"), getStringBuffer("bar"), getStringBuffer("baz"), ]); expect( writer.writeMessage({ names: ["foo", "bar", "baz"], }) ).toEqual(buffer); }); it("writes fixed length arrays", () => { const typeName = "foo_msgs/Bar"; const writer1 = new MessageWriter(parseMessageDefinition("string[1] names", typeName), typeName); const writer2 = new MessageWriter(parseMessageDefinition("string[2] names", typeName), typeName); const writer3 = new MessageWriter(parseMessageDefinition("string[3] names", typeName), typeName); expect( writer1.writeMessage({ names: ["foo", "bar", "baz"], }) ).toEqual(getStringBuffer("foo")); expect( writer2.writeMessage({ names: ["foo", "bar", "baz"], }) ).toEqual(Buffer.concat([getStringBuffer("foo"), getStringBuffer("bar")])); expect( writer3.writeMessage({ names: ["foo", "bar", "baz"], }) ).toEqual(Buffer.concat([getStringBuffer("foo"), getStringBuffer("bar"), getStringBuffer("baz")])); }); it("does not write any data for a zero length array", () => { const typeName = "foo_msgs/Bar"; const writer = new MessageWriter(parseMessageDefinition("string[] names", typeName), typeName); const buffer = Buffer.concat([ // variable length array has int32 as first entry Buffer.from([0x00, 0x00, 0x00, 0x00]), ]); const resultBuffer = writer.writeMessage({ names: [] }); expect(resultBuffer).toEqual(buffer); }); describe("typed arrays", () => { it("writes a uint8[]", () => { const typeName = "foo_msgs/Bar"; const writer = new MessageWriter(parseMessageDefinition("uint8[] values\nuint8 after", typeName), typeName); const message = { values: Uint8Array.from([1, 2, 3]), after: 4 }; const result = writer.writeMessage(message); const buffer = Buffer.from([0x03, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04]); expect(result).toEqual(buffer); }); it("writes a uint8[] with a fixed length", () => { const typeName = "foo_msgs/Bar"; const writer = new MessageWriter(parseMessageDefinition("uint8[3] values\nuint8 after", typeName), typeName); const message = { values: Uint8Array.from([1, 2, 3]), after: 4 }; const result = writer.writeMessage(message); const buffer = Buffer.from([0x01, 0x02, 0x03, 0x04]); expect(result).toEqual(buffer); }); }); }); describe("complex types", () => { it("writes single complex type", () => { const typeName = "foo_msgs/Bar"; const writer = new MessageWriter( parseMessageDefinition("string firstName \n string lastName\nuint16 age", typeName), typeName ); const buffer = Buffer.concat([getStringBuffer("foo"), getStringBuffer("bar"), Buffer.from([0x05, 0x00])]); const message = { firstName: "foo", lastName: "bar", age: 5, }; expect(writer.writeMessage(message)).toEqual(buffer); }); it("writes nested complex types", () => { const messageDefinition = ` string username Account account ============ MSG: custom_type/Account string name uint16 id `; const typeName = "custom_type/User"; const writer = new MessageWriter(parseMessageDefinition(messageDefinition, typeName), typeName); const buffer = Buffer.concat([getStringBuffer("foo"), getStringBuffer("bar"), Buffer.from([100, 0x00])]); const message = { username: "foo", account: { name: "bar", id: 100, }, }; expect(writer.writeMessage(message)).toEqual(buffer); }); it("writes nested complex types with arrays", () => { const messageDefinition = ` string username Account[] accounts ============ MSG: custom_type/Account string name uint16 id `; const typeName = "custom_type/User"; const writer = new MessageWriter(parseMessageDefinition(messageDefinition, typeName), typeName); const buffer = Buffer.concat([ getStringBuffer("foo"), // uint32LE length of array (2) Buffer.from([0x02, 0x00, 0x00, 0x00]), getStringBuffer("bar"), Buffer.from([100, 0x00]), getStringBuffer("baz"), Buffer.from([101, 0x00]), ]); const message = { username: "foo", accounts: [ { name: "bar", id: 100, }, { name: "baz", id: 101, }, ], }; expect(writer.writeMessage(message)).toEqual(buffer); }); it("writes complex type with nested arrays", () => { const messageDefinition = ` string username Account[] accounts ============ MSG: custom_type/Account string name uint16 id Photo[] photos ======= MSG: custom_type/Photo string url uint8 id `; const typeName = "custom_type/User"; const writer = new MessageWriter(parseMessageDefinition(messageDefinition, typeName), typeName); const buffer = Buffer.concat([ getStringBuffer("foo"), // uint32LE length of Account array (2) Buffer.from([0x02, 0x00, 0x00, 0x00]), // name getStringBuffer("bar"), // id Buffer.from([100, 0x00]), // uint32LE length of Photo array (3) Buffer.from([0x03, 0x00, 0x00, 0x00]), // photo url getStringBuffer("http://foo.com"), // photo id Buffer.from([10]), // photo url getStringBuffer("http://bar.com"), // photo id Buffer.from([12]), // photo url getStringBuffer("http://zug.com"), // photo id Buffer.from([16]), // next account getStringBuffer("baz"), Buffer.from([101, 0x00]), // uint32LE length of Photo array (0) Buffer.from([0x00, 0x00, 0x00, 0x00]), ]); const message = { username: "foo", accounts: [ { name: "bar", id: 100, photos: [ { url: "http://foo.com", id: 10, }, { url: "http://bar.com", id: 12, }, { url: "http://zug.com", id: 16, }, ], }, { name: "baz", id: 101, photos: [], }, ], }; expect(writer.writeMessage(message)).toEqual(buffer); }); const withBytesAndBools = ` byte OK=0 byte WARN=1 byte ERROR=2 byte STALE=3 byte FOO = 3 # the space exists in some topics byte FLOAT64 = 8 bool level\t\t# level of operation enumerated above DiagnosticStatus status ================================================================================ MSG: diagnostic_msgs/DiagnosticStatus # This message holds the status of an individual component of the robot. # # Possible levels of operations byte OK=0 byte WARN=1 # Comment # FLOATING OUT HERE byte ERROR=2 byte STALE=3 byte level # level of operation enumerated above string name # a description of the test/component reporting`; it("writes bytes and constants", () => { const typeName = "diagnostic_msgs/DiagnosticArray"; const writer = new MessageWriter(parseMessageDefinition(withBytesAndBools, typeName), typeName); const buffer = Buffer.concat([Buffer.from([0x01]), Buffer.from([0x00]), getStringBuffer("foo")]); const message = { level: true, status: { leve: 0, name: "foo", }, }; expect(writer.writeMessage(message)).toEqual(buffer); }); }); describe("calculateBufferSize", () => { it("with a complex type", () => { const messageDefinition = ` string username Account[] accounts ============ MSG: custom_type/Account string name uint16 id bool isActive Photo[] photos ======= MSG: custom_type/Photo string url uint8[] ids `; const message = { username: "foo", accounts: [ { name: "bar", id: 100, isActive: true, photos: [ { url: "http://foo.com", ids: Uint8Array.from([10, 100]), }, { url: "http://bar.com", ids: Uint8Array.from([12]), }, { url: "http://zug.com", ids: Uint8Array.from([]), }, ], }, { name: "baz", id: 101, isActive: false, photos: [], }, ], }; const typeName = "custom_type/User"; const writer = new MessageWriter(parseMessageDefinition(messageDefinition, typeName), typeName); expect(writer.calculateBufferSize(message)).toEqual(108); }); }); describe("MessageReader and MessageWriter outputs are compatible", () => { it("with a complex type", () => { const messageDefinition = ` string username Account[] accounts uint64 favoriteNumber ============ MSG: custom_type/Account string name uint16 id bool isActive Photo[] photos ======= MSG: custom_type/Photo string url uint8[] ids `; const maxUint64 = BigInt(2) ** BigInt(64) - BigInt(1); const message = { username: "foo", favoriteNumber: maxUint64, accounts: [ { name: "bar", id: 100, isActive: true, photos: [ { url: "http://foo.com", ids: Uint8Array.from([10, 100]), }, { url: "http://bar.com", ids: Uint8Array.from([12]), }, { url: "http://zug.com", ids: Uint8Array.from([]), }, ], }, { name: "baz", id: 101, isActive: false, photos: [], }, ], }; const typeName = "custom_type/User"; const parsedDefinition = parseMessageDefinition(messageDefinition, typeName); const reader = new MessageReader(parsedDefinition, typeName); const writer = new MessageWriter(parsedDefinition, typeName); expect(reader.readMessage(writer.writeMessage(message))).toEqual(message); }); }); });