yencode 1.0.8 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +339 -231
  2. package/binding.gyp +292 -39
  3. package/crcutil-1.0/code/multiword_64_64_gcc_amd64_asm.cc +7 -7
  4. package/crcutil-1.0/code/multiword_64_64_gcc_i386_mmx.cc +14 -14
  5. package/crcutil-1.0/code/multiword_64_64_intrinsic_i386_mmx.cc +1 -1
  6. package/crcutil-1.0/code/uint128_sse2.h +2 -0
  7. package/index.js +329 -22
  8. package/package.json +2 -2
  9. package/src/common.h +299 -0
  10. package/src/crc.cc +95 -0
  11. package/src/crc.h +23 -0
  12. package/src/crc_arm.cc +175 -0
  13. package/src/crc_common.h +4 -0
  14. package/{crc_folding.c → src/crc_folding.cc} +175 -185
  15. package/src/decoder.cc +61 -0
  16. package/src/decoder.h +53 -0
  17. package/src/decoder_avx.cc +18 -0
  18. package/src/decoder_avx2.cc +18 -0
  19. package/src/decoder_avx2_base.h +615 -0
  20. package/src/decoder_common.h +512 -0
  21. package/src/decoder_neon.cc +474 -0
  22. package/src/decoder_neon64.cc +451 -0
  23. package/src/decoder_sse2.cc +16 -0
  24. package/src/decoder_sse_base.h +711 -0
  25. package/src/decoder_ssse3.cc +18 -0
  26. package/src/encoder.cc +170 -0
  27. package/src/encoder.h +21 -0
  28. package/src/encoder_avx.cc +16 -0
  29. package/src/encoder_avx2.cc +16 -0
  30. package/src/encoder_avx_base.h +564 -0
  31. package/src/encoder_common.h +109 -0
  32. package/src/encoder_neon.cc +547 -0
  33. package/src/encoder_sse2.cc +13 -0
  34. package/src/encoder_sse_base.h +724 -0
  35. package/src/encoder_ssse3.cc +18 -0
  36. package/src/hedley.h +1899 -0
  37. package/src/platform.cc +147 -0
  38. package/src/yencode.cc +449 -0
  39. package/test/_maxsize.js +9 -0
  40. package/test/_speedbase.js +147 -0
  41. package/test/speedcrc.js +20 -0
  42. package/test/speeddec.js +92 -0
  43. package/test/speedenc.js +44 -0
  44. package/{testcrc.js → test/testcrc.js} +53 -39
  45. package/test/testdec.js +183 -0
  46. package/test/testenc.js +163 -0
  47. package/test/testpostdec.js +126 -0
  48. package/test.js +0 -91
  49. package/yencode.cc +0 -1622
package/index.js CHANGED
@@ -2,24 +2,190 @@
2
2
 
3
3
  var y = require('./build/Release/yencode.node');
4
4
 
5
- var nl = new Buffer([13, 10]);
5
+ var toBuffer = Buffer.alloc ? Buffer.from : Buffer;
6
+
7
+ var nl = toBuffer([13, 10]);
6
8
  var RE_BADCHAR = /\r\n\0/g;
