Repository navigation
Expand file tree
/
Copy pathobject.js
More file actions
197 lines (178 loc) · 6.89 KB
/
Copy pathobject.js
File metadata and controls
197 lines (178 loc) · 6.89 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
'use strict'
var { getMinimumSize, assertNonOverlapping, encodeField, decodeField } = require('./utils')
function ObjectCodec(schema) {
assertNonOverlapping(schema)
var min = getMinimumSize(schema)
var fields = 0, fixed_fields = 0, variable_fields = 0, variable_field, length_field
for(var i in schema) {
fields ++
if(!schema[i].pointed) { fixed_fields ++ }
else {
variable_fields ++;
//if there is exactly one variable field, remember it. (but unset if 2 vfields)
variable_field = variable_field === undefined ? schema[i] : null
}
//currently, length is required to be the first field
//but there are also other types planned like checksum, so maybe relax this...
if(schema[i].isLength)
length_field = schema[i]
}
function encode (obj, buffer=Buffer.alloc(encodingLength(obj)), start=0) {
var free = min, length_i
if(isNaN(free)) throw new Error('min size was nan')
for(var i = 0; i < schema.length; i++) {
var field = schema[i]
if(field.isType) {
var value = obj[field.name]
free += encodeField(field.position, field.direct, null, field.typeValue, buffer, start, free)
}
else if(!field.isLength) {
var value = obj[field.name]
free += encodeField(field.position, field.direct, field.pointed, value, buffer, start, free)
if(isNaN(free)) throw new Error('free was nan after field:'+i)
}
else
length_i = i
}
//the length field must be first
if(length_field !== undefined) {
encodeField(length_field.position, length_field.direct, null, free-(length_field.offset|0), buffer, start)
}
//if this was encoded as a pointed field
//we need to know how far the free pointer has moved.
//hmm, encodeField returns only the pointed bytes used
//not the direct bytes, but that's an private interface.
//other encoders
encode.bytes = free
return free
}
function decode (buffer, start=0, end=buffer.length) {
end = Math.min(end, buffer.length)
var a = {} //new Array(schema.length)
for(var i = 0; i < schema.length; i++) {
var field = schema[i]
var value = a[field.name] = decodeField(field.position, field.direct, field.pointed, buffer, start, end, field.allow_zero)
//remember the bytes, if length is included
if(field.isLength) {
decode.bytes = value+(length_field.offset|0)
var _end = start + decode.bytes
if(_end > end) throw new Error('length field out of bounds, expected:'+end+', got:'+_end)
//but a smaller value for end is acceptable.
end = _end
} else if(!length_field && variable_fields == 1 && field == variable_field) {
decode.bytes = min + field.pointed.decode.bytes
}
}
//calculating the total bytes decoded is a bit tricky because it's actually possible,
//with relative pointers, that the bytes are not contigious.
//for example, it's possible that nested objects share references to poinded values.
//in cases where you actually need this, a length field should be included.
if(variable_fields === 0) decode.bytes = min
return a
}
//I want to be able to decode a path.
//foo.bar.baz[10] and do a fixed number of reads
//and then decode the value (hopefully a primitive)
function get_field (index) {
return Number.isInteger(index) ? schema[index] : schema.find(v => v.name == index)
}
function dereference (buffer, start, index) {
var length = min
var field = get_field(index)
if(!field) throw new Error('cannot dereference invalid field:' + index)
var position = field.position
if(!field.pointed)
//might be a embedded fixed sized value, not a primitive
//so better to return pointer than to decode here
return start + position //return pointer to direct value
else {
var relp = field.direct.decode(buffer, start + position)
if(relp === 0) return -1
return (start + position) + relp
}
}
function reflect (index) {
var field = get_field(index)
if(!field) throw new Error('invalid field:'+index)
return field.pointed ? field.pointed : field.direct
}
function encodingLength (value) {
var v_size = 0
for(var i = 0; i < schema.length; i++) {
var field = schema[i]
if(field.pointed) {
var fv = value[field.name]
if(fv != null)
v_size += field.pointed.encodingLength(fv)
else if(!field.isNullable)
throw new Error('field:'+field.name+' is not nullable, but no value provided')
}
}
return min + v_size
}
function encodedLength (buffer, start, end=buffer.length) {
if(variable_fields === 0) return min
else if(length_field) return decodeField(length_field.position, length_field.direct, null, buffer, start, end)+(length_field.offset|0)
else if(variable_fields === 1 && variable_field.direct == null) {
return min + variable_field.pointed.encodedLength(buffer, start+variable_field.position)
}
else {
//without an explicit length field, check which pointed field
var max = min
var max_f = null
for(var i = 0; i < schema.length; i++) {
var field = schema[i]
if(field.pointed) {
var ptr = dereference(buffer, start, i)
var len = field.pointed.encodedLength(buffer, ptr)
var field_end = ptr + len
if(max < (field_end-start)) {
max = field_end-start
max_f = i
}
}
}
return max
}
}
//compact takes an object that maybe has padding or points to far away objects
//(this might happen because fields have been updated to point to new values)
function compact(buffer, start, _buffer, _start) {
if(!_buffer) {
var total = min
for(var i = 0; i < schema.length; i++) {
var field = schema[i]
if(field.pointed) {
total += field.pointed.encodedLength(buffer, dereference(buffer, start, i))
}
}
_buffer = Buffer.alloc(total), _start = 0
}
var free = min
for(var i = 0; i < schema.length; i++) {
var field = schema[i]
if(field.pointed) {
var ptr = dereference(buffer, start, i)
var len = field.pointed.encodedLength(buffer, ptr)
buffer.copy(_buffer, free, ptr, ptr+len)
field.direct.encode(free - field.position, _buffer, field.position+_start)
free += len
}
else {
field.direct.encode(field.direct.decode(buffer, start+field.position), _buffer, field.position)
}
}
//if input buffer was provided it would be more useful to return `free`
//could return {length: free} so either way {length} = compact(...)
//is the length.
return _buffer
}
return {
type:'object',
schema: schema,
encode, decode, dereference, reflect, encodingLength,//, encodedLength
bytes: variable_fields === 0 ? min : null,
encodedLength, compact
}
}
module.exports = ObjectCodec