Skip to content
Merged
Show file tree
Hide file tree
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
28 changes: 26 additions & 2 deletions nbsdecoder.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ export interface NbsTypeSubtypeBuffer extends NbsTypeSubtype {
type: Buffer;
}

/** The timestamps for a type subtype */
export interface TypeIndex {
typeSubType: NbsTypeSubtype;
timestamps: NbsTimestamp[];
}

/**
* A decoder that can be used to read packets from NBS files
*/
Expand All @@ -73,6 +79,13 @@ export declare class NbsDecoder {
*/
public constructor(paths: string[]);

/**
* Get all the timestamps of a specified message type subtype.
*
* @param typeSubtype A type subtype object to get the timestamps for
*/
public getTypeIndex(typeSubtype: NbsTypeSubtype): NbsTimestamp[];

/** Get a list of all the types present in the loaded nbs files */
public getAvailableTypes(): NbsTypeSubtypeBuffer[];

Expand All @@ -82,9 +95,9 @@ export declare class NbsDecoder {
/**
* Get the timestamp range (start, end) for all packets of the given type in the loaded nbs files
*
* @param type A type subtype object to get the timestamp range for
* @param typeSubtype A type subtype object to get the timestamp range for
*/
public getTimestampRange(type: NbsTypeSubtype): [NbsTimestamp, NbsTimestamp];
public getTimestampRange(typeSubtype: NbsTypeSubtype): [NbsTimestamp, NbsTimestamp];

/**
* Get the packets at or before the given timestamp for all types in the loaded nbs files
Expand Down Expand Up @@ -114,6 +127,17 @@ export declare class NbsDecoder {
types: NbsTypeSubtype[]
): NbsPacket[];

/**
* Get the packet of the given type at the given index in the loaded nbs file.
*
* Returns either the packet at the index with the given type, or `undefined` if the given
* index is outside the range of packets for the type.
*
* @param index The index of the requested packet
* @param typeSubtype The type of the requested packet
*/
public getPacketByIndex(index: number, typeSubtype: NbsTypeSubtype): NbsPacket | undefined;

/**
* Get the timestamp to seek to such that all messages of the given types are stepped by (n) steps
*
Expand Down
68 changes: 68 additions & 0 deletions src/Decoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@ namespace nbs {
InstanceMethod<&Decoder::GetTimestampRange>(
"getTimestampRange",
napi_property_attributes(napi_writable | napi_configurable)),
InstanceMethod<&Decoder::GetTypeIndex>("getTypeIndex",
napi_property_attributes(napi_writable | napi_configurable)),
InstanceMethod<&Decoder::GetPackets>("getPackets",
napi_property_attributes(napi_writable | napi_configurable)),
InstanceMethod<&Decoder::GetPacketByIndex>("getPacketByIndex",
napi_property_attributes(napi_writable | napi_configurable)),
InstanceMethod<&Decoder::NextTimestamp>("nextTimestamp",
napi_property_attributes(napi_writable | napi_configurable)),
InstanceMethod<&Decoder::Close>("close", napi_property_attributes(napi_writable | napi_configurable)),
Expand Down Expand Up @@ -106,6 +110,30 @@ namespace nbs {
}
}

Napi::Value Decoder::GetTypeIndex(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();

TypeSubtype typeSubtype;
try {
typeSubtype = this->TypeSubtypeFromJsValue(info[0], env);
}
catch (const std::exception& ex) {
Napi::TypeError::New(env, "invalid type for argument `typeSubtype`: " + std::string(ex.what()))
.ThrowAsJavaScriptException();
return env.Undefined();
}

auto typeIterator = this->index.getIteratorForType(typeSubtype);
auto timestamps = Napi::Array::New(env, std::distance(typeIterator.first, typeIterator.second));

uint64_t idx = 0;
for (auto it = typeIterator.first; it != typeIterator.second; it++) {
timestamps[idx++] = timestamp::ToJsValue(it->item.timestamp, env);
}

return timestamps;
}

Napi::Value Decoder::GetAvailableTypes(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();

Expand Down Expand Up @@ -278,6 +306,46 @@ namespace nbs {
return jsPackets;
}

Napi::Value Decoder::GetPacketByIndex(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();

int64_t index = 0;
auto jsIndex = info[0];
if (jsIndex.IsNumber()) {
index = jsIndex.As<Napi::Number>().Int64Value();
}
else {
Napi::TypeError::New(env, "invalid type for argument `index`: expected integer")
.ThrowAsJavaScriptException();
return env.Undefined();
}

if (index < 0) {
return env.Undefined();
}

TypeSubtype typeSubtype;
try {
typeSubtype = this->TypeSubtypeFromJsValue(info[1], env);
}
catch (const std::exception& ex) {
Napi::TypeError::New(env, "invalid type for argument `typeSubtype`: " + std::string(ex.what()))
.ThrowAsJavaScriptException();
return env.Undefined();
}

auto typeIterator = this->index.getIteratorForType(typeSubtype);

// If the index is out of range return undefined
if (std::distance(typeIterator.first, typeIterator.second) <= index) {
return env.Undefined();
}

auto packetLocation = std::next(typeIterator.first, index);
auto packet = this->Read(*packetLocation);
return Packet::ToJsValue(packet, env);
}

std::vector<Packet> Decoder::GetMatchingPackets(const uint64_t& timestamp, const std::vector<TypeSubtype>& types) {
std::vector<Packet> packets;

Expand Down
6 changes: 6 additions & 0 deletions src/Decoder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ namespace nbs {
/// Returns a JS array of type subtype objects
Napi::Value GetAvailableTypes(const Napi::CallbackInfo& info);

/// Get all the timestamps of a specified message type subtype from the index
Napi::Value GetTypeIndex(const Napi::CallbackInfo& info);

/// Get a list of the available types in the nbs files of this decoder
/// Returns a JS array with two elements: the start timestamp object and the end timestamp object
Napi::Value GetTimestampRange(const Napi::CallbackInfo& info);
Expand All @@ -30,6 +33,9 @@ namespace nbs {
/// Returns a JS array of packet objects
Napi::Value GetPackets(const Napi::CallbackInfo& info);

/// Get the packet at the given index of the given type subtype
Napi::Value GetPacketByIndex(const Napi::CallbackInfo& info);

Napi::Value NextTimestamp(const Napi::CallbackInfo& info);

/**
Expand Down
13 changes: 10 additions & 3 deletions src/Index.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#include "zstr/zstr.hpp"
namespace nbs {

using IndexIterator = std::vector<IndexItemFile>::iterator;

class Index {
public:
/// Empty default constructor
Expand Down Expand Up @@ -79,9 +81,14 @@ namespace nbs {
}

/// Get the iterator range (begin, end) for the given type and subtype in the index
std::pair<std::vector<IndexItemFile>::iterator, std::vector<IndexItemFile>::iterator> getIteratorForType(
const TypeSubtype& type) {
return this->typeMap[type];
std::pair<nbs::IndexIterator, nbs::IndexIterator> getIteratorForType(const TypeSubtype& type) {
// Check that the type actually exists, otherwise the type map will add the given type
// with an empty iterator
auto typeExists = (this->typeMap.find(type) != this->typeMap.end());
if (typeExists == true) {
return this->typeMap[type];
}
return {};
}

/// Get a list of iterator ranges (begin, end) for the given list of type and subtype in the index
Expand Down
101 changes: 101 additions & 0 deletions tests/test_decoder.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,35 @@ test('NbsDecoder constructor throws for invalid arguments', () => {
);
});

test('NbsDecoder.getTypeIndex() returns the correct index for the given type subtype', () => {
const pingIndices = decoder.getTypeIndex({ type: pingType, subtype: 0 });
assert.equal(pingIndices.length, 300);

const pongIndices = decoder.getTypeIndex({ type: pongType, subtype: 0 });
assert.equal(pongIndices.length, 300);

const pang100Indices = decoder.getTypeIndex({ type: pangType, subtype: 100 });
assert.equal(pang100Indices.length, 150);

const pang200Indices = decoder.getTypeIndex({ type: pangType, subtype: 200 });
assert.equal(pang200Indices.length, 150);
});

test('NbsDecoder.getTypeIndex() returns an empty array for types not in the decoder', () => {
const fakePacketIndices = decoder.getTypeIndex({ type: 'fakeIndex', subtype: 0 });
assert.equal(fakePacketIndices.length, 0);
});

test('NbsDecoder.getTypeIndex() returns the correct index with the correct packet timestamps', () => {
const pingIndices = decoder.getTypeIndex({ type: pingType, subtype: 0 });

assert.equal(pingIndices[0], { seconds: 1000, nanos: 0 });
assert.equal(pingIndices[1], { seconds: 1003, nanos: 0 });

const lastIndex = pingIndices.length - 1;
assert.equal(pingIndices[lastIndex], { seconds: 1897, nanos: 0 });
});

test('NbsDecoder.getAvailableTypes() returns a list of all the types available in the nbs files', () => {
const types = decoder.getAvailableTypes();

Expand Down Expand Up @@ -438,6 +467,78 @@ test('NbsDecoder.getPackets() returns the last packet of each type when given a
});
});

test('NbsDecoder.getPacketByIndex() throws for invalid arguments', () => {
assert.throws(
() => {
decoder.getPacketByIndex();
},
/invalid type for argument `index`: expected integer/,
'NbsDecoder.getPacketByIndex() throws for missing `index` argument'
);

assert.throws(
() => {
decoder.getPacketByIndex(true);
},
/invalid type for argument `index`: expected integer/,
'NbsDecoder.getPacketByIndex() throws for incorrect `index` argument type'
);

assert.throws(
() => {
decoder.getPacketByIndex(0);
},
/invalid type for argument `typeSubtype`: expected object/,
'NbsDecoder.getPacketByIndex() throws for missing `type` argument'
);

assert.throws(
() => {
decoder.getPacketByIndex(0, {});
},
/invalid type for argument `typeSubtype`: expected object with `type` and `subtype` keys/,
'NbsDecoder.getPacketByIndex() throws for invalid `type` argument'
);
});

test('NbsDecoder.getPacketByIndex() returns the correct packet for a valid index', () => {
const packet0 = decoder.getPacketByIndex(0, { type: pingType, subtype: 0 });
const packet1 = decoder.getPacketByIndex(1, { type: pingType, subtype: 0 });
const packetLast = decoder.getPacketByIndex(299, { type: pingType, subtype: 0 });

assert.equal(packet0, {
timestamp: { seconds: 1000, nanos: 0 },
type: pingType,
subtype: 0,
payload: Buffer.from('ping.0', 'utf8'),
});

assert.equal(packet1, {
timestamp: { seconds: 1003, nanos: 0 },
type: pingType,
subtype: 0,
payload: Buffer.from('ping.1', 'utf8'),
});

assert.equal(packetLast, {
timestamp: { seconds: 1897, nanos: 0 },
type: pingType,
subtype: 0,
payload: Buffer.from('ping.699', 'utf8'),
});
});

test('NbsDecoder.getPacketByIndex() returns undefined for indices outside the valid range', () => {
// Negative indices
assert.equal(decoder.getPacketByIndex(-1, { type: pingType, subtype: 0 }), undefined);

// Indices one above the max
assert.equal(decoder.getPacketByIndex(300, { type: pingType, subtype: 0 }), undefined);
assert.equal(decoder.getPacketByIndex(300, { type: pongType, subtype: 0 }), undefined);
assert.equal(decoder.getPacketByIndex(150, { type: pangType, subtype: 100 }), undefined);
assert.equal(decoder.getPacketByIndex(150, { type: pangType, subtype: 200 }), undefined);
});

const multiTypeNextTimestampArray = [
{ type: pongType, subtype: 0 },
{ type: pingType, subtype: 0 },
Expand Down