9
+ var RE_YPROP = /([a-z_][a-z_0-9]*)=/;
10
+ var RE_NUMBER = /^\d+$/;
11
+ var decodePrev = ['\r\n', '=', '\r', '', '\r\n.', '\r\n.\r', '\r\n='];
12
+
13
+ var DecoderError = function(code, message) {
14
+ var err = new Error(message);
15
+ err.code = code;
16
+ return err;
17
+ }
18
+ var DecoderWarning = function(code, message) {
19
+ if(!this) return new DecoderWarning(code, message);
20
+ this.code = code;
21
+ this.message = message;
22
+ }
23
+ DecoderWarning.prototype = { toString: function() { return this.message; } };
24
+
25
+ var bufferFind, bufferFindRev;
26
+ if(Buffer.prototype.indexOf)
27
+ bufferFind = function(buf, search, start) {
28
+ return buf.indexOf(search, start, module.exports.encoding);
29
+ };
30
+ else
31
+ bufferFind = function(buf, search, start) {
32
+ if(!Buffer.isBuffer(search))
33
+ search = toBuffer(search, module.exports.encoding);
34
+ if(search.length == 0) return -1;
35
+ start = start|0;
36
+ if(search.length > buf.length-start) return -1;
37
+
38
+ for(var i = start; i < buf.length - search.length + 1; i++) {
39
+ var match = true;
40
+ for(var j = 0; j < search.length; j++) {
41
+ if(buf[i+j] != search[j]) {
42
+ match = false;
43
+ break;
44
+ }
45
+ }
46
+ if(match) return i;
47
+ }
48
+ return -1;
49
+ };
50
+ if(Buffer.prototype.lastIndexOf)
51
+ bufferFindRev = function(buf, search) {
52
+ return buf.lastIndexOf(search, -1, module.exports.encoding);
53
+ };
54
+ else
55
+ bufferFindRev = function(buf, search) {
56
+ if(!Buffer.isBuffer(search))
57
+ search = toBuffer(search, module.exports.encoding);
58
+ if(search.length == 0) return -1;
59
+ if(search.length > buf.length) return -1;
60
+
61
+ for(var i = buf.length-search.length; i >= 0; i--) {
62
+ var match = true;
63
+ for(var j = 0; j < search.length; j++) {
64
+ if(buf[i+j] != search[j]) {
65
+ match = false;
66
+ break;
67
+ }
68
+ }
69
+ if(match) return i;
70
+ }
71
+ return -1;
72
+ };
73
+
74
+ var decoderParseLines = function(lines, ydata) {
75
+ var warnings = [];
76
+ for(var i=0; i<lines.length; i++) {
77
+ var yprops = {};
78
+
79
+ var line = lines[i].substr(2); // cut off '=y'
80
+ // parse tag
81
+ var p = line.indexOf(' ');
82
+ var tag = (p<0 ? line : line.substr(0, p));
83
+ line = line.substr(tag.length+1).trim();
84
+
85
+ // parse props
86
+ var m = line.match(RE_YPROP);
87
+ while(m) {
88
+ if(m.index != 0) {
89
+ warnings.push(DecoderWarning('ignored_line_data', 'Unknown additional data ignored: "' + line.substr(0, m.index) + '"'));
90
+ }
91
+ var prop = m[1], val;
92
+ var valPos = m.index + m[0].length;
93
+ if(tag == 'begin' && prop == 'name') {
94
+ // special treatment of filename - the value is the rest of the line (can include spaces)
95
+ val = line.substr(valPos);
96
+ line = '';
97
+ } else {
98
+ p = line.indexOf(' ', valPos);
99
+ val = (p<0 ? line.substr(valPos) : line.substr(valPos, p-valPos));
100
+ line = line.substr(valPos + val.length +1);
101
+ }
102
+ if(prop in yprops) {
103
+ warnings.push(DecoderWarning('duplicate_property', 'Duplicate property encountered: `' + prop + '`'));
104
+ }
105
+ yprops[prop] = val;
106
+ m = line.match(RE_YPROP);
107
+ }
108
+ if(line != '') {
109
+ warnings.push(DecoderWarning('ignored_line_data', 'Unknown additional end-of-line data ignored: "' + line + '"'));
110
+ }
111
+
112
+ if(tag in ydata) {
113
+ warnings.push(DecoderWarning('duplicate_line', 'Duplicate line encountered: `' + tag + '`'));
114
+ }
115
+ ydata[tag] = yprops;
116
+ }
117
+ return warnings;
118
+ };
119
+
120
+ var propIsNotValidNumber = function(prop) {
121
+ return prop && !RE_NUMBER.test(prop);
122
+ };
7
123
 
8
124
  module.exports = {
9
125
  encoding: 'utf8',
10
126
 
11
127
  encode: y.encode,
12
128
  encodeTo: y.encodeTo,
13
- maxSize: function(length, line_size) {
129
+ decode: y.decode,
130
+ decodeTo: y.decodeTo,
131
+
132
+ decodeChunk: function(data, output, prev) {
133
+ // both output and prev are optional
134
+ if(typeof output !== 'undefined' && !Buffer.isBuffer(output)) {
135
+ prev = output;
136
+ output = null;
137
+ }
138
+ if(prev === null || typeof prev === 'undefined')
139
+ prev = '\r\n';
140
+
141
+ if(Buffer.isBuffer(prev)) prev = prev.toString();
142
+ prev = prev.substr(-4); // only care about the last 4 chars of previous state
143
+ if(prev == '\r\n.=') prev = '\r\n='; // aliased after dot stripped
144
+ if(data.length == 0) return {
145
+ read: 0,
146
+ written: 0,
147
+ output: toBuffer([]),
148
+ ended: 0,
149
+ state: prev
150
+ };
151
+ var state = decodePrev.indexOf(prev);
152
+ if(state < 0) {
153
+ for(var l=-3; l<0; i++) {
154
+ state = decodePrev.indexOf(prev.substr(l));
155
+ if(state >= 0) break;
156
+ }
157
+ if(state < 0) state = decodePrev.indexOf('');
158
+ }
159
+ var ret = y.decodeIncr(data, state, output);
160
+
161
+ return {
162
+ read: ret.read,
163
+ written: ret.written,
164
+ output: ret.output || output,
165
+ ended: !!ret.ended,
166
+ state: [decodePrev[ret.state], '\r\n=y', '\r\n.\r\n'][ret.ended]
167
+ };
168
+ },
169
+
170
+ minSize: function(length, line_size) {
14
171
  if(!length) return 0;
15
- return Math.floor(
16
- length*2 // all characters escaped
17
- + ((length*4) / (line_size||128)) // newlines, considering the possibility of all chars escaped
172
+ return length // no characters escaped
173
+ + 2 * Math.floor(length / (line_size||128)); // newlines
174
+ },
175
+ maxSize: function(length, line_size, esc_ratio) {
176
+ if(!length) return 0;
177
+ if(!esc_ratio && esc_ratio !== 0)
178
+ esc_ratio = 2;
179
+ else
180
+ esc_ratio++;
181
+ if(esc_ratio < 1 || esc_ratio > 2)
182
+ throw new Error('yEnc escape ratio must be between 0 and 1');
183
+ return Math.ceil(length*esc_ratio) // all characters escaped
184
+ + 2 * Math.floor((length*esc_ratio) / (line_size||128)) // newlines, considering the possibility of all chars escaped
18
185
  + 2 // allocation for offset and that a newline may occur early
19
- + 32 // extra space just in case things go awry... just kidding, it's just extra padding to make SIMD logic easier
20
- );
186
+ + 64 // extra space just in case things go awry... just kidding, it's just extra padding to make SIMD logic easier
187
+ ;
21
188
  },
22
- // TODO: check ordering of CRC32
23
189
  crc32: y.crc32,
24
190
  crc32_combine: y.crc32_combine,
25
191
  crc32_zeroes: y.crc32_zeroes,
@@ -27,18 +193,159 @@ module.exports = {
27
193
  post: function(filename, data, line_size) {
28
194
  if(!line_size) line_size = 128;
29
195
 
30
- if(!Buffer.isBuffer(data)) data = new Buffer(data);
196
+ if(!Buffer.isBuffer(data)) data = toBuffer(data);
31
197
 
32
- filename = new Buffer(filename.replace(RE_BADCHAR, '').substr(0, 256), exports.encoding);
198
+ filename = toBuffer(filename.replace(RE_BADCHAR, '').substr(0, 256), exports.encoding);
33
199
  return Buffer.concat([
34
- new Buffer('=ybegin line='+line_size+' size='+data.length+' name='),
200
+ toBuffer('=ybegin line='+line_size+' size='+data.length+' name='),
35
201
  filename, nl,
36
202
  y.encode(data, line_size),
37
- new Buffer('\r\n=yend size='+data.length+' crc32=' + y.crc32(data).toString('hex'))
203
+ toBuffer('\r\n=yend size='+data.length+' crc32=' + y.crc32(data).toString('hex'))
38
204
  ]);
39
205
  },
40
206
  multi_post: function(filename, size, parts, line_size) {
41
207
  return new YEncoder(filename, size, parts, line_size);
208
+ },
209
+ from_post: function(data, isRaw) {
210
+ if(!Buffer.isBuffer(data))
211
+ throw new TypeError('Expected string or Buffer');
212
+
213
+ var ret = {};
214
+
215
+ // find '=ybegin' to know where the yEnc data starts
216
+ var yencStart;
217
+ if(data.slice(0, 8).toString('hex') == '3d79626567696e20' /*=ybegin */) {
218
+ // common case: starts right at the beginning
219
+ yencStart = 0;
220
+ } else {
221
+ // otherwise, we have to search for the beginning marker
222
+ yencStart = bufferFind(data, '\r\n=ybegin ');
223
+ if(yencStart < 0)
224
+ return DecoderError('no_start_found', 'yEnc start marker not found');
225
+ yencStart += 2;
226
+ }
227
+ ret.yencStart = yencStart;
228
+
229
+ // find all start lines
230
+ var lines = [];
231
+ var sp = yencStart;
232
+ var p = bufferFind(data, '\r\n', yencStart+8);
233
+ while(p > 0) {
234
+ var line = data.slice(sp, p).toString(this.encoding).trim();
235
+ lines.push(line);
236
+ sp = p+2;
237
+ if(line.substr(0, 6) == '=yend ') { // no data in post
238
+ ret.yencEnd = sp;
239
+ break;
240
+ }
241
+
242
+ if(data[sp] != 0x3d /*=*/ || data[sp+1] != 0x79 /*y*/) {
243
+ ret.dataStart = sp;
244
+ break;
245
+ }
246
+ p = bufferFind(data, '\r\n', sp);
247
+ }
248
+ if(!ret.dataStart && !ret.yencEnd) // reached end of data but '=yend' not found
249
+ return DecoderError('no_end_found', 'yEnd end marker not found');
250
+
251
+ var ydata = {};
252
+ var warnings = decoderParseLines(lines, ydata);
253
+
254
+ if(!ret.yencEnd) {
255
+ var yencEnd = bufferFindRev(data.slice(ret.dataStart), '\r\n=yend ');
256
+ if(yencEnd < 0)
257
+ return DecoderError('no_end_found', 'yEnd end marker not found');
258
+
259
+ yencEnd += ret.dataStart;
260
+ ret.dataEnd = yencEnd;
261
+ p = bufferFind(data, '\r\n', yencEnd+8);
262
+ if(p < 0) {
263
+ warnings.push(DecoderWarning('missing_yend_newline', 'No line terminator found for =yend line'));
264
+ p = data.length;
265
+ ret.yencEnd = p;
266
+ } else
267
+ ret.yencEnd = p+2;
268
+ var endLine = data.slice(yencEnd+2, p).toString(this.encoding).trim();
269
+
270
+ warnings = warnings.concat(decoderParseLines([endLine], ydata));
271
+ }
272
+
273
+ ret.props = ydata;
274
+ // check properties
275
+ if(!ydata.begin.line || !ydata.begin.size || !('name' in ydata.begin)) // required properties, according to yEnc 1.2 spec
276
+ return DecoderError('missing_required_properties', 'Could not find line/size/name properties on ybegin line');
277
+ if(!ydata.end.size)
278
+ return DecoderError('missing_required_properties', 'Could not find size properties on yend line');
279
+
280
+ // check numerical fields
281
+ var NUMERICAL_FIELDS = {
282
+ begin: ['line', 'total', 'part', 'size'],
283
+ part: ['begin', 'end'],
284
+ end: ['size', 'part']
285
+ };
286
+ for(var tag in NUMERICAL_FIELDS) {
287
+ if(!ydata[tag]) continue;
288
+ NUMERICAL_FIELDS[tag].forEach(function(key) {
289
+ if(!ydata[tag][key]) return;
290
+ if(!RE_NUMBER.test(ydata[tag][key]))
291
+ warnings.push(DecoderWarning('invalid_prop_'+key, '`'+key+'` is not a number'));
292
+ else if(ydata[tag][key] === '0' && key != 'size') // most fields have to be at least one
293
+ warnings.push(DecoderWarning('zero_prop_'+key, '`'+key+'` cannot be 0'));
294
+ });
295
+ }
296
+ if(ydata.begin.part && ydata.end.part && ydata.begin.part != ydata.end.part)
297
+ warnings.push(DecoderWarning('part_number_mismatch', 'Part number specified in begin and end do not match'));
298
+ else if(ydata.begin.total && ((ydata.begin.part && +ydata.begin.total < +ydata.begin.part) || (ydata.end.part && +ydata.begin.total < +ydata.end.part)))
299
+ warnings.push(DecoderWarning('part_number_exceeds_total', 'Specified part number exceeds specified total'));
300
+
301
+ var expectedSize = +ydata.end.size;
302
+ if(ydata.part && ydata.part.begin && ydata.part.end) {
303
+ var partBegin = +ydata.part.begin;
304
+ var partEnd = +ydata.part.end;
305
+ if(partBegin > partEnd)
306
+ warnings.push(DecoderWarning('invalid_part_range', 'begin offset cannot exceed end offset'));
307
+ else if(expectedSize != partEnd-partBegin +1)
308
+ warnings.push(DecoderWarning('size_mismatch_part_range', 'Specified size does not match part range'));
309
+ else if(partEnd > +ydata.begin.size)
310
+ warnings.push(DecoderWarning('part_range_exceeds_size', 'Specified part range exceeds total file size'));
311
+ } else if(+ydata.begin.total > 1 || +ydata.begin.part > 1 || +ydata.end.part > 1)
312
+ warnings.push(DecoderWarning('missing_part_range', 'Part range not specified for multi-part post'));
313
+
314
+ ['pcrc32','crc32'].forEach(function(prop) {
315
+ if(ydata.end[prop] && !/^[a-fA-F0-9]{8}$/.test(ydata.end[prop]))
316
+ warnings.push(DecoderWarning('invalid_prop_'+prop, '`'+prop+'` is not a valid CRC32 value'));
317
+ });
318
+ if(!ydata.begin.part && ydata.begin.size != ydata.end.size)
319
+ warnings.push(DecoderWarning('size_mismatch', 'Size specified in begin and end do not match'));
320
+ else if(+ydata.begin.size < +ydata.end.size)
321
+ warnings.push(DecoderWarning('size_mismatch', 'Size specified for part exceeds size specified for whole file'));
322
+
323
+ if(ret.dataStart) {
324
+ ret.data = y.decode(data.slice(ret.dataStart, ret.dataEnd), isRaw);
325
+ ret.crc32 = y.crc32(ret.data);
326
+ var hexCrc = ret.crc32.toString('hex');
327
+
328
+ if(expectedSize != ret.data.length)
329
+ warnings.push(DecoderWarning('data_size_mismatch', 'Decoded data length doesn\'t match size specified in yend'));
330
+ if(ydata.end.pcrc32 && hexCrc != ydata.end.pcrc32.toLowerCase())
331
+ warnings.push(DecoderWarning('pcrc32_mismatch', 'Specified pcrc32 is invalid'));
332
+ if(ydata.end.crc32 && !ydata.part && hexCrc != ydata.end.crc32.toLowerCase())
333
+ // if single part, check CRC32 as well
334
+ warnings.push(DecoderWarning('crc32_mismatch', 'Specified crc32 is invalid'));
335
+ } else {
336
+ // empty article
337
+ if(expectedSize != 0)
338
+ warnings.push(DecoderWarning('data_size_mismatch', 'Decoded data length doesn\'t match size specified in yend'));
339
+ if(ydata.end.pcrc32 && ydata.end.pcrc32 != '00000000')
340
+ warnings.push(DecoderWarning('pcrc32_mismatch', 'Specified pcrc32 is invalid'));
341
+ if(ydata.end.crc32 && !ydata.part && ydata.end.crc32 != '00000000')
342
+ warnings.push(DecoderWarning('crc32_mismatch', 'Specified crc32 is invalid'));
343
+ }
344
+
345
+ if(warnings.length)
346
+ ret.warnings = warnings;
347
+
348
+ return ret;
42
349
  }
43
350
  };
44
351
 
@@ -51,17 +358,17 @@ function YEncoder(filename, size, parts, line_size) {
51
358
 
52
359
  this.part = 0;
53
360
  this.pos = 0;
54
- this.crc = new Buffer([0,0,0,0]);
361
+ this.crc = toBuffer([0,0,0,0]);
55
362
 
56
- filename = new Buffer(filename.replace(RE_BADCHAR, '').substr(0, 256), exports.encoding);
363
+ filename = toBuffer(filename.replace(RE_BADCHAR, '').substr(0, 256), exports.encoding);
57
364
  if(parts > 1) {
58
365
  this.yInfo = Buffer.concat([
59
- new Buffer(' total='+parts+' line='+line_size+' size='+size+' name='),
366
+ toBuffer(' total='+parts+' line='+line_size+' size='+size+' name='),
60
367
  filename, nl
61
368
  ]);
62
369
  } else {
63
370
  this.yInfo = Buffer.concat([
64
- new Buffer('=ybegin line='+line_size+' size='+size+' name='),
371
+ toBuffer('=ybegin line='+line_size+' size='+size+' name='),
65
372
  filename, nl
66
373
  ]);
67
374
  this.encode = this._encodeSingle;
@@ -72,7 +379,7 @@ var singleEncodeError = function() {
72
379
  };
73
380
  YEncoder.prototype = {
74
381
  encode: function(data) {
75
- if(!Buffer.isBuffer(data)) data = new Buffer(data);
382
+ if(!Buffer.isBuffer(data)) data = toBuffer(data);
76
383
 
77
384
  this.part++;
78
385
  if(this.part > this.parts)
@@ -93,11 +400,11 @@ YEncoder.prototype = {
93
400
  }
94
401
 
95
402
  var ret = Buffer.concat([
96
- new Buffer('=ybegin part='+this.part),
403
+ toBuffer('=ybegin part='+this.part),
97
404
  yInfo,
98
- new Buffer('=ypart begin='+( this.pos+1 )+' end='+end+'\r\n'),
405
+ toBuffer('=ypart begin='+( this.pos+1 )+' end='+end+'\r\n'),
99
406
  y.encode(data, this.line_size),
100
- new Buffer('\r\n=yend size='+data.length+' part='+this.part+' pcrc32='+crc.toString('hex')+fullCrc)
407
+ toBuffer('\r\n=yend size='+data.length+' part='+this.part+' pcrc32='+crc.toString('hex')+fullCrc)
101
408
  ]);
102
409
 
103
410
  this.pos = end;
@@ -105,7 +412,7 @@ YEncoder.prototype = {
105
412
  },
106
413
 
107
414
  _encodeSingle: function(data) {
108
- if(!Buffer.isBuffer(data)) data = new Buffer(data);
415
+ if(!Buffer.isBuffer(data)) data = toBuffer(data);
109
416
 
110
417
  if(this.size != data.length)
111
418
  throw new Error('File size doesn\'t match total data length');
@@ -120,7 +427,7 @@ YEncoder.prototype = {
120
427
  return Buffer.concat([
121
428
  yInfo,
122
429
  y.encode(data, this.line_size),
123
- new Buffer('\r\n=yend size='+data.length+' crc32=' + this.crc.toString('hex'))
430
+ toBuffer('\r\n=yend size='+data.length+' crc32=' + this.crc.toString('hex'))
124
431
  ]);
125
432
  }
126
433
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "yencode",
3
- "version": "1.0.8",
4
- "description": "Fast yEnc encoder and CRC32 calculator using compiled code",
3
+ "version": "1.1.2",
4
+ "description": "SIMD accelerated yEnc encoder/decoder and CRC32 calculator",
5
5
  "keywords": [
6
6
  "yenc",
7
7
  "yencode",