README.old
9.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
This library provides a way to read and write binary data.
Node CType is a way to read and write binary data in structured and easy to use
formats. It's name comes from the header file, though it does not share as much
with it as it perhaps should.
There are two levels of the API. One is the raw API which everything is built on
top of, while the other provides a much nicer abstraction and is built entirely
by using the lower level API. The hope is that the low level API is both clear
and useful. The low level API gets it's names from stdint.h (a rather
appropriate source). The lower level API is presented at the end of this
document.
Standard CType API
The CType interface is presented as a parser object that controls the
endianness combined with a series of methods to change that value, parse and
write out buffers, and a way to provide typedefs. Standard Types
The CType parser supports the following basic types which return Numbers except
as indicated:
* int8_t
* int16_t
* int32_t
* int64_t (returns an array where val[0] << 32 + val[1] would be the value)
* uint8_t
* uint16_t
* uint32_t
* uint64_t (returns an array where val[0] << 32 + val[1] would be the value)
* float
* double
* char (returns a buffer with just that single character)
* char[] (returns an object with the buffer and the number of characters read which is either the total amount requested or until the first 0)
Specifying Structs
The CType parser also supports the notion of structs. A struct is an array of
JSON objects that defines an order of keys which have types and values. One
would build a struct to represent a point (x,y) as follows:
[
{ x: { type: 'int16_t' }},
{ y: { type: 'int16_t' }}
]
When this is passed into the read routine, it would read the first two bytes
(as defined by int16_t) to determine the Number to use for X, and then it would
read the next two bytes to determine the value of Y. When read this could
return something like:
{
x: 42,
y: -23
}
When someone wants to write values, we use the same format as above, but with
additional value field:
[
{ x: { type: 'int16_t', value: 42 }},
{ y: { type: 'int16_t', value: -23 }}
]
Now, the structure above may be optionally annotated with offsets. This tells
us to rather than read continuously we should read the given value at the
specified offset. If an offset is provided, it is is effectively the equivalent
of lseek(offset, SEEK_SET). Thus, subsequent values will be read from that
offset and incremented by the appropriate value. As an example:
[
{ x: { type: 'int16_t' }},
{ y: { type: 'int16_t', offset: 20 }},
{ z: { type: 'int16_t' }}
]
We would read x from the first starting offset given to us, for the sake of
example, let's assume that's 0. After reading x, the next offset to read from
would be 2; however, y specifies an offset, thus we jump directly to that
offset and read y from byte 20. We would then read z from byte 22.
The same offsets may be used when writing values.
Typedef
The basic set of types while covers the basics, is somewhat limiting. To make
this richer, there is functionality to typedef something like in C. One can use
typedef to add a new name to an existing type or to define a name to refer to a
struct. Thus the following are all examples of a typedef:
typedef('size_t', 'uint32_t');
typedef('ssize_t', 'int32_t');
typedef('point_t', [
{ x: { type: 'int16_t' }},
{ y: { type: 'int16_t' }}
]);
Once something has been typedef'd it can be used in any of the definitions
previously shown.
One cannot remove a typedef once created, this is analogous to C.
The set of defined types can be printed with lstypes. The format of this output
is subject to change, but likely will look something like:
> lstypes();
{
size_t: 'uint32_t',
ssize_t: 'int32_t',
point_t: [
{ x: { type: 'int16_t' }},
{ y: { type: 'int16_t' }}
]
}
Specifying arrays
Arrays can be specified by appending []s to a type. Arrays must have the size
specified. The size must be specified and it can be done in one of two ways:
* An explicit non-zero integer size
* A name of a previously declared variable in the struct whose value is a
number.
Note, that when using the name of a variable, it should be the string name for
the key. This is only valid inside structs and the value must be declared
before the value with the array. The following are examples:
[
{ ip_addr4: { type: 'uint8_t[4]' }},
{ len: { type: 'uint32_t' }},
{ data: { type: 'uint8_t[len]' }}
]
Arrays are permitted in typedefs; however, they must have a declared integer
size. The following are examples of valid and invalid arrays:
typedef('path', 'char[1024]'); /* Good */
typedef('path', 'char[len]'); /* Bad! */
64 bit values:
Unfortunately Javascript represents values with a double, so you lose precision
and the ability to represent Integers roughly beyond 2^53. To alleviate this, I
propose the following for returning 64 bit integers when read:
value[2]: Each entry is a 32 bit number which can be reconstructed to the
original by the following formula:
value[0] << 32 + value[1] (Note this will not work in Javascript)
CTF JSON data:
node-ctype can also handle JSON data that mathces the format described in the
documentation of the tool ctf2json. Given the JSON data which specifies type
information, it will transform that into a parser that understands all of the
types defined inside of it. This is useful for more complicated structures that
have a lot of typedefs.
Interface overview
The following is the header-file like interface to the parser object:
/*
* Create a new instance of the parser. Each parser has its own store of
* typedefs and endianness. Conf is an object with the following values:
*
* endian Either 'big' or 'little' do determine the endianness we
* want to read from or write to.
*
*/
function CTypeParser(conf);
/*
* Parses the CTF JSON data and creates a parser that understands all of those
* types.
*
* data Parsed JSON data that maches that CTF JSON
* specification.
*
* conf The configuration object to create a new CTypeParser
* from.
*/
CTypeParser parseCTF(data, conf);
/*
* 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.
*/
Object CTypeParser.readData(<Type Definition>, buffer, offset);
/*
* This is the second half of what we were born to do, write out the data
* itself.
*
* 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
*/
void CTypeParser.writeData(<Type Definition>, buffer, offset);
/*
* 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.
*/
void CTypeParser.prototype.typedef(name, value);
Object CTypeParser.prototype.lstypes();
/*
* Get the endian value for the current parser
*/
String CTypeParser.prototype.getEndian();
/*
* 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.
*
*/
void CTypeParser.protoype.setEndian(String);
/*
* 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
*/
Number function toAbs64(val)
/*
* 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
*/
Number function toApprox64(val)
Low Level API
The following function are provided at the low level:
Read unsigned integers from a buffer:
Number ruint8(buffer, endian, offset);
Number ruint16(buffer, endian, offset);
Number ruint32(buffer, endian, offset);
Number[] ruint64(buffer, endian, offset);
Read signed integers from a buffer:
Number rsint8(buffer, endian, offset);
Number rsint16(buffer, endian, offset);
Number rsint32(buffer, endian, offset);
Number[] rsint64(buffer, endian, offset);
Read floating point numbers from a buffer:
Number rfloat(buffer, endian, offset); /* IEEE-754 Single precision */
Number rdouble(buffer, endian, offset); /* IEEE-754 Double precision */
Write unsigned integers to a buffer:
void wuint8(Number, endian, buffer, offset);
void wuint16(Number, endian, buffer, offset);
void wuint32(Number, endian, buffer, offset);
void wuint64(Number[], endian, buffer, offset);
Write signed integers from a buffer:
void wsint8(Number, endian, buffer, offset);
void wsint16(Number, endian, buffer, offset);
void wsint32(Number, endian, buffer, offset);
void wsint64(Number[], endian, buffer offset);
Write floating point numbers from a buffer:
void wfloat(Number, buffer, endian, offset); /* IEEE-754 Single precision */
void wdouble(Number, buffer, endian, offset); /* IEEE-754 Double precision */