Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion demo/node/rntuple.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,4 @@ else
console.log('test 3 - readString passed');
else
console.error('FAILURE: test 3 - readString does not match');
}
}
25 changes: 18 additions & 7 deletions demo/node/rntuple_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,10 @@ if (rntuple.builder?.name !== 'Staff')
else
console.log('OK: name is', rntuple.builder?.name);

if (typeof rntuple.builder?.version !== 'number')
console.error('FAILURE: version is missing or invalid');
else
console.log('OK: version is', rntuple.builder.version);

if (!rntuple.builder?.description)
console.error('FAILURE: description is missing');
if (!rntuple.builder?.description){
if (rntuple.builder?.description !== '')
console.error('FAILURE: description should be the empty string');
}
else
console.log('OK: description is', rntuple.builder.description);

Expand All @@ -33,3 +30,17 @@ if (rntuple.builder?.xxhash3 === undefined || rntuple.builder.xxhash3 === null)
else
console.log('OK: xxhash3 is', '0x' + rntuple.builder.xxhash3.toString(16).padStart(16, '0'));

// Fields Check

if (!rntuple.builder?.fieldDescriptors?.length)
console.error('FAILURE: No fields deserialized');
else {
console.log(`OK: ${rntuple.builder.fieldDescriptors.length} field(s) deserialized`);
for (let i = 0; i < rntuple.builder.fieldDescriptors.length; ++i) {
const field = rntuple.builder.fieldDescriptors[i];
if (!field.fieldName || !field.typeName)
console.error(`FAILURE: Field ${i} is missing name or type`);
else
console.log(`OK: Field ${i}: ${field.fieldName} (${field.typeName})`);
}
}
138 changes: 108 additions & 30 deletions modules/rntuple.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ const LITTLE_ENDIAN = true;
class RBufferReader {

constructor(buffer) {
if (buffer instanceof ArrayBuffer)
this.buffer = buffer;
else if (ArrayBuffer.isView(buffer)) {
const bytes = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
this.buffer = bytes.slice().buffer;
if (buffer instanceof ArrayBuffer) {
this.buffer = buffer;
this.byteOffset = 0;
this.byteLength = buffer.byteLength;
} else if (ArrayBuffer.isView(buffer)) {
this.buffer = buffer.buffer;
this.byteOffset = buffer.byteOffset;
this.byteLength = buffer.byteLength;
} else
throw new TypeError('Invalid buffer type');


this.view = new DataView(this.buffer);
this.offset = 0;
}
Expand All @@ -23,7 +25,7 @@ class RBufferReader {

// Read unsigned 8-bit integer (1 BYTE)
readU8() {
const val = this.view.getUint8(this.offset, LITTLE_ENDIAN);
const val = this.view.getUint8(this.offset);
this.offset += 1;
return val;
}
Expand All @@ -44,7 +46,7 @@ class RBufferReader {

// Read signed 8-bit integer (1 BYTE)
readS8() {
const val = this.view.getInt8(this.offset, LITTLE_ENDIAN);
const val = this.view.getInt8(this.offset);
this.offset += 1;
return val;
}
Expand All @@ -70,6 +72,13 @@ class RBufferReader {
return val;
}

// Read 64-bit float (8 BYTES)
readF64() {
const val = this.view.getFloat64(this.offset, LITTLE_ENDIAN);
this.offset += 8;
return val;
}

// Read a string with 32-bit length prefix
readString() {
const length = this.readU32();
Expand Down Expand Up @@ -98,36 +107,28 @@ class RBufferReader {

class RNTupleDescriptorBuilder {

deserializeHeader(header_blob) {
deserializeHeader(header_blob) {
if (!header_blob) return;

const reader = new RBufferReader(header_blob);
const reader = new RBufferReader(header_blob);
// Read the envelope metadata
this._readEnvelopeMetadata(reader);

// 1. Read header version (4 bytes)
this.version = reader.readU32();
// TODO: Validate the envelope checksum at the end of deserialization
// const payloadStart = reader.offset;

// 2. Read feature flags (4 bytes)
this.headerFeatureFlags = reader.readU32();
// Read feature flags list (may span multiple 64-bit words)
this._readFeatureFlags(reader);

// 3. Read xxhash3 (64-bit, 8 bytes)
this.xxhash3 = reader.readU64();
// Read metadata strings
this.name = reader.readString();
this.description = reader.readString();
this.writer = reader.readString();

// 4. Read name (length-prefixed string)
this.name = reader.readString();

// 5. Read description (length-prefixed string)
this.description = reader.readString();


// Console output to verify deserialization results
console.log('Version:', this.version);
console.log('Header Feature Flags:', this.headerFeatureFlags);
console.log('xxhash3:', '0x' + this.xxhash3.toString(16).padStart(16, '0'));
console.log('Name:', this.name);
console.log('Description:', this.description);
// List frame: list of field record frames
this._readFieldDescriptors(reader);
}


deserializeFooter(footer_blob) {
if (!footer_blob) return;

Expand All @@ -141,6 +142,83 @@ deserializeFooter(footer_blob) {
}


_readEnvelopeMetadata(reader) {
const typeAndLength = reader.readU64(),

// Envelope metadata
// The 16 bits are the envelope type ID, and the 48 bits are the envelope length
envelopeType = Number(typeAndLength & 0xFFFFn),
envelopeLength = Number((typeAndLength >> 16n) & 0xFFFFFFFFFFFFn);

console.log('Envelope Type ID:', envelopeType);
console.log('Envelope Length:', envelopeLength);
return { envelopeType, envelopeLength };
}

_readFeatureFlags(reader) {
this.featureFlags = [];
while (true) {
const val = reader.readU64();
this.featureFlags.push(val);
if ((val & 0x8000000000000000n) === 0n) break; // MSB not set: end of list
}

// verify all feature flags are zero
if (this.featureFlags.some(v => v !== 0n))
throw new Error('Unexpected non-zero feature flags: ' + this.featureFlags);
}

_readFieldDescriptors(reader) {
const fieldListSize = reader.readS64(), // signed 64-bit
fieldListIsList = fieldListSize < 0;


if (!fieldListIsList)
throw new Error('Field list frame is not a list frame, which is required.');

const fieldListCount = reader.readU32(); // number of field entries
console.log('Field List Count:', fieldListCount);

// List frame: list of field record frames

const fieldDescriptors = [];
for (let i = 0; i < fieldListCount; ++i) {
const fieldRecordSize = reader.readS64(),
fieldVersion = reader.readU32(),
typeVersion = reader.readU32(),
parentFieldId = reader.readU32(),
structRole = reader.readU16(),
flags = reader.readU16(),

fieldName = reader.readString(),
typeName = reader.readString(),
typeAlias = reader.readString(),
description = reader.readString();
console.log(`Field Record Size: ${fieldRecordSize}`);
let arraySize = null, sourceFieldId = null, checksum = null;

if (flags & 0x1) arraySize = reader.readU64();
if (flags & 0x2) sourceFieldId = reader.readU32();
if (flags & 0x4) checksum = reader.readU32();

fieldDescriptors.push({
fieldVersion,
typeVersion,
parentFieldId,
structRole,
flags,
fieldName,
typeName,
typeAlias,
description,
arraySize,
sourceFieldId,
checksum
});
}
this.fieldDescriptors = fieldDescriptors;
}

}

/** @summary Very preliminary function to read header/footer from RNTuple
Expand Down