945 lines
25 KiB
JavaScript
945 lines
25 KiB
JavaScript
|
/*
|
||
|
* rm - Feb 2011
|
||
|
* ctype.js
|
||
|
*
|
||
|
* This module provides a simple abstraction towards reading and writing
|
||
|
* different types of binary data. It is designed to use ctio.js and provide a
|
||
|
* richer and more expressive API on top of it.
|
||
|
*
|
||
|
* By default we support the following as built in basic types:
|
||
|
* int8_t
|
||
|
* int16_t
|
||
|
* int32_t
|
||
|
* uint8_t
|
||
|
* uint16_t
|
||
|
* uint32_t
|
||
|
* uint64_t
|
||
|
* float
|
||
|
* double
|
||
|
* char
|
||
|
* char[]
|
||
|
*
|
||
|
* Each type is returned as a Number, with the exception of char and char[]
|
||
|
* which are returned as Node Buffers. A char is considered a uint8_t.
|
||
|
*
|
||
|
* Requests to read and write data are specified as an array of JSON objects.
|
||
|
* This is also the same way that one declares structs. Even if just a single
|
||
|
* value is requested, it must be done as a struct. The array order determines
|
||
|
* the order that we try and read values. Each entry has the following format
|
||
|
* with values marked with a * being optional.
|
||
|
*
|
||
|
* { key: { type: /type/, value*: /value/, offset*: /offset/ }
|
||
|
*
|
||
|
* If offset is defined, we lseek(offset, SEEK_SET) before reading the next
|
||
|
* value. Value is defined when we're writing out data, otherwise it's ignored.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
var mod_ctf = require('./ctf.js');
|
||
|
var mod_ctio = require('./ctio.js');
|
||
|
var mod_assert = require('assert');
|
||
|
|
||
|
/*
|
||
|
* This is the set of basic types that we support.
|
||
|
*
|
||
|
* read The function to call to read in a value from a buffer
|
||
|
*
|
||
|
* write The function to call to write a value to a buffer
|
||
|
*
|
||
|
*/
|
||
|
var deftypes = {
|
||
|
'uint8_t': { read: ctReadUint8, write: ctWriteUint8 },
|
||
|
'uint16_t': { read: ctReadUint16, write: ctWriteUint16 },
|
||
|
'uint32_t': { read: ctReadUint32, write: ctWriteUint32 },
|
||
|
'uint64_t': { read: ctReadUint64, write: ctWriteUint64 },
|
||
|
'int8_t': { read: ctReadSint8, write: ctWriteSint8 },
|
||
|
'int16_t': { read: ctReadSint16, write: ctWriteSint16 },
|
||
|
'int32_t': { read: ctReadSint32, write: ctWriteSint32 },
|
||
|
'int64_t': { read: ctReadSint64, write: ctWriteSint64 },
|
||
|
'float': { read: ctReadFloat, write: ctWriteFloat },
|
||
|
'double': { read: ctReadDouble, write: ctWriteDouble },
|
||
|
'char': { read: ctReadChar, write: ctWriteChar },
|
||
|
'char[]': { read: ctReadCharArray, write: ctWriteCharArray }
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* The following are wrappers around the CType IO low level API. They encode
|
||
|
* knowledge about the size and return something in the expected format.
|
||
|
*/
|
||
|
function ctReadUint8(endian, buffer, offset)
|
||
|
{
|
||
|
var val = mod_ctio.ruint8(buffer, endian, offset);
|
||
|
return ({ value: val, size: 1 });
|
||
|
}
|
||
|
|
||
|
function ctReadUint16(endian, buffer, offset)
|
||
|
{
|
||
|
var val = mod_ctio.ruint16(buffer, endian, offset);
|
||
|
return ({ value: val, size: 2 });
|
||
|
}
|
||
|
|
||
|
function ctReadUint32(endian, buffer, offset)
|
||
|
{
|
||
|
var val = mod_ctio.ruint32(buffer, endian, offset);
|
||
|
return ({ value: val, size: 4 });
|
||
|
}
|
||
|
|
||
|
function ctReadUint64(endian, buffer, offset)
|
||
|
{
|
||
|
var val = mod_ctio.ruint64(buffer, endian, offset);
|
||
|
return ({ value: val, size: 8 });
|
||
|
}
|
||
|
|
||
|
function ctReadSint8(endian, buffer, offset)
|
||
|
{
|
||
|
var val = mod_ctio.rsint8(buffer, endian, offset);
|
||
|
return ({ value: val, size: 1 });
|
||
|
}
|
||
|
|
||
|
function ctReadSint16(endian, buffer, offset)
|
||
|
{
|
||
|
var val = mod_ctio.rsint16(buffer, endian, offset);
|
||
|
return ({ value: val, size: 2 });
|
||
|
}
|
||
|
|
||
|
function ctReadSint32(endian, buffer, offset)
|
||
|
{
|
||
|
var val = mod_ctio.rsint32(buffer, endian, offset);
|
||
|
return ({ value: val, size: 4 });
|
||
|
}
|
||
|
|
||
|
function ctReadSint64(endian, buffer, offset)
|
||
|
{
|
||
|
var val = mod_ctio.rsint64(buffer, endian, offset);
|
||
|
return ({ value: val, size: 8 });
|
||
|
}
|
||
|
|
||
|
function ctReadFloat(endian, buffer, offset)
|
||
|
{
|
||
|
var val = mod_ctio.rfloat(buffer, endian, offset);
|
||
|
return ({ value: val, size: 4 });
|
||
|
}
|
||
|
|
||
|
function ctReadDouble(endian, buffer, offset)
|
||
|
{
|
||
|
var val = mod_ctio.rdouble(buffer, endian, offset);
|
||
|
return ({ value: val, size: 8 });
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Reads a single character into a node buffer
|
||
|
*/
|
||
|
function ctReadChar(endian, buffer, offset)
|
||
|
{
|
||
|
var res = new Buffer(1);
|
||
|
res[0] = mod_ctio.ruint8(buffer, endian, offset);
|
||
|
return ({ value: res, size: 1 });
|
||
|
}
|
||
|
|
||
|
function ctReadCharArray(length, endian, buffer, offset)
|
||
|
{
|
||
|
var ii;
|
||
|
var res = new Buffer(length);
|
||
|
|
||
|
for (ii = 0; ii < length; ii++)
|
||
|
res[ii] = mod_ctio.ruint8(buffer, endian, offset + ii);
|
||
|
|
||
|
return ({ value: res, size: length });
|
||
|
}
|
||
|
|
||
|
function ctWriteUint8(value, endian, buffer, offset)
|
||
|
{
|
||
|
mod_ctio.wuint8(value, endian, buffer, offset);
|
||
|
return (1);
|
||
|
}
|
||
|
|
||
|
function ctWriteUint16(value, endian, buffer, offset)
|
||
|
{
|
||
|
mod_ctio.wuint16(value, endian, buffer, offset);
|
||
|
return (2);
|
||
|
}
|
||
|
|
||
|
function ctWriteUint32(value, endian, buffer, offset)
|
||
|
{
|
||
|
mod_ctio.wuint32(value, endian, buffer, offset);
|
||
|
return (4);
|
||
|
}
|
||
|
|
||
|
function ctWriteUint64(value, endian, buffer, offset)
|
||
|
{
|
||
|
mod_ctio.wuint64(value, endian, buffer, offset);
|
||
|
return (8);
|
||
|
}
|
||
|
|
||
|
function ctWriteSint8(value, endian, buffer, offset)
|
||
|
{
|
||
|
mod_ctio.wsint8(value, endian, buffer, offset);
|
||
|
return (1);
|
||
|
}
|
||
|
|
||
|
function ctWriteSint16(value, endian, buffer, offset)
|
||
|
{
|
||
|
mod_ctio.wsint16(value, endian, buffer, offset);
|
||
|
return (2);
|
||
|
}
|
||
|
|
||
|
function ctWriteSint32(value, endian, buffer, offset)
|
||
|
{
|
||
|
mod_ctio.wsint32(value, endian, buffer, offset);
|
||
|
return (4);
|
||
|
}
|
||
|
|
||
|
function ctWriteSint64(value, endian, buffer, offset)
|
||
|
{
|
||
|
mod_ctio.wsint64(value, endian, buffer, offset);
|
||
|
return (8);
|
||
|
}
|
||
|
|
||
|
function ctWriteFloat(value, endian, buffer, offset)
|
||
|
{
|
||
|
mod_ctio.wfloat(value, endian, buffer, offset);
|
||
|
return (4);
|
||
|
}
|
||
|
|
||
|
function ctWriteDouble(value, endian, buffer, offset)
|
||
|
{
|
||
|
mod_ctio.wdouble(value, endian, buffer, offset);
|
||
|
return (8);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Writes a single character into a node buffer
|
||
|
*/
|
||
|
function ctWriteChar(value, endian, buffer, offset)
|
||
|
{
|
||
|
if (!(value instanceof Buffer))
|
||
|
throw (new Error('Input must be a buffer'));
|
||
|
|
||
|
mod_ctio.ruint8(value[0], endian, buffer, offset);
|
||
|
return (1);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* We're going to write 0s into the buffer if the string is shorter than the
|
||
|
* length of the array.
|
||
|
*/
|
||
|
function ctWriteCharArray(value, length, endian, buffer, offset)
|
||
|
{
|
||
|
var ii;
|
||
|
|
||
|
if (!(value instanceof Buffer))
|
||
|
throw (new Error('Input must be a buffer'));
|
||
|
|
||
|
if (value.length > length)
|
||
|
throw (new Error('value length greater than array length'));
|
||
|
|
||
|
for (ii = 0; ii < value.length && ii < length; ii++)
|
||
|
mod_ctio.wuint8(value[ii], endian, buffer, offset + ii);
|
||
|
|
||
|
for (; ii < length; ii++)
|
||
|
mod_ctio.wuint8(0, endian, offset + ii);
|
||
|
|
||
|
|
||
|
return (length);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Each parser has their own set of types. We want to make sure that they each
|
||
|
* get their own copy as they may need to modify it.
|
||
|
*/
|
||
|
function ctGetBasicTypes()
|
||
|
{
|
||
|
var ret = {};
|
||
|
var key;
|
||
|
for (key in deftypes)
|
||
|
ret[key] = deftypes[key];
|
||
|
|
||
|
return (ret);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Given a string in the form of type[length] we want to split this into an
|
||
|
* object that extracts that information. We want to note that we could possibly
|
||
|
* have nested arrays so this should only check the furthest one. It may also be
|
||
|
* the case that we have no [] pieces, in which case we just return the current
|
||
|
* type.
|
||
|
*/
|
||
|
function ctParseType(str)
|
||
|
{
|
||
|
var begInd, endInd;
|
||
|
var type, len;
|
||
|
if (typeof (str) != 'string')
|
||
|
throw (new Error('type must be a Javascript string'));
|
||
|
|
||
|
endInd = str.lastIndexOf(']');
|
||
|
if (endInd == -1) {
|
||
|
if (str.lastIndexOf('[') != -1)
|
||
|
throw (new Error('found invalid type with \'[\' but ' +
|
||
|
'no corresponding \']\''));
|
||
|
|
||
|
return ({ type: str });
|
||
|
}
|
||
|
|
||
|
begInd = str.lastIndexOf('[');
|
||
|
if (begInd == -1)
|
||
|
throw (new Error('found invalid type with \']\' but ' +
|
||
|
'no corresponding \'[\''));
|
||
|
|
||
|
if (begInd >= endInd)
|
||
|
throw (new Error('malformed type, \']\' appears before \'[\''));
|
||
|
|
||
|
type = str.substring(0, begInd);
|
||
|
len = str.substring(begInd + 1, endInd);
|
||
|
|
||
|
return ({ type: type, len: len });
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Given a request validate that all of the fields for it are valid and make
|
||
|
* sense. This includes verifying the following notions:
|
||
|
* - Each type requested is present in types
|
||
|
* - Only allow a name for a field to be specified once
|
||
|
* - If an array is specified, validate that the requested field exists and
|
||
|
* comes before it.
|
||
|
* - If fields is defined, check that each entry has the occurrence of field
|
||
|
*/
|
||
|
function ctCheckReq(def, types, fields)
|
||
|
{
|
||
|
var ii, jj;
|
||
|
var req, keys, key;
|
||
|
var found = {};
|
||
|
|
||
|
if (!(def instanceof Array))
|
||
|
throw (new Error('definition is not an array'));
|
||
|
|
||
|
if (def.length === 0)
|
||
|
throw (new Error('definition must have at least one element'));
|
||
|
|
||
|
for (ii = 0; ii < def.length; ii++) {
|
||
|
req = def[ii];
|
||
|
if (!(req instanceof Object))
|
||
|
throw (new Error('definition must be an array of' +
|
||
|
'objects'));
|
||
|
|
||
|
keys = Object.keys(req);
|
||
|
if (keys.length != 1)
|
||
|
throw (new Error('definition entry must only have ' +
|
||
|
'one key'));
|
||
|
|
||
|
if (keys[0] in found)
|
||
|
throw (new Error('Specified name already ' +
|
||
|
'specified: ' + keys[0]));
|
||
|
|
||
|
if (!('type' in req[keys[0]]))
|
||
|
throw (new Error('missing required type definition'));
|
||
|
|
||
|
key = ctParseType(req[keys[0]]['type']);
|
||
|
|
||
|
/*
|
||
|
* We may have nested arrays, we need to check the validity of
|
||
|
* the types until the len field is undefined in key. However,
|
||
|
* each time len is defined we need to verify it is either an
|
||
|
* integer or corresponds to an already seen key.
|
||
|
*/
|
||
|
while (key['len'] !== undefined) {
|
||
|
if (isNaN(parseInt(key['len'], 10))) {
|
||
|
if (!(key['len'] in found))
|
||
|
throw (new Error('Given an array ' +
|
||
|
'length without a matching type'));
|
||
|
|
||
|
}
|
||
|
|
||
|
key = ctParseType(key['type']);
|
||
|
}
|
||
|
|
||
|
/* Now we can validate if the type is valid */
|
||
|
if (!(key['type'] in types))
|
||
|
throw (new Error('type not found or typdefed: ' +
|
||
|
key['type']));
|
||
|
|
||
|
/* Check for any required fields */
|
||
|
if (fields !== undefined) {
|
||
|
for (jj = 0; jj < fields.length; jj++) {
|
||
|
if (!(fields[jj] in req[keys[0]]))
|
||
|
throw (new Error('Missing required ' +
|
||
|
'field: ' + fields[jj]));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
found[keys[0]] = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Create a new instance of the parser. Each parser has its own store of
|
||
|
* typedefs and endianness. Conf is an object with the following required
|
||
|
* values:
|
||
|
*
|
||
|
* endian Either 'big' or 'little' do determine the endianness we
|
||
|
* want to read from or write to.
|
||
|
*
|
||
|
* And the following optional values:
|
||
|
*
|
||
|
* char-type Valid options here are uint8 and int8. If uint8 is
|
||
|
* specified this changes the default behavior of a single
|
||
|
* char from being a buffer of a single character to being
|
||
|
* a uint8_t. If int8, it becomes an int8_t instead.
|
||
|
*/
|
||
|
function CTypeParser(conf)
|
||
|
{
|
||
|
if (!conf) throw (new Error('missing required argument'));
|
||
|
|
||
|
if (!('endian' in conf))
|
||
|
throw (new Error('missing required endian value'));
|
||
|
|
||
|
if (conf['endian'] != 'big' && conf['endian'] != 'little')
|
||
|
throw (new Error('Invalid endian type'));
|
||
|
|
||
|
if ('char-type' in conf && (conf['char-type'] != 'uint8' &&
|
||
|
conf['char-type'] != 'int8'))
|
||
|
throw (new Error('invalid option for char-type: ' +
|
||
|
conf['char-type']));
|
||
|
|
||
|
this.endian = conf['endian'];
|
||
|
this.types = ctGetBasicTypes();
|
||
|
|
||
|
/*
|
||
|
* There may be a more graceful way to do this, but this will have to
|
||
|
* serve.
|
||
|
*/
|
||
|
if ('char-type' in conf && conf['char-type'] == 'uint8')
|
||
|
this.types['char'] = this.types['uint8_t'];
|
||
|
|
||
|
if ('char-type' in conf && conf['char-type'] == 'int8')
|
||
|
this.types['char'] = this.types['int8_t'];
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Sets the current endian value for the Parser. If the value is not valid,
|
||
|
* throws an Error.
|
||
|
*
|
||
|
* endian Either 'big' or 'little' do determine the endianness we
|
||
|
* want to read from or write to.
|
||
|
*
|
||
|
*/
|
||
|
CTypeParser.prototype.setEndian = function (endian)
|
||
|
{
|
||
|
if (endian != 'big' && endian != 'little')
|
||
|
throw (new Error('invalid endian type, must be big or ' +
|
||
|
'little'));
|
||
|
|
||
|
this.endian = endian;
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* Returns the current value of the endian value for the parser.
|
||
|
*/
|
||
|
CTypeParser.prototype.getEndian = function ()
|
||
|
{
|
||
|
return (this.endian);
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* A user has requested to add a type, let us honor their request. Yet, if their
|
||
|
* request doth spurn us, send them unto the Hells which Dante describes.
|
||
|
*
|
||
|
* name The string for the type definition we're adding
|
||
|
*
|
||
|
* value Either a string that is a type/array name or an object
|
||
|
* that describes a struct.
|
||
|
*/
|
||
|
CTypeParser.prototype.typedef = function (name, value)
|
||
|
{
|
||
|
var type;
|
||
|
|
||
|
if (name === undefined)
|
||
|
throw (new (Error('missing required typedef argument: name')));
|
||
|
|
||
|
if (value === undefined)
|
||
|
throw (new (Error('missing required typedef argument: value')));
|
||
|
|
||
|
if (typeof (name) != 'string')
|
||
|
throw (new (Error('the name of a type must be a string')));
|
||
|
|
||
|
type = ctParseType(name);
|
||
|
|
||
|
if (type['len'] !== undefined)
|
||
|
throw (new Error('Cannot have an array in the typedef name'));
|
||
|
|
||
|
if (name in this.types)
|
||
|
throw (new Error('typedef name already present: ' + name));
|
||
|
|
||
|
if (typeof (value) != 'string' && !(value instanceof Array))
|
||
|
throw (new Error('typedef value must either be a string or ' +
|
||
|
'struct'));
|
||
|
|
||
|
if (typeof (value) == 'string') {
|
||
|
type = ctParseType(value);
|
||
|
if (type['len'] !== undefined) {
|
||
|
if (isNaN(parseInt(type['len'], 10)))
|
||
|
throw (new (Error('typedef value must use ' +
|
||
|
'fixed size array when outside of a ' +
|
||
|
'struct')));
|
||
|
}
|
||
|
|
||
|
this.types[name] = value;
|
||
|
} else {
|
||
|
/* We have a struct, validate it */
|
||
|
ctCheckReq(value, this.types);
|
||
|
this.types[name] = value;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* Include all of the typedefs, but none of the built in types. This should be
|
||
|
* treated as read-only.
|
||
|
*/
|
||
|
CTypeParser.prototype.lstypes = function ()
|
||
|
{
|
||
|
var key;
|
||
|
var ret = {};
|
||
|
|
||
|
for (key in this.types) {
|
||
|
if (key in deftypes)
|
||
|
continue;
|
||
|
ret[key] = this.types[key];
|
||
|
}
|
||
|
|
||
|
return (ret);
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* Given a type string that may have array types that aren't numbers, try and
|
||
|
* fill them in from the values object. The object should be of the format where
|
||
|
* indexing into it should return a number for that type.
|
||
|
*
|
||
|
* str The type string
|
||
|
*
|
||
|
* values An object that can be used to fulfill type information
|
||
|
*/
|
||
|
function ctResolveArray(str, values)
|
||
|
{
|
||
|
var ret = '';
|
||
|
var type = ctParseType(str);
|
||
|
|
||
|
while (type['len'] !== undefined) {
|
||
|
if (isNaN(parseInt(type['len'], 10))) {
|
||
|
if (typeof (values[type['len']]) != 'number')
|
||
|
throw (new Error('cannot sawp in non-number ' +
|
||
|
'for array value'));
|
||
|
ret = '[' + values[type['len']] + ']' + ret;
|
||
|
} else {
|
||
|
ret = '[' + type['len'] + ']' + ret;
|
||
|
}
|
||
|
type = ctParseType(type['type']);
|
||
|
}
|
||
|
|
||
|
ret = type['type'] + ret;
|
||
|
|
||
|
return (ret);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* [private] Either the typedef resolves to another type string or to a struct.
|
||
|
* If it resolves to a struct, we just pass it off to read struct. If not, we
|
||
|
* can just pass it off to read entry.
|
||
|
*/
|
||
|
CTypeParser.prototype.resolveTypedef = function (type, dispatch, buffer,
|
||
|
offset, value)
|
||
|
{
|
||
|
var pt;
|
||
|
|
||
|
mod_assert.ok(type in this.types);
|
||
|
if (typeof (this.types[type]) == 'string') {
|
||
|
pt = ctParseType(this.types[type]);
|
||
|
if (dispatch == 'read')
|
||
|
return (this.readEntry(pt, buffer, offset));
|
||
|
else if (dispatch == 'write')
|
||
|
return (this.writeEntry(value, pt, buffer, offset));
|
||
|
else
|
||
|
throw (new Error('invalid dispatch type to ' +
|
||
|
'resolveTypedef'));
|
||
|
} else {
|
||
|
if (dispatch == 'read')
|
||
|
return (this.readStruct(this.types[type], buffer,
|
||
|
offset));
|
||
|
else if (dispatch == 'write')
|
||
|
return (this.writeStruct(value, this.types[type],
|
||
|
buffer, offset));
|
||
|
else
|
||
|
throw (new Error('invalid dispatch type to ' +
|
||
|
'resolveTypedef'));
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* [private] Try and read in the specific entry.
|
||
|
*/
|
||
|
CTypeParser.prototype.readEntry = function (type, buffer, offset)
|
||
|
{
|
||
|
var parse, len;
|
||
|
|
||
|
/*
|
||
|
* Because we want to special case char[]s this is unfortunately
|
||
|
* a bit uglier than it really should be. We want to special
|
||
|
* case char[]s so that we return a node buffer, thus they are a
|
||
|
* first class type where as all other arrays just call into a
|
||
|
* generic array routine which calls their data-specific routine
|
||
|
* the specified number of times.
|
||
|
*
|
||
|
* The valid dispatch options we have are:
|
||
|
* - Array and char => char[] handler
|
||
|
* - Generic array handler
|
||
|
* - Generic typedef handler
|
||
|
* - Basic type handler
|
||
|
*/
|
||
|
if (type['len'] !== undefined) {
|
||
|
len = parseInt(type['len'], 10);
|
||
|
if (isNaN(len))
|
||
|
throw (new Error('somehow got a non-numeric length'));
|
||
|
|
||
|
if (type['type'] == 'char')
|
||
|
parse = this.types['char[]']['read'](len,
|
||
|
this.endian, buffer, offset);
|
||
|
else
|
||
|
parse = this.readArray(type['type'],
|
||
|
len, buffer, offset);
|
||
|
} else {
|
||
|
if (type['type'] in deftypes)
|
||
|
parse = this.types[type['type']]['read'](this.endian,
|
||
|
buffer, offset);
|
||
|
else
|
||
|
parse = this.resolveTypedef(type['type'], 'read',
|
||
|
buffer, offset);
|
||
|
}
|
||
|
|
||
|
return (parse);
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* [private] Read an array of data
|
||
|
*/
|
||
|
CTypeParser.prototype.readArray = function (type, length, buffer, offset)
|
||
|
{
|
||
|
var ii, ent, pt;
|
||
|
var baseOffset = offset;
|
||
|
var ret = new Array(length);
|
||
|
pt = ctParseType(type);
|
||
|
|
||
|
for (ii = 0; ii < length; ii++) {
|
||
|
ent = this.readEntry(pt, buffer, offset);
|
||
|
offset += ent['size'];
|
||
|
ret[ii] = ent['value'];
|
||
|
}
|
||
|
|
||
|
return ({ value: ret, size: offset - baseOffset });
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* [private] Read a single struct in.
|
||
|
*/
|
||
|
CTypeParser.prototype.readStruct = function (def, buffer, offset)
|
||
|
{
|
||
|
var parse, ii, type, entry, key;
|
||
|
var baseOffset = offset;
|
||
|
var ret = {};
|
||
|
|
||
|
/* Walk it and handle doing what's necessary */
|
||
|
for (ii = 0; ii < def.length; ii++) {
|
||
|
key = Object.keys(def[ii])[0];
|
||
|
entry = def[ii][key];
|
||
|
|
||
|
/* Resolve all array values */
|
||
|
type = ctParseType(ctResolveArray(entry['type'], ret));
|
||
|
|
||
|
if ('offset' in entry)
|
||
|
offset = baseOffset + entry['offset'];
|
||
|
|
||
|
parse = this.readEntry(type, buffer, offset);
|
||
|
|
||
|
offset += parse['size'];
|
||
|
ret[key] = parse['value'];
|
||
|
}
|
||
|
|
||
|
return ({ value: ret, size: (offset-baseOffset)});
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* This is what we were born to do. We read the data from a buffer and return it
|
||
|
* in an object whose keys match the values from the object.
|
||
|
*
|
||
|
* def The array definition of the data to read in
|
||
|
*
|
||
|
* buffer The buffer to read data from
|
||
|
*
|
||
|
* offset The offset to start writing to
|
||
|
*
|
||
|
* Returns an object where each key corresponds to an entry in def and the value
|
||
|
* is the read value.
|
||
|
*/
|
||
|
CTypeParser.prototype.readData = function (def, buffer, offset)
|
||
|
{
|
||
|
/* Sanity check for arguments */
|
||
|
if (def === undefined)
|
||
|
throw (new Error('missing definition for what we should be' +
|
||
|
'parsing'));
|
||
|
|
||
|
if (buffer === undefined)
|
||
|
throw (new Error('missing buffer for what we should be ' +
|
||
|
'parsing'));
|
||
|
|
||
|
if (offset === undefined)
|
||
|
throw (new Error('missing offset for what we should be ' +
|
||
|
'parsing'));
|
||
|
|
||
|
/* Sanity check the object definition */
|
||
|
ctCheckReq(def, this.types);
|
||
|
|
||
|
return (this.readStruct(def, buffer, offset)['value']);
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* [private] Write out an array of data
|
||
|
*/
|
||
|
CTypeParser.prototype.writeArray = function (value, type, length, buffer,
|
||
|
offset)
|
||
|
{
|
||
|
var ii, pt;
|
||
|
var baseOffset = offset;
|
||
|
if (!(value instanceof Array))
|
||
|
throw (new Error('asked to write an array, but value is not ' +
|
||
|
'an array'));
|
||
|
|
||
|
if (value.length != length)
|
||
|
throw (new Error('asked to write array of length ' + length +
|
||
|
' but that does not match value length: ' + value.length));
|
||
|
|
||
|
pt = ctParseType(type);
|
||
|
for (ii = 0; ii < length; ii++)
|
||
|
offset += this.writeEntry(value[ii], pt, buffer, offset);
|
||
|
|
||
|
return (offset - baseOffset);
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* [private] Write the specific entry
|
||
|
*/
|
||
|
CTypeParser.prototype.writeEntry = function (value, type, buffer, offset)
|
||
|
{
|
||
|
var len, ret;
|
||
|
|
||
|
if (type['len'] !== undefined) {
|
||
|
len = parseInt(type['len'], 10);
|
||
|
if (isNaN(len))
|
||
|
throw (new Error('somehow got a non-numeric length'));
|
||
|
|
||
|
if (type['type'] == 'char')
|
||
|
ret = this.types['char[]']['write'](value, len,
|
||
|
this.endian, buffer, offset);
|
||
|
else
|
||
|
ret = this.writeArray(value, type['type'],
|
||
|
len, buffer, offset);
|
||
|
} else {
|
||
|
if (type['type'] in deftypes)
|
||
|
ret = this.types[type['type']]['write'](value,
|
||
|
this.endian, buffer, offset);
|
||
|
else
|
||
|
ret = this.resolveTypedef(type['type'], 'write',
|
||
|
buffer, offset, value);
|
||
|
}
|
||
|
|
||
|
return (ret);
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* [private] Write a single struct out.
|
||
|
*/
|
||
|
CTypeParser.prototype.writeStruct = function (value, def, buffer, offset)
|
||
|
{
|
||
|
var ii, entry, type, key;
|
||
|
var baseOffset = offset;
|
||
|
var vals = {};
|
||
|
|
||
|
for (ii = 0; ii < def.length; ii++) {
|
||
|
key = Object.keys(def[ii])[0];
|
||
|
entry = def[ii][key];
|
||
|
|
||
|
type = ctParseType(ctResolveArray(entry['type'], vals));
|
||
|
|
||
|
if ('offset' in entry)
|
||
|
offset = baseOffset + entry['offset'];
|
||
|
|
||
|
offset += this.writeEntry(value[ii], type, buffer, offset);
|
||
|
/* Now that we've written it out, we can use it for arrays */
|
||
|
vals[key] = value[ii];
|
||
|
}
|
||
|
|
||
|
return (offset);
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* Unfortunately, we're stuck with the sins of an initial poor design. Because
|
||
|
* of that, we are going to have to support the old way of writing data via
|
||
|
* writeData. There we insert the values that you want to write into the
|
||
|
* definition. A little baroque. Internally, we use the new model. So we need to
|
||
|
* just get those values out of there. But to maintain the principle of least
|
||
|
* surprise, we're not going to modify the input data.
|
||
|
*/
|
||
|
function getValues(def)
|
||
|
{
|
||
|
var ii, out, key;
|
||
|
out = [];
|
||
|
for (ii = 0; ii < def.length; ii++) {
|
||
|
key = Object.keys(def[ii])[0];
|
||
|
mod_assert.ok('value' in def[ii][key]);
|
||
|
out.push(def[ii][key]['value']);
|
||
|
}
|
||
|
|
||
|
return (out);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* This is the second half of what we were born to do, write out the data
|
||
|
* itself. Historically this function required you to put your values in the
|
||
|
* definition section. This was not the smartest thing to do and a bit of an
|
||
|
* oversight to be honest. As such, this function now takes a values argument.
|
||
|
* If values is non-null and non-undefined, it will be used to determine the
|
||
|
* values. This means that the old method is still supported, but is no longer
|
||
|
* acceptable.
|
||
|
*
|
||
|
* def The array definition of the data to write out with
|
||
|
* values
|
||
|
*
|
||
|
* buffer The buffer to write to
|
||
|
*
|
||
|
* offset The offset in the buffer to write to
|
||
|
*
|
||
|
* values An array of values to write.
|
||
|
*/
|
||
|
CTypeParser.prototype.writeData = function (def, buffer, offset, values)
|
||
|
{
|
||
|
var hv;
|
||
|
|
||
|
if (def === undefined)
|
||
|
throw (new Error('missing definition for what we should be' +
|
||
|
'parsing'));
|
||
|
|
||
|
if (buffer === undefined)
|
||
|
throw (new Error('missing buffer for what we should be ' +
|
||
|
'parsing'));
|
||
|
|
||
|
if (offset === undefined)
|
||
|
throw (new Error('missing offset for what we should be ' +
|
||
|
'parsing'));
|
||
|
|
||
|
hv = (values != null && values != undefined);
|
||
|
if (hv) {
|
||
|
if (!Array.isArray(values))
|
||
|
throw (new Error('missing values for writing'));
|
||
|
ctCheckReq(def, this.types);
|
||
|
} else {
|
||
|
ctCheckReq(def, this.types, [ 'value' ]);
|
||
|
}
|
||
|
|
||
|
this.writeStruct(hv ? values : getValues(def), def, buffer, offset);
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* Functions to go to and from 64 bit numbers in a way that is compatible with
|
||
|
* Javascript limitations. There are two sets. One where the user is okay with
|
||
|
* an approximation and one where they are definitely not okay with an
|
||
|
* approximation.
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* Attempts to convert an array of two integers returned from rsint64 / ruint64
|
||
|
* into an absolute 64 bit number. If however the value would exceed 2^52 this
|
||
|
* will instead throw an error. The mantissa in a double is a 52 bit number and
|
||
|
* rather than potentially give you a value that is an approximation this will
|
||
|
* error. If you would rather an approximation, please see toApprox64.
|
||
|
*
|
||
|
* val An array of two 32-bit integers
|
||
|
*/
|
||
|
function toAbs64(val)
|
||
|
{
|
||
|
if (val === undefined)
|
||
|
throw (new Error('missing required arg: value'));
|
||
|
|
||
|
if (!Array.isArray(val))
|
||
|
throw (new Error('value must be an array'));
|
||
|
|
||
|
if (val.length != 2)
|
||
|
throw (new Error('value must be an array of length 2'));
|
||
|
|
||
|
/* We have 20 bits worth of precision in this range */
|
||
|
if (val[0] >= 0x100000)
|
||
|
throw (new Error('value would become approximated'));
|
||
|
|
||
|
return (val[0] * Math.pow(2, 32) + val[1]);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Will return the 64 bit value as returned in an array from rsint64 / ruint64
|
||
|
* to a value as close as it can. Note that Javascript stores all numbers as a
|
||
|
* double and the mantissa only has 52 bits. Thus this version may approximate
|
||
|
* the value.
|
||
|
*
|
||
|
* val An array of two 32-bit integers
|
||
|
*/
|
||
|
function toApprox64(val)
|
||
|
{
|
||
|
if (val === undefined)
|
||
|
throw (new Error('missing required arg: value'));
|
||
|
|
||
|
if (!Array.isArray(val))
|
||
|
throw (new Error('value must be an array'));
|
||
|
|
||
|
if (val.length != 2)
|
||
|
throw (new Error('value must be an array of length 2'));
|
||
|
|
||
|
return (Math.pow(2, 32) * val[0] + val[1]);
|
||
|
}
|
||
|
|
||
|
function parseCTF(json, conf)
|
||
|
{
|
||
|
var ctype = new CTypeParser(conf);
|
||
|
mod_ctf.ctfParseJson(json, ctype);
|
||
|
|
||
|
return (ctype);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Export the few things we actually want to. Currently this is just the CType
|
||
|
* Parser and ctio.
|
||
|
*/
|
||
|
exports.Parser = CTypeParser;
|
||
|
exports.toAbs64 = toAbs64;
|
||
|
exports.toApprox64 = toApprox64;
|
||
|
|
||
|
exports.parseCTF = parseCTF;
|
||
|
|
||
|
exports.ruint8 = mod_ctio.ruint8;
|
||
|
exports.ruint16 = mod_ctio.ruint16;
|
||
|
exports.ruint32 = mod_ctio.ruint32;
|
||
|
exports.ruint64 = mod_ctio.ruint64;
|
||
|
exports.wuint8 = mod_ctio.wuint8;
|
||
|
exports.wuint16 = mod_ctio.wuint16;
|
||
|
exports.wuint32 = mod_ctio.wuint32;
|
||
|
exports.wuint64 = mod_ctio.wuint64;
|
||
|
|
||
|
exports.rsint8 = mod_ctio.rsint8;
|
||
|
exports.rsint16 = mod_ctio.rsint16;
|
||
|
exports.rsint32 = mod_ctio.rsint32;
|
||
|
exports.rsint64 = mod_ctio.rsint64;
|
||
|
exports.wsint8 = mod_ctio.wsint8;
|
||
|
exports.wsint16 = mod_ctio.wsint16;
|
||
|
exports.wsint32 = mod_ctio.wsint32;
|
||
|
exports.wsint64 = mod_ctio.wsint64;
|
||
|
|
||
|
exports.rfloat = mod_ctio.rfloat;
|
||
|
exports.rdouble = mod_ctio.rdouble;
|
||
|
exports.wfloat = mod_ctio.wfloat;
|
||
|
exports.wdouble = mod_ctio.wdouble;
|