/* Copyright (c) 2015, Scality
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var addon = require('bindings')('Release/node-eclib.node')
var ECLibUtil = require("./eclib-util.js");
var enums = require("./eclib-enum.js");
var __ = require('underscore');
/**
* This represents our interface with the erasure coding layer.
* @constructor
* @param {Object} [opts] - Contains options used by the library
* @param {Number} [opts.bc_id=0] - Backend ID
* @param {Number} [opts.k=8] - Number of data fragments
* @param {Number} [opts.m=4] - Number of parity fragments
* @param {Number} [opts.w=0] - word size (in bits)
* @param {Number} [opts.hd=0] - hamming distance (==m for Reed-Solomon)
* @param {Number} [opts.ct=0] - checksum type
*/
function ECLib(opts) {
var d_options = {
"bc_id": 0,
"k": 8,
"m": 4,
"w": 0,
"hd": 0,
"ct": 0
};
this.opt = {};
__.extend(this.opt, d_options);
if (__.size(opts) > 0) {
__.extend(this.opt, opts);
}
this.ins_id = null;
this.eclibUtil = new ECLibUtil();
this.isValidInstance = function() {
return (!__.isUndefined(this.ins_id));
};
this.resetOptions = function() {
this.opt = null;
__.extend(this.opt, d_options);
};
}
ECLib.prototype = {
/**
* This creates a new instance, using the options set at construction
* @param {Function} [callback] - callback(eclib, instanceId, error)
* @returns {Number} - Instance id
*/
init: function(callback) {
var instance_descriptor_id = -1;
var err = {};
var o = this.opt;
if (this.eclibUtil.validateInstanceCreateParams(o.bc_id, o.k,
o.m, o.w, o.hd, o.ct)) {
instance_descriptor_id = addon.EclCreate(o.bc_id, o.k, o.m, o.w,
o.hd, o.ct);
if (instance_descriptor_id <= 0) {
err.errorcode = instance_descriptor_id;
err.message = this.eclibUtil
.getErrorMessage(instance_descriptor_id);
} else {
this.ins_id = instance_descriptor_id;
}
} else {
err.errorcode = enums.ErrorCode.EINVALIDPARAMS;
err.message = this.eclibUtil.getErrorMessage(err.errorcode);
instance_descriptor_id = err.errorcode;
}
if (!callback) {
return instance_descriptor_id;
}
callback.call(err, this, instance_descriptor_id);
},
/**
* This destroys the current instance.
* @param {Function} [callback] - callback(eclib, resultCode, err)
* @returns {Number} - Result code
*/
destroy: function(callback) {
var resultcode = enums.ErrorCode.EBACKENDNOTAVAIL;
var err = {};
if (this.isValidInstance()) {
resultcode = addon.EclDestroy(this.ins_id);
if (resultcode !== 0) {
err.errorcode = resultcode;
err.message = this.eclibUtil.getErrorMessage(resultcode);
}
} else {
err.errorcode = resultcode;
err.message = this.eclibUtil.getErrorMessage(resultcode);
}
if (!callback) {
return resultcode;
}
callback.call(this, resultcode, err);
},
/**
* This encodes the data.
* @param {Buffer} data - The data to be encoded
* @param {Function} callback - callback(status, encodedDataArray,
* encodedParityArray, encodedFragmentLen)
*/
encode: function(data, callback) {
var o = this.opt;
addon.EclEncode(this.ins_id, o.k, o.m, data, data.length, callback);
},
/**
* This encodes the data array.
* @param {Buffer[]} bufArray - Buffers to be encoded
* @param {Function} callback - callback(status, encodedDataArray,
* encodedParityArray, encodedFragmentLen)
*/
encodev: function(bufArray, callback) {
var o = this.opt;
var size = bufArray.reduce(function getSize(value, buffer) {
return value + buffer.length;
}, 0);
addon.EclEncodeV(this.ins_id, o.k, o.m, bufArray.length, bufArray,
size, callback);
},
/**
* This decodes the fragments array.
* @param {Buffer[]} fragmentArray - The fragment array to decode
* @param {Boolean} metadataCheck - Checking of the metadata
* @param {Function} callback - (err, decodedData)
*/
decode: function(fragmentArray, metadataCheck, callback) {
addon.EclDecode(this.ins_id, fragmentArray, fragmentArray.length,
fragmentArray[0].length, metadataCheck, callback);
},
/**
* Reconstruct a missing fragment.
* @param {Buffer[]} availFragments - Fragments available for reconstruction
* @param {Number} fragmentId - Missing fragment id
* @param {Function} callback - (err, reconstructedFragment)
*/
reconstructFragment: function(availFragments, fragmentId, callback) {
if (!availFragments.length) {
callback(new Error('invalid number of available fragments (must be > 0)'), null);
return ;
}
addon.EclReconstructFragment(
this.ins_id,
availFragments,
availFragments.length,
availFragments[0].length,
fragmentId,
callback
);
},
/**
* Reconstruct missing fragments.
* @param {Buffer[]} availFragments - Fragments available for reconstruction
* @param {Number} fragmentIds - Missing fragment ids
* @param {Function} callback - (err, allFragments)
*/
reconstruct: function(availFragments, fragmentIds, callback) {
// If we sort the missing indexes, than we can safely insert each
// recoevered fragment when we have it. Example: we have 10 fragments,
// but the 3rd, 6th and 8th are missing. If the `fragmentIds`
// is unsorted, like [8,6,3], then we have the following
// `availFragments`: [1,2,4,5,7,9,10]. If we first recover the 8th
// fragment, we don't where to insert it. But if we first recover the
// 3rd fragment, we know we can insert it at index 3, so that we then
// have the `availFragments` set to [1,2,3,4,5,7,9,10] when we recover
// the 6th fragment.
var done = 0;
fragmentIds.sort();
// Recover all missing fragments one by one.
fragmentIds.forEach(function reconstructEach(id) {
this.reconstructFragment(availFragments, id,
function recon(err, fragment) {
if (err) {
callback(err);
}
availFragments.splice(id, 0, fragment);
if (++done === fragmentIds.length) {
callback(null, availFragments);
return ;
}
});
}.bind(this));
},
getFragmentMetadata: function(fragment, fragment_metadata, callback) {
// TODO: what is this function supposed to do ?
},
setOptions: function(opts){
__.extend(this.opt,opts);
}
}
module.exports = ECLib;