@ronomon/reed-solomon
Version:
Fast, reliable Reed-Solomon erasure coding as a native addon for Node.js
828 lines (809 loc) • 35.2 kB
JavaScript
var assert = require('assert');
var Node = { crypto: require('crypto') };
var Queue = require('@ronomon/queue');
var ReedSolomon = require('./binding.node');
function Args(options) {
var w = options.w === undefined ? 4 : options.w;
var k = options.k === undefined ? 3 : options.k;
var m = options.m === undefined ? 2 : options.m;
var create = false;
[
'sources',
'targets',
'buffer',
'bufferOffset',
'bufferSize',
'parity',
'parityOffset',
'paritySize'
].forEach(
function(key) {
if (options.hasOwnProperty(key)) create = true;
}
);
if (options.context) {
var context = options.context;
} else if (create) {
var context = ReedSolomon.create(k, m);
assert(Buffer.isBuffer(context));
} else {
var context = Buffer.alloc(3 + k * w * m * w);
context[0] = w;
context[1] = k;
context[2] = m;
}
var sources = options.sources;
if (sources === undefined) {
sources = 0;
for (var i = 0; i < k; i++) sources |= (1 << i);
}
var targets = options.targets;
if (targets === undefined) {
targets = 0;
for (var i = k; i < k + m; i++) targets |= (1 << i);
}
var bufferOffset = options.bufferOffset;
if (bufferOffset === undefined) bufferOffset = 0;
var bufferSize = options.bufferSize;
if (bufferSize === undefined) bufferSize = 8 * k;
var parityOffset = options.parityOffset;
if (parityOffset === undefined) parityOffset = 0;
var paritySize = options.paritySize;
if (paritySize === undefined) paritySize = 8 * m;
var buffer = options.buffer;
if (buffer === undefined) buffer = Buffer.alloc(bufferOffset + bufferSize);
var parity = options.parity;
if (parity === undefined) parity = Buffer.alloc(parityOffset + paritySize);
return [
context,
sources,
targets,
buffer,
bufferOffset,
bufferSize,
parity,
parityOffset,
paritySize,
function() {}
];
}
var BadArgs = {
create: 'bad arguments, expected: (int k, int m)',
encode: 'bad arguments, expected: (Buffer context, int sources, ' +
'int targets, Buffer buffer, int bufferOffset, int bufferSize, ' +
'Buffer parity, int parityOffset, int paritySize, function end)',
XOR: 'bad arguments, expected: (Buffer source, int sourceOffset, ' +
'Buffer target, int targetOffset, int size)'
};
function Bits(flags) {
var bits = 0;
while (flags > 0) {
if (flags & 1) bits++;
flags >>= 1;
}
return bits;
}
function Hash(buffer) {
var hash = Node.crypto.createHash('SHA256').update(buffer).digest('hex');
return hash.slice(0, 32);
}
function Inspect(args, buffer, parity) {
var self = Inspect;
console.log(new Array(50).join('-'));
var k = args[0];
var m = args[1];
var bufferOffset = args[2];
var parityOffset = args[3];
var shardSize = args[4];
for (var i = 0; i < k + m; i++) {
if (i === 0) {
var offset = self.offset(buffer, bufferOffset);
if (offset) console.log(offset);
} else if (i === k) {
var offset = self.offset(parity, parityOffset);
if (offset) console.log(offset);
}
var prefix = (i + (i < k ? ' (K)' : ' (M)') + ': ').padStart(8, ' ');
var suffix = ' (' + shardSize + ')';
if (i < k) {
var shard = buffer.slice(
bufferOffset + i * shardSize,
bufferOffset + i * shardSize + shardSize
);
} else {
var shard = parity.slice(
parityOffset + (i - k) * shardSize,
parityOffset + (i - k) * shardSize + shardSize
);
}
console.log(prefix + self.shard(shard) + suffix);
}
}
Inspect.offset = function(buffer, offset) {
var self = Inspect;
if (offset > 0) {
var prefix = ' ';
var suffix = ' (' + offset + ')';
return prefix + self.shard(buffer.slice(0, offset)) + suffix;
}
return '';
};
Inspect.shard = function(shard) {
var match = true;
for (var index = 1, length = shard.length; index < length; index++) {
if (shard[index] != shard[index - 1]) {
match = false;
break;
}
}
if (match) {
if (shard[0] === 0) return new Array(32 + 1).join('0');
if (shard[0] === 255) return new Array(32 + 1).join('f');
}
return Hash(shard);
};
function Random() {
var self = Random;
if (self.hash === undefined) self.hash = 1;
self.hash = ((self.hash + 0x7ED55D16) + (self.hash << 12)) & 0xFFFFFFF;
self.hash = ((self.hash ^ 0xC761C23C) ^ (self.hash >>> 19)) & 0xFFFFFFF;
self.hash = ((self.hash + 0x165667B1) + (self.hash << 5)) & 0xFFFFFFF;
self.hash = ((self.hash + 0xD3A2646C) ^ (self.hash << 9)) & 0xFFFFFFF;
self.hash = ((self.hash + 0xFD7046C5) + (self.hash << 3)) & 0xFFFFFFF;
self.hash = ((self.hash ^ 0xB55A4F09) ^ (self.hash >>> 16)) & 0xFFFFFFF;
return (self.hash & 0xFFFFFFF) / 0x10000000;
}
function Shuffle(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
function Slice(buffer, bufferOffset, shardSize, shardIndex) {
return buffer.slice(
bufferOffset + shardSize * shardIndex,
bufferOffset + shardSize * shardIndex + shardSize
);
}
function XOR(sources) {
var target = Buffer.alloc(sources[0].length);
var sourcesLength = sources.length;
while (sourcesLength--) {
var source = sources[sourcesLength];
for (var index = 0, length = source.length; index < length; index++) {
target[index] ^= source[index];
}
}
return target;
}
var namespace = 'ReedSolomon';
var B0 = Buffer.alloc(0);
var B1 = Buffer.alloc(1);
var B8 = Buffer.alloc(8);
var B16 = Buffer.alloc(16);
[
[ 'create', [], BadArgs.create ],
[ 'create', [1, 2, 3], BadArgs.create ],
[ 'create', ['1', '2'], BadArgs.create ],
[ 'create', [1.001, 2], BadArgs.create ],
[ 'create', [2, 1.001], BadArgs.create ],
[ 'create', [undefined, 1], BadArgs.create ],
[ 'create', [1 / 0, 1], BadArgs.create ],
[ 'create', [0, 1], 'k < 1' ],
[ 'create', [ReedSolomon.MAX_K + 1, 1], 'k > MAX_K' ],
[ 'create', [1, 0], 'm < 1' ],
[ 'create', [1, ReedSolomon.MAX_M + 1], 'm > MAX_M' ],
[ 'encode', [], BadArgs.encode ],
[ 'encode', Args({ context: B1 }), 'context.length < 3' ],
[
'encode',
Args({ context: Buffer.from([2,1,1]) }),
'context.length is bad'
],
[
'encode',
Args({ context: Buffer.from([2,1,1,1,1,1,1]) }),
'bitmatrix not optimized'
],
[ 'encode', Args({ w: 0 }), 'w != 2, 4, 8' ],
[ 'encode', Args({ w: 1 }), 'w != 2, 4, 8' ],
[ 'encode', Args({ w: 3 }), 'w != 2, 4, 8' ],
[ 'encode', Args({ w: 5 }), 'w != 2, 4, 8' ],
[ 'encode', Args({ w: 6 }), 'w != 2, 4, 8' ],
[ 'encode', Args({ w: 7 }), 'w != 2, 4, 8' ],
[ 'encode', Args({ w: 9 }), 'w != 2, 4, 8' ],
[ 'encode', Args({ k: 0 }), 'k < 1' ],
[ 'encode', Args({ k: ReedSolomon.MAX_K + 1 }), 'k > MAX_K' ],
[ 'encode', Args({ m: 0 }), 'm < 1' ],
[ 'encode', Args({ m: ReedSolomon.MAX_M + 1 }), 'm > MAX_M' ],
[ 'encode', Args({ w: 2, k: 3, m: 2 }), 'k + m > (1 << w)' ],
[ 'encode', Args({ w: 4, k: 13, m: 4 }), 'k + m > (1 << w)' ],
[ 'encode', Args({ k: 1, m: 1, sources: 4 }), 'sources > k + m' ],
[ 'encode', Args({ k: 1, m: 1, sources: 0 }), 'sources == 0' ],
[ 'encode', Args({ k: 2, m: 1, sources: 1 }), 'sources < k' ],
[ 'encode', Args({ k: 1, m: 1, targets: 4 }), 'targets > k + m' ],
[ 'encode', Args({ k: 1, m: 1, targets: 0 }), 'targets == 0' ],
[ 'encode', Args({ k: 1, m: 1, targets: 3 }), 'targets > m' ],
[ 'encode', Args({ targets: 1 }), '(sources & targets) != 0' ],
[ 'encode', Args({ bufferSize: 0 }), 'bufferSize == 0' ],
[
'encode',
Args({ bufferOffset: 4294967296, bufferSize: 8, buffer: B8 }),
BadArgs.encode
],
[
'encode',
Args({ bufferOffset: 0, bufferSize: 4294967296, buffer: B8 }),
BadArgs.encode
],
[
'encode',
Args({ bufferOffset: 1, bufferSize: 8, buffer: B8 }),
'bufferOffset + bufferSize > buffer.length'
],
[
'encode',
Args({ bufferOffset: 4294967295, bufferSize: 8, buffer: B8 }),
'bufferOffset + bufferSize > buffer.length'
],
[ 'encode', Args({ bufferSize: 16 }), 'bufferSize % k != 0' ],
[ 'encode', Args({ bufferSize: 30 }), 'shardSize % w != 0' ],
[ 'encode', Args({ bufferSize: 12 }), 'shardSize % 8 != 0' ],
[ 'encode', Args({ paritySize: 0 }), 'paritySize == 0' ],
[ 'encode', Args({ paritySize: 7 }), 'paritySize % m != 0' ],
[ 'encode', Args({ paritySize: 8 }), 'paritySize / m != bufferSize / k' ],
[
'encode',
Args({ parityOffset: 4294967296, paritySize: 16, parity: B16 }),
BadArgs.encode
],
[
'encode',
Args({ parityOffset: 0, paritySize: 4294967296, parity: B16 }),
BadArgs.encode
],
[
'encode',
Args({ parityOffset: 1, paritySize: 16, parity: B16 }),
'parityOffset + paritySize > parity.length'
],
[
'encode',
Args({ parityOffset: 4294967295, paritySize: 16, parity: B16 }),
'parityOffset + paritySize > parity.length'
],
[ 'search', [undefined], 'expected no arguments' ],
[ 'XOR', [], BadArgs.XOR ],
[ 'XOR', [null, 0, null, 0, 0], BadArgs.XOR ],
[ 'XOR', [B1, 0, B1, 0, -1], BadArgs.XOR ],
[ 'XOR', [B1, 4294967296, B1, 0, 1], BadArgs.XOR ],
[ 'XOR', [B1, 0, B1, 4294967296, 1], BadArgs.XOR ],
[ 'XOR', [B1, 0, B1, 0, 4294967296], BadArgs.XOR ],
[ 'XOR', [B0, 1, B0, 0, 0], 'sourceOffset + size > source.length' ],
[ 'XOR', [B0, 0, B1, 0, 1], 'sourceOffset + size > source.length' ],
[ 'XOR', [B1, 4294967295, B1, 0, 1], 'sourceOffset + size > source.length' ],
[ 'XOR', [B0, 0, B0, 1, 0], 'targetOffset + size > target.length' ],
[ 'XOR', [B1, 0, B0, 0, 1], 'targetOffset + size > target.length' ],
[ 'XOR', [B1, 0, B1, 4294967295, 1], 'targetOffset + size > target.length' ]
].forEach(
function(exception) {
var error;
try {
ReedSolomon[exception[0]](...exception[1]);
} catch (e) {
error = e.message;
}
if (error !== exception[2]) {
throw new Error(
JSON.stringify(error) + ' !== ' + JSON.stringify(exception[2])
);
}
}
);
(function() {
var xors = 100;
while (xors--) {
if (Random() < 0.6) {
var size = Math.floor(Random() * 65536);
} else {
var size = Math.floor(Random() * 8);
}
var sourceOffset = Math.floor(Random() * 16);
var targetOffset = Math.floor(Random() * 16);
var cipher = Node.crypto.createCipheriv(
'AES-256-CTR',
Buffer.alloc(32),
Buffer.alloc(16)
);
var buffer = cipher.update(
Buffer.alloc(sourceOffset + size + targetOffset + size)
);
cipher.final();
var source = buffer;
var target = buffer;
targetOffset += sourceOffset;
targetOffset += size;
var expect = Buffer.from(buffer);
for (var index = 0; index < size; index++) {
expect[targetOffset + index] ^= source[sourceOffset + index];
}
ReedSolomon.XOR(source, sourceOffset, target, targetOffset, size);
assert(Hash(target) === Hash(expect));
}
})();
var queue = new Queue(1);
queue.onData = function(args, end) {
// Use this to regenerate the fixed test vectors:
var regenerate = false;
var k = args[0];
var m = args[1];
var bufferOffset = args[2];
var parityOffset = args[3];
var shardSize = args[4];
var vector = args[5];
var context = ReedSolomon.create(k, m);
var w = context[0];
console.log(new Array(50).join('='));
console.log(' W=' + w + ' K=' + k + ' M=' + m);
assert(w == 2 || w == 4 || w == 8);
assert(k + m <= (1 << w));
assert(context.length === 3 + k * w * m * w);
var bufferSize = shardSize * k;
var paritySize = shardSize * m;
var seed = Node.crypto.createHash('SHA256');
seed.update([k, m, bufferOffset, parityOffset, shardSize].join(','));
var cipher = Node.crypto.createCipheriv(
'AES-256-CTR',
seed = seed.digest(),
Buffer.alloc(16)
);
var buffer = cipher.update(Buffer.alloc(bufferOffset + bufferSize));
assert(buffer.length === bufferOffset + bufferSize);
var parity = cipher.update(Buffer.alloc(parityOffset + paritySize));
if (parityOffset) {
assert(
cipher.update(Buffer.alloc(parityOffset)).copy(parity, 0) === parityOffset
);
}
cipher.final(); // Free cipher.
var hashContext = Hash(context);
var hashBuffer = Hash(buffer.slice(0, bufferOffset));
var hashParity = Hash(parity.slice(0, parityOffset));
var shards = [];
var hashes = [];
var sources = 0;
var targets = 0;
for (var i = 0; i < k; i++) {
shards[i] = Slice(buffer, bufferOffset, shardSize, i);
hashes[i] = Hash(shards[i]);
sources |= (1 << i);
}
for (var i = k; i < k + m; i++) {
shards[i] = Slice(parity, parityOffset, shardSize, i - k);
targets |= (1 << i);
}
Inspect(args, buffer, parity);
ReedSolomon.encode(
context,
sources,
targets,
buffer,
bufferOffset,
bufferSize,
parity,
parityOffset,
paritySize,
function(error) {
if (error) return end(error);
Inspect(args, buffer, parity);
assert(Hash(context) == hashContext);
assert(Hash(buffer.slice(0, bufferOffset)) == hashBuffer);
assert(Hash(parity.slice(0, parityOffset)) == hashParity);
for (var i = 0; i < k; i++) assert(Hash(shards[i]) === hashes[i]);
for (var i = k; i < k + m; i++) hashes[i] = Hash(shards[i]);
// Test against fixed vector:
var result = Hash(hashes.join(','));
if (regenerate) {
console.log(' [' +
[
k.toString().padStart(2, ' '),
m,
bufferOffset.toString().padStart(2, ' '),
parityOffset.toString().padStart(2, ' '),
shardSize.toString().padStart(6, ' '),
"'" + result + "'"
].join(', ') + ']' +
(
(
k == ReedSolomon.MAX_K &&
m == ReedSolomon.MAX_M &&
shardSize > 8
) ? '' : ','
)
);
} else {
assert(result === vector);
}
// First parity shard must always be an XOR of all data shards:
assert(Hash(XOR(shards.slice(0, k))) === hashes[k]);
if (k === 1) {
// All shards must be identical when k=1:
for (var i = 1; i < k + m; i++) assert(hashes[i] === hashes[0]);
}
// Choose source and target shards:
var sources = 0;
var targets = 0;
var voided = [];
var indices = [];
for (var i = 0; i < k + m; i++) indices.push(i);
Shuffle(indices);
var targetsIndex = 0;
var targetsLength = Math.ceil(Random() * m);
while (targetsIndex < targetsLength) {
targets |= (1 << indices[targetsIndex++]);
}
var sourcesIndex = 0;
var sourcesLength = k + Math.floor(Random() * (m - targetsLength));
while (sourcesIndex < sourcesLength) {
sources |= (1 << indices[targetsLength + sourcesIndex++]);
}
assert((sources & targets) == 0);
assert(Bits(sources) >= k && Bits(sources) <= k + m);
assert(Bits(targets) >= 1 && Bits(targets) <= m);
for (var i = 0; i < k + m; i++) {
voided[i] = undefined;
if (targets & (1 << i)) {
shards[i].fill(0);
} else if ((sources & (1 << i)) == 0 && Random() < 0.5) {
shards[i].fill(255);
voided[i] = Hash(shards[i]);
}
}
Inspect(args, buffer, parity);
ReedSolomon.encode(
context,
sources,
targets,
buffer,
bufferOffset,
bufferSize,
parity,
parityOffset,
paritySize,
function(error) {
if (error) return end(error);
Inspect(args, buffer, parity, sources, targets);
assert(Hash(context) === hashContext);
assert(Hash(buffer.slice(0, bufferOffset)) === hashBuffer);
assert(Hash(parity.slice(0, parityOffset)) === hashParity);
var strictVoided = (
k === 1 ||
(
Bits(targets) === 1 &&
Bits(sources % (1 << (k + 1))) === k &&
Bits(targets % (1 << (k + 1))) === 1
)
);
for (var i = 0; i < k + m; i++) {
var hashShard = Hash(shards[i]);
if (voided[i]) {
// Shard was neither a source nor target.
if (strictVoided) {
// We expect an optimization to avoid the shard:
assert(hashShard === voided[i]);
} else {
// We are free to repair the shard if needed for other shards:
assert(hashShard === hashes[i] || hashShard === voided[i]);
}
} else {
assert(hashShard === hashes[i]);
}
}
end();
}
);
}
);
};
queue.onEnd = function(error) {
if (error) throw error;
console.log(new Array(50).join('='));
console.log(' PASSED');
console.log(new Array(50).join('='));
};
assert(typeof ReedSolomon.search === 'function');
assert(ReedSolomon.MAX_K === 24);
assert(ReedSolomon.MAX_M === 6);
queue.concat([
[ 1, 1, 3, 2, 8, '8f2f6338f7f86123959816e8fbb3ce1f'],
[ 1, 1, 4, 2, 77856, '47b8befeab9ff4548d46121e3fd311e4'],
[ 1, 2, 11, 3, 8, '1fa2a2bc51a887746a9f7dc69a29cbb2'],
[ 1, 2, 0, 13, 217840, '43549bf420c1bd77893663eb3371a085'],
[ 1, 3, 8, 1, 8, '0dc8f67f13b2e6380104e5ada1d233ce'],
[ 1, 3, 8, 11, 206528, '12092146e2a5c8f11a4ac759247a4e8b'],
[ 1, 4, 15, 13, 8, '082b81d7bc913f907280fcfa4579fb5c'],
[ 1, 4, 4, 11, 201528, 'c0ebbde41e413580c97d28d55e2aab4e'],
[ 1, 5, 2, 12, 8, '729b3f1931d1f07e80650deff1c23ce5'],
[ 1, 5, 1, 11, 75128, '1e425618dcdb194067f58bfa4ebdce9f'],
[ 1, 6, 6, 4, 8, '8dd97aeb6b46b77000561477b90f1a43'],
[ 1, 6, 4, 5, 258144, '7e78c7b99df20a8e0e40a00a1de90b34'],
[ 2, 1, 4, 12, 8, '4d4835bb6a337283fc164fe11f2f84bc'],
[ 2, 1, 0, 13, 128864, '4bfb334fc675aa2024ac1497645f38cc'],
[ 2, 2, 12, 2, 8, 'a682beb83c43fc40f600f9ee1906c8d2'],
[ 2, 2, 11, 3, 213528, '84d767ff54f2ca67edea4f63be828140'],
[ 2, 3, 8, 14, 8, 'd2a21c6f6afc715dd9ee4628d3ba038a'],
[ 2, 3, 4, 2, 117448, '7d365f2f9231e8310776fa6529200e27'],
[ 2, 4, 4, 10, 8, '6032933d617d875530a5c584b39b3b52'],
[ 2, 4, 9, 15, 228168, 'fdb0527b81e8cb1c5320fe8b31e53cc8'],
[ 2, 5, 9, 6, 8, '597d248d1ec32aa237e80d8f6e7d2873'],
[ 2, 5, 0, 14, 129032, 'd138d700195c0fa765dbaa937a884f87'],
[ 2, 6, 12, 12, 8, '77309990546426fc6794dbff3549f579'],
[ 2, 6, 3, 4, 108888, '5864a7af80772917108042c6b039e42f'],
[ 3, 1, 5, 9, 8, '81d0059cfcd0303dbd944220e76f3187'],
[ 3, 1, 0, 15, 254320, '9388f521ad51f8301bd34c11d7b2b0d6'],
[ 3, 2, 3, 12, 8, '21bb16aa96305d1e6d63ac1cfb524b71'],
[ 3, 2, 14, 0, 123136, 'a205985140347e7b75a668516ab9e695'],
[ 3, 3, 6, 9, 8, '02c0985420b4b21d10a2cf87e8cb288f'],
[ 3, 3, 6, 9, 34488, 'a55516606faccb6f5d08b5219b263ab4'],
[ 3, 4, 12, 6, 8, 'bf581ff28c5d94255211a78f96b38a6c'],
[ 3, 4, 5, 3, 162040, '356c2ee083f931cf375fbbd1eb3de294'],
[ 3, 5, 11, 13, 8, '3363d49814998e74952fa970aafc737e'],
[ 3, 5, 5, 9, 57904, 'b58b87f1ce5f8c78399bb774cbe15bc6'],
[ 3, 6, 13, 0, 8, 'a306881dfd5f1d7a0275c11fd0b385eb'],
[ 3, 6, 7, 11, 71200, '3628820c2ba756a0422fdcf81d7ba5ad'],
[ 4, 1, 8, 8, 8, 'b880b7cc70a430d28163ce2e9c47c9fd'],
[ 4, 1, 2, 9, 199648, 'a42df9841cf12b7317866436f5250110'],
[ 4, 2, 5, 1, 8, 'b817e4953b2e69fcd89fdd431b81d8d6'],
[ 4, 2, 4, 8, 84816, '2f2c7e6f50f7a8ffb99ae4b9b1682896'],
[ 4, 3, 7, 8, 8, '9280ccf9421e96d01a4e2da3b6e16764'],
[ 4, 3, 10, 0, 158184, 'd6667e932a2549a3b62161a79e990205'],
[ 4, 4, 2, 9, 8, 'bdea43c206917dd16ae61a45b71ab021'],
[ 4, 4, 1, 2, 255704, '77c53b5724f4839043c41bad2a54c806'],
[ 4, 5, 12, 6, 8, '678df1e0eb2317be96af5492de1ab594'],
[ 4, 5, 4, 8, 109160, 'cd018fc2af02f55d52def647b797c6d1'],
[ 4, 6, 5, 3, 8, '268cba36dd6fe623a6e058c631de29da'],
[ 4, 6, 15, 9, 20640, '11d3c5b7328bcca99340465b6bd5c28f'],
[ 5, 1, 6, 8, 8, '8c19fc188792a7682bf1c927996e808d'],
[ 5, 1, 4, 6, 33696, '234103bc31ed69cc532687ec7d379ffc'],
[ 5, 2, 12, 2, 8, '547b4bd53452169f1a032ff7767e27dc'],
[ 5, 2, 4, 0, 222464, 'cf25d5baaa27fd0369366c05d110621a'],
[ 5, 3, 5, 10, 8, 'cb3bc8560051466c3e99ac28735a09c1'],
[ 5, 3, 14, 8, 201616, '11f685cd21d627c6becb3436a3f38ac8'],
[ 5, 4, 6, 11, 8, '1dcd62393c2a0bbb36325707b424074a'],
[ 5, 4, 9, 3, 158328, '667b92e9ee1b8c90f63b5d22af4777e8'],
[ 5, 5, 12, 8, 8, '8e887bcd2c47e5aeb7cb9555abd51974'],
[ 5, 5, 9, 2, 246456, '2070ae39545ac6219782f339b4328cdb'],
[ 5, 6, 4, 6, 8, '5b0bdb939ce39bfa45a2b3f8bcf3ab73'],
[ 5, 6, 0, 12, 140752, 'd37328a72808f4b39bd8d5124d5e46e1'],
[ 6, 1, 2, 14, 8, 'b6d4ee256dbb2b0c3983d48365b05aee'],
[ 6, 1, 3, 4, 259664, '842f4cee3895cd5351610643a7dcd477'],
[ 6, 2, 8, 5, 8, '2ab266a7843a651b6565183727f4b3dc'],
[ 6, 2, 3, 4, 211216, 'a015106aeeb37e951c076e151c8f0b47'],
[ 6, 3, 5, 10, 8, '78f42cbe317191fdf6973aa62aa29b03'],
[ 6, 3, 13, 6, 46472, '706281f04510f6a7b44eb75119ba1fee'],
[ 6, 4, 11, 14, 8, 'b6ca87806834d540d0e8172c4ea811d5'],
[ 6, 4, 9, 2, 162048, 'ab7e05bbbd859367de230f22824561b9'],
[ 6, 5, 2, 5, 8, 'aaf948718ece3e36af12db2927d530df'],
[ 6, 5, 9, 12, 259376, '3c1215b346e30acfe50deae45c295b39'],
[ 6, 6, 2, 11, 8, 'f23deb29c6397a656fc64773e9723536'],
[ 6, 6, 0, 14, 252312, 'ada74fff2ce463c5cd9cdf9ba65855e2'],
[ 7, 1, 3, 14, 8, 'c639d633efc8f1f2d3da0dc54c67a6e4'],
[ 7, 1, 14, 15, 112488, '52b6ccef2f7c8f6f5c435ec55ecce94f'],
[ 7, 2, 4, 5, 8, 'e55ddc42a5cbd8f38e7d691593de7da4'],
[ 7, 2, 8, 2, 128216, '51300f47646591a32d6c0558f8ea7073'],
[ 7, 3, 3, 1, 8, '816cee5ffddbfbb6cdebf682e4819de6'],
[ 7, 3, 10, 11, 83536, '79c1e823274b316aba47519bef446c72'],
[ 7, 4, 9, 12, 8, 'c0c6166b9d749095e310fefccc2f61cc'],
[ 7, 4, 3, 5, 15920, 'aa65d39a23a94b2767b1b6e69201f4d1'],
[ 7, 5, 14, 3, 8, '703f918dbcc005b793840935317b4d2a'],
[ 7, 5, 10, 15, 255008, '2fac6e471bcaba4b170edf1580e67d98'],
[ 7, 6, 14, 3, 8, '8e4ee85339bcf4066f9f45e0dc6b3210'],
[ 7, 6, 10, 9, 8096, '3ead0c0cc1085c42cd4f9d0b38feb978'],
[ 8, 1, 2, 13, 8, '38697ab421dc43c64fac9a0e833e5d62'],
[ 8, 1, 2, 9, 217760, '22069802fbcd3e49c588ad08a0e16cb2'],
[ 8, 2, 2, 13, 8, 'bca46cda037db6001ca93868ea869e97'],
[ 8, 2, 9, 7, 181320, '69bc6bd2bd362e8f09c49fb82d7ee371'],
[ 8, 3, 15, 3, 8, '3e192cd7004deb2da5d17051e187e04f'],
[ 8, 3, 13, 4, 168928, '93197bb51c3a43372742dad1290f26ac'],
[ 8, 4, 12, 4, 8, '56f1f1196beb6aa01ddde9d424b016d3'],
[ 8, 4, 3, 11, 227800, '9bc78101484c024c89a7959a1c7cb214'],
[ 8, 5, 12, 0, 8, 'b239e58d357293e0ef56885ef7d4ca6b'],
[ 8, 5, 12, 1, 135616, '3d24f48d9ab3fb06d471be24c535f41d'],
[ 8, 6, 2, 15, 8, '89d3c9c9f9176b4e8f99abfff60f0af9'],
[ 8, 6, 7, 6, 79672, 'd6b6b662ee56711be67c0ceef9a7b98c'],
[ 9, 1, 9, 6, 8, '462e5787fd5efcc1573bfec106010b31'],
[ 9, 1, 5, 11, 71488, '4d6d380922fcd790873abf65ff8f6a50'],
[ 9, 2, 10, 10, 8, '68416a9be47a4fc052c11ac45a0e7a04'],
[ 9, 2, 15, 1, 173096, '1d788532d2eb9aca722ad7d8110296b9'],
[ 9, 3, 10, 13, 8, '38ae8241c553daf9f38c17d16892b406'],
[ 9, 3, 14, 0, 178192, '8bfa1c9bacc394f66571f1640eec70a8'],
[ 9, 4, 5, 3, 8, 'a33a3da115298d350461ed264b871188'],
[ 9, 4, 3, 15, 220120, '9998c110cd9372ff3b28bf86a1190d3e'],
[ 9, 5, 1, 6, 8, 'f625bfca82e2a1a0dcb61fe4b9077910'],
[ 9, 5, 15, 11, 149080, 'f02e09b26bd622664ad6344f070b1a1c'],
[ 9, 6, 6, 4, 8, '7b8659859a9310421cedfc16979e8abe'],
[ 9, 6, 3, 1, 190064, 'd46e8b3de14902ea04836c1be7abe142'],
[10, 1, 3, 13, 8, 'a18a04a81303ce3444b61ddb492233f6'],
[10, 1, 9, 15, 47800, '48506ae505d413bfcad976722e96d426'],
[10, 2, 11, 12, 8, 'f10ae500f9eb274f070132318400ef1b'],
[10, 2, 12, 15, 159360, '591ea0defc50529a7add19ebcf744a0f'],
[10, 3, 10, 5, 8, '61dc803b39a3bb417e46d58f61279203'],
[10, 3, 13, 12, 248992, '1dd39162588c68e0d30a40d0e45711aa'],
[10, 4, 7, 3, 8, '3e0212d68950ead260fa854a53b33fc7'],
[10, 4, 7, 14, 223304, '69e0af8a72a36f6f90c872601331b20c'],
[10, 5, 12, 0, 8, 'ac445f54594f7ff867aec5cde0ae712c'],
[10, 5, 12, 7, 144432, '80e22dd16e0b93d1981d1de20d8e97e0'],
[10, 6, 14, 12, 8, 'a364714d66982b75bbe67ff6513f25aa'],
[10, 6, 8, 1, 176032, '1fb7e3ba784eae89368d8481b1f62285'],
[11, 1, 11, 9, 8, 'a7c2f99571d10ed3d770d1a1692daf91'],
[11, 1, 13, 1, 112104, '666d12634e828f39f19aff255421bceb'],
[11, 2, 8, 7, 8, 'd3aff352656129f649a169628b49e93f'],
[11, 2, 11, 1, 162368, '310a399c0e1227cd27adf937affdb5e9'],
[11, 3, 5, 2, 8, '0c4d5fe5a4bacc7cc330917cece33ff6'],
[11, 3, 15, 9, 170576, '54de9001c0a3bc909c442de3fe64e8cb'],
[11, 4, 1, 5, 8, 'da5712c4fdc5336daf7322f2c3c106a1'],
[11, 4, 15, 3, 116728, 'f3333f9a7b45a30d20a352ea6ba6d70b'],
[11, 5, 3, 13, 8, '2cafc9f5bdd41ccd64f2a9a69cf1a49f'],
[11, 5, 8, 14, 238784, '8f553c10ff469c9c2dbe68709b93d852'],
[11, 6, 2, 2, 8, '03715ae86707f468a401647f7539f66d'],
[11, 6, 13, 14, 162520, 'cfbae98f4b676bf40a2beddff035b87c'],
[12, 1, 7, 1, 8, 'aa9bd24d2b65c61ae5122651e4e69b9c'],
[12, 1, 0, 7, 206232, '69625790455158c0782c8342b31b3c82'],
[12, 2, 13, 0, 8, 'f5f3f10208a72617730730651037c71d'],
[12, 2, 12, 14, 85216, '2c69cbef8617d5d44f9865bd1c336887'],
[12, 3, 12, 13, 8, '0181fe0f9abb255bcd5dc0b1609b714e'],
[12, 3, 10, 8, 91864, '7cd28d74aa9a90506ec5e14a472c0094'],
[12, 4, 5, 15, 8, 'be6e87003f2fc7ad97f1f6a24b94181f'],
[12, 4, 3, 6, 13296, 'f22aa82a322d8eeac5835944ebdcf5f8'],
[12, 5, 14, 15, 8, '6c5bd0c21f9aa0f2cf2c00a71da64439'],
[12, 5, 2, 15, 127128, '2ff102f5838e5a2e5d73af214552ac03'],
[12, 6, 10, 14, 8, 'd1768bf23e4d5433a74f8a19eccbbd88'],
[12, 6, 15, 9, 152184, 'dc36000e922135b5ad33356fa2e33f80'],
[13, 1, 0, 1, 8, 'd5936ff9f94aa63b4eb822e1dd3cb4a2'],
[13, 1, 9, 7, 32824, '20691577c9f13eece9504d68455de8f4'],
[13, 2, 7, 12, 8, 'b7448ecc49a2f2e30033aa9a12f7819d'],
[13, 2, 0, 9, 259496, 'ae74241f4791d891d50b063462bc81d5'],
[13, 3, 2, 13, 8, '07d5143bfc4dd940fbff434ea85b539a'],
[13, 3, 12, 2, 101856, '5dd2d48f0c46f061cdc6f4ba874ed84b'],
[13, 4, 15, 5, 8, 'fef0c58a3ce672917f29f758c804cb66'],
[13, 4, 2, 11, 140632, '32f0c1c518793475a5e25443b448501a'],
[13, 5, 4, 2, 8, '861faa4b169da02930625063843be0fd'],
[13, 5, 7, 10, 69032, '42354a7d9247b6ac4703eefc60216c47'],
[13, 6, 12, 8, 8, '7d9a1c1f952652b1a63caf476169948d'],
[13, 6, 5, 6, 94904, 'ab6c26837919444f45d9420e8c439c6d'],
[14, 1, 4, 0, 8, 'f7f2c26468cec7f6bf0577c14db7c1fc'],
[14, 1, 8, 13, 106240, 'f28f7012396f0b921c11f0f61f73f49d'],
[14, 2, 9, 3, 8, 'b2858fc37a35e4adb2c6511cfc32fce8'],
[14, 2, 15, 7, 141536, '1c64a44dbe6411713d795e32b46b740c'],
[14, 3, 3, 2, 8, 'f01b0a230def453222247a0f214d5fd4'],
[14, 3, 1, 4, 207248, 'e653805e77e2c0fe74c535ff0898cde0'],
[14, 4, 6, 6, 8, '299db94c14bef44307791ebdc488c5b1'],
[14, 4, 2, 2, 230936, '3d301a614429d322cd50788f1360399f'],
[14, 5, 10, 15, 8, '80bd661561d6b0e9770303ee6cd0b96a'],
[14, 5, 6, 15, 56504, '7b6e8fcb149bc3948ecf37491c660c3c'],
[14, 6, 8, 11, 8, '7b3e5f4d82e17d8ed28250b573d034e1'],
[14, 6, 9, 10, 93864, 'ca2971289c73b23eefa970b3e6ed8e9d'],
[15, 1, 7, 0, 8, '64bf4a173b4d5c9cfcb560fa1ef64f8f'],
[15, 1, 7, 13, 76904, 'ebb356693d10980aa037ded2fb0f3115'],
[15, 2, 8, 11, 8, '1bc9f6036fcc33ca45cff91c6bd83ac4'],
[15, 2, 12, 12, 148768, '6862097e46f6be94ff5ecfecf463446c'],
[15, 3, 13, 4, 8, '4035fa3cb6feb6a169cc8495bd9480f6'],
[15, 3, 4, 8, 179960, 'b917cb12baabc7a749c88392b80620bc'],
[15, 4, 12, 12, 8, '9019370f0de5915e8eb4f4e3c0e63f03'],
[15, 4, 0, 0, 107120, '858507f0ac93a3a9998798ce2bfb87e2'],
[15, 5, 0, 11, 8, 'ea50f0c76a20a84bf5e98d64750acb99'],
[15, 5, 11, 11, 114840, '0ce5d54cab2dc9ef2ddcb8ac042fc844'],
[15, 6, 12, 0, 8, 'b2a154ca29a843255a6cb20bac5d810e'],
[15, 6, 14, 12, 247000, '6136c7d5a5236097f24b1ae1f81ca4c1'],
[16, 1, 10, 1, 8, '274015d3ff2e349a31d9b5610d191b75'],
[16, 1, 1, 11, 113032, '56c9bcbcee78f300fec5cfe26e984a2b'],
[16, 2, 14, 9, 8, '3fe3dc791507d63984b2528ab8a1ddf7'],
[16, 2, 7, 10, 234120, '85c9cc314abf57df17677ba18a860f93'],
[16, 3, 11, 12, 8, 'cf7bbdf05cac9601f11818aaabfac258'],
[16, 3, 14, 10, 93072, 'd8cc80ceb4ee66c878d303f703b25991'],
[16, 4, 14, 9, 8, '0fcea6b86f34e40febd63d9d583f7d9e'],
[16, 4, 15, 7, 261248, '687ff8c9e2d961ccd42f179c34eaf5ec'],
[16, 5, 8, 15, 8, '7f564f749f537f4e13160803ffd8b009'],
[16, 5, 5, 6, 584, '9cce329617fcb1f2e62bd0caadefca3a'],
[16, 6, 8, 14, 8, '0cf8e5e8fc49a573b4a2ae0cf5dd79a2'],
[16, 6, 8, 10, 161536, 'a78b659348f720aee81414d47ed9a65f'],
[17, 1, 15, 5, 8, '9946dbb071338ddc31eaa9eae7a9855f'],
[17, 1, 8, 11, 76800, '3589f9313a8d60685dbc35b1de8053cb'],
[17, 2, 12, 14, 8, '402d16366552b0e044e39a6414e6bba9'],
[17, 2, 11, 4, 245336, 'b54728063969bad6a09e12883ea43a0b'],
[17, 3, 3, 9, 8, '1218ebb5b52817f108538287269b135d'],
[17, 3, 14, 5, 16080, '0695b6886d2317442f95327ae3ccc0bf'],
[17, 4, 13, 10, 8, 'd5574fed01cb2ac1890d9deaff0a1287'],
[17, 4, 13, 7, 80056, 'bc60b3b01871054449723adbf78f9fb1'],
[17, 5, 1, 4, 8, 'e4ea822b53fc7bd0cb063f75b7b71bdb'],
[17, 5, 6, 2, 97896, '499958f0bb68065a514aa34da6229669'],
[17, 6, 15, 0, 8, '30e9cd78f40521bebb65f410a63ead99'],
[17, 6, 9, 5, 232400, '12768e52b6e92a533ef8cce9e6788d0e'],
[18, 1, 2, 15, 8, 'dd06185915836a7cf55d78005b4930d0'],
[18, 1, 9, 5, 62616, 'ee46bf610d7fc136679ab4c34584042f'],
[18, 2, 12, 2, 8, '33e8cecd0742597aa2efff4f0c2225ba'],
[18, 2, 15, 7, 85776, '5496bea384c4b66bb66af0eb057886b9'],
[18, 3, 7, 2, 8, '3ff76f7137dec141897f7fd6b7777700'],
[18, 3, 12, 13, 65176, '72d742b2f7ce4c21669c8470c54846ef'],
[18, 4, 6, 6, 8, '2ddede699a3133828f0e82f224b7679c'],
[18, 4, 10, 4, 254048, '446475327d52cbbe522abef4e583ef18'],
[18, 5, 2, 13, 8, '2e0ae42d5061e7d1ecc510587c5a09eb'],
[18, 5, 0, 8, 62248, '0b313d5e1a51e080324f3289bfd5874b'],
[18, 6, 13, 15, 8, '59711df18a765b38524efdaa7b74bbf1'],
[18, 6, 1, 3, 240272, '68487526400e6784e65d91fe99c02852'],
[19, 1, 8, 11, 8, '2973ef25e14cb6c53b54a742262d89ba'],
[19, 1, 0, 14, 104240, 'ec7f3a2c2a03f488e2ba68f900da0a91'],
[19, 2, 15, 14, 8, '08e8bb8770723a0963196a20cc6e2b18'],
[19, 2, 12, 0, 153840, '4f5090c62b06504e21c910a2e97f8fd9'],
[19, 3, 15, 7, 8, '242d89575bee93ec48511127ae1f09ff'],
[19, 3, 11, 9, 70200, '900fe857cf1ba44ec3af7023ebd2ac9c'],
[19, 4, 0, 11, 8, '2d69e80e55cd0f5ab037879f61b1bb5a'],
[19, 4, 11, 7, 63080, 'b3d8f9475ecd1a9a8e1854ea3e4339f7'],
[19, 5, 14, 11, 8, 'bdf1330fddfb051f4d75fbcb65c5c6c0'],
[19, 5, 13, 9, 3408, 'c9a053ef204ed82c6bc7d853adbc8264'],
[19, 6, 1, 8, 8, '9c8bccde0c80c8db6d2e5ae9e71e5724'],
[19, 6, 1, 3, 225920, '188a019290a5c32803368b06cc8f648d'],
[20, 1, 6, 9, 8, '444511974ec94d0df0147ca987b591fb'],
[20, 1, 6, 12, 18040, '0ab81e82931828c99726f29f9b9687e4'],
[20, 2, 0, 2, 8, '701148e974bde0086464a2777bf9bf58'],
[20, 2, 3, 12, 146272, '50851f16251ff87f2529bd8549d9d192'],
[20, 3, 11, 2, 8, '106de793ff3670db14a37be97046b50c'],
[20, 3, 6, 13, 118952, '0887a4d1102c9d6ba3ed34348399364c'],
[20, 4, 8, 14, 8, '3c2c45627a6e36a3b9bd2f2423eb90e9'],
[20, 4, 15, 3, 109336, 'e1c24097964da075793a4a1e5c0b3117'],
[20, 5, 5, 15, 8, 'e0260ca01bf59f8141eba45b32c1aabf'],
[20, 5, 10, 6, 204360, '815d1a4e0e40de3bbe18af3734471e69'],
[20, 6, 10, 0, 8, 'ae35f0d7e767648c3b63bb6f8a749bc6'],
[20, 6, 8, 5, 145176, '0cab1776cf0f2618330b0b726f59b98a'],
[21, 1, 3, 6, 8, 'bf43f9573090c65350d5c08f71665d50'],
[21, 1, 9, 8, 93992, '1a864af12fcad7541bca6dce1612ed38'],
[21, 2, 11, 4, 8, '9c20ae148967ca72aa06e74bf71a97dd'],
[21, 2, 13, 2, 214368, '337c57f2a706bef66d9d1ee1a558c62d'],
[21, 3, 3, 15, 8, '48f021bf537d7476f1598c15332bd7a5'],
[21, 3, 12, 12, 251384, 'b90c9d28ac478c3c9f90c059b01867e6'],
[21, 4, 5, 9, 8, '3c4bb0f1acb29c3b5de0e93d72363235'],
[21, 4, 14, 8, 74528, '2ea2c61ee32f3c6ebbc4e2f9207cad15'],
[21, 5, 14, 8, 8, '72c739f04ff02812371641a163551ff2'],
[21, 5, 7, 13, 28352, 'cb3747147060a6bbee0280eb6485486c'],
[21, 6, 15, 5, 8, 'd47990bc2d7ed711cafe16ec845e904f'],
[21, 6, 7, 0, 99016, '4431515aba67e9ac7fd2de5bf92f9c69'],
[22, 1, 9, 1, 8, '5bff45a4c5f3238be08da73671c2a513'],
[22, 1, 11, 0, 2784, '9440e0d0bbcdc9a46196a9f6243f8334'],
[22, 2, 12, 7, 8, 'ad4cd5738522180425b2e06ddac91271'],
[22, 2, 7, 1, 51296, 'f939968d179bb8754147467cd486dda4'],
[22, 3, 3, 14, 8, 'f75387b5de4a003fcd4dad4c90dcffd4'],
[22, 3, 2, 13, 69688, '3edcb60bbcfe94cdf0b87061151f3a1b'],
[22, 4, 4, 0, 8, '530452174fce99c3af5127c542d2d5d7'],
[22, 4, 6, 12, 16536, 'd0450db72af8c82bd67d7a5fac70e255'],
[22, 5, 10, 14, 8, 'e66e4678d2771496fee24c4ec37a8d5a'],
[22, 5, 4, 9, 242200, 'e9de4e2b014d2f39b544d8700d4563a4'],
[22, 6, 11, 15, 8, 'a41e65cb031a7708f5e6944fd829367d'],
[22, 6, 13, 8, 171376, '583a8e4ae9c544f8442ba3ec041e29d2'],
[23, 1, 15, 5, 8, '430cbd360ca24bdd42e043ec67e485ee'],
[23, 1, 13, 1, 252184, '682d8c48b3ceb812b1ccaaef7ff62b13'],
[23, 2, 6, 9, 8, '1d059c12afeb4ebfd2195bd282a90ddd'],
[23, 2, 11, 12, 150544, 'c2e7745efe93c0cf9af314ed6f434d60'],
[23, 3, 4, 2, 8, 'c076fc9800c30281b9d7d08a1dc03830'],
[23, 3, 7, 7, 106616, '5eee426eab4bca725f9d2060da97236f'],
[23, 4, 9, 2, 8, '780a6ae9e71e9685251495b4a264bc43'],
[23, 4, 9, 7, 72144, 'ff49b3670e9ff13fb585d251cea44bbe'],
[23, 5, 9, 3, 8, '204c91836ec6066e9704e5feef73b16e'],
[23, 5, 10, 5, 239360, '22a5a408f73f4b456df51a72324e5224'],
[23, 6, 0, 7, 8, 'a3811fa5d75319941391f55bc14a0d1f'],
[23, 6, 10, 14, 251712, '1e78f9583e639b376f422a4344bf4801'],
[24, 1, 13, 13, 8, 'a5c2b5f4d1254d1826f7862d0e82de76'],
[24, 1, 0, 13, 175992, '4183cf25f6640060fefa712fca7cfc0d'],
[24, 2, 14, 13, 8, 'c2ce1743c4e9ee13cc8c1a368935d9d3'],
[24, 2, 8, 0, 260800, 'd560024ed4a66bc59612cf4a3d4dd913'],
[24, 3, 1, 13, 8, '3d0cec5a55ec0107c12b5e6696fd856e'],
[24, 3, 5, 15, 21296, '930a9c912fae738bd70ca55895a65504'],
[24, 4, 2, 9, 8, '5f59c5454eee03206b2d1752fe4b8446'],
[24, 4, 2, 3, 164432, '56c87f72d622b77b35276c846f7c4d79'],
[24, 5, 13, 9, 8, '4968f6d39f355c41e006b754b436d823'],
[24, 5, 4, 12, 89816, '93e82c3652f9f963eed72e0d640d9528'],
[24, 6, 10, 5, 8, '1cd9f45259767305dd7b565bcf9c0359'],
[24, 6, 3, 1, 164320, 'f2fad94fdb9e4518728cb0a5a5dbe392']
]);
queue.end();