Beta 7 with some repo names changed.
This commit is contained in:
1 parent
0f7b40cca7
commit
963c19493a
3408 files changed
+462217
-15516
No files matched your search
+104
@@ -0,0 +1,104 @@
|
||||
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
|
||||
|
||||
var Bytes = require("./bytes");
|
||||
var Nat = require("./nat");
|
||||
var elliptic = require("elliptic");
|
||||
var rlp = require("./rlp");
|
||||
var secp256k1 = new elliptic.ec("secp256k1"); // eslint-disable-line
|
||||
|
||||
var _require = require("./hash"),
|
||||
keccak256 = _require.keccak256,
|
||||
keccak256s = _require.keccak256s;
|
||||
|
||||
var create = function create(entropy) {
|
||||
var innerHex = keccak256(Bytes.concat(Bytes.random(32), entropy || Bytes.random(32)));
|
||||
var middleHex = Bytes.concat(Bytes.concat(Bytes.random(32), innerHex), Bytes.random(32));
|
||||
var outerHex = keccak256(middleHex);
|
||||
return fromPrivate(outerHex);
|
||||
};
|
||||
|
||||
var toChecksum = function toChecksum(address) {
|
||||
var addressHash = keccak256s(address.slice(2));
|
||||
var checksumAddress = "0x";
|
||||
for (var i = 0; i < 40; i++) {
|
||||
checksumAddress += parseInt(addressHash[i + 2], 16) > 7 ? address[i + 2].toUpperCase() : address[i + 2];
|
||||
}return checksumAddress;
|
||||
};
|
||||
|
||||
var fromPrivate = function fromPrivate(privateKey) {
|
||||
var buffer = new Buffer(privateKey.slice(2), "hex");
|
||||
var ecKey = secp256k1.keyFromPrivate(buffer);
|
||||
var publicKey = "0x" + ecKey.getPublic(false, 'hex').slice(2);
|
||||
var publicHash = keccak256(publicKey);
|
||||
var address = toChecksum("0x" + publicHash.slice(-40));
|
||||
return {
|
||||
address: address,
|
||||
privateKey: privateKey
|
||||
};
|
||||
};
|
||||
|
||||
var encodeSignature = function encodeSignature(_ref) {
|
||||
var _ref2 = _slicedToArray(_ref, 3),
|
||||
v = _ref2[0],
|
||||
r = _ref2[1],
|
||||
s = _ref2[2];
|
||||
|
||||
return Bytes.flatten([r, s, v]);
|
||||
};
|
||||
|
||||
var decodeSignature = function decodeSignature(hex) {
|
||||
return [Bytes.slice(64, 65, hex), Bytes.slice(0, 32, hex), Bytes.slice(32, 64, hex)];
|
||||
};
|
||||
|
||||
var makeSign = function makeSign(addToV) {
|
||||
return function (hash, privateKey) {
|
||||
var signature = secp256k1.keyFromPrivate(new Buffer(privateKey.slice(2), "hex")).sign(new Buffer(hash.slice(2), "hex"), { canonical: true });
|
||||
return encodeSignature([Bytes.pad(1, Bytes.fromNumber(addToV + signature.recoveryParam)), Bytes.pad(32, Bytes.fromNat("0x" + signature.r.toString(16))), Bytes.pad(32, Bytes.fromNat("0x" + signature.s.toString(16)))]);
|
||||
};
|
||||
};
|
||||
|
||||
var sign = makeSign(27); // v=27|28 instead of 0|1...
|
||||
|
||||
var recover = function recover(hash, signature) {
|
||||
var vals = decodeSignature(signature);
|
||||
var vrs = { v: Bytes.toNumber(vals[0]), r: vals[1].slice(2), s: vals[2].slice(2) };
|
||||
var ecPublicKey = secp256k1.recoverPubKey(new Buffer(hash.slice(2), "hex"), vrs, vrs.v < 2 ? vrs.v : 1 - vrs.v % 2); // because odd vals mean v=0... sadly that means v=0 means v=1... I hate that
|
||||
var publicKey = "0x" + ecPublicKey.encode("hex", false).slice(2);
|
||||
var publicHash = keccak256(publicKey);
|
||||
var address = toChecksum("0x" + publicHash.slice(-40));
|
||||
return address;
|
||||
};
|
||||
|
||||
var transactionSigningData = function transactionSigningData(tx) {
|
||||
return rlp.encode([Bytes.fromNat(tx.nonce), Bytes.fromNat(tx.gasPrice), Bytes.fromNat(tx.gas), tx.to.toLowerCase(), Bytes.fromNat(tx.value), tx.data, Bytes.fromNat(tx.chainId || "0x1"), "0x", "0x"]);
|
||||
};
|
||||
|
||||
var signTransaction = function signTransaction(tx, privateKey) {
|
||||
var signingData = transactionSigningData(tx);
|
||||
var signature = makeSign(Nat.toNumber(tx.chainId || "0x1") * 2 + 35)(keccak256(signingData), privateKey);
|
||||
var rawTransaction = rlp.decode(signingData).slice(0, 6).concat(decodeSignature(signature));
|
||||
return rlp.encode(rawTransaction);
|
||||
};
|
||||
|
||||
var recoverTransaction = function recoverTransaction(rawTransaction) {
|
||||
var values = rlp.decode(rawTransaction);
|
||||
var signature = encodeSignature(values.slice(6, 9));
|
||||
var recovery = Bytes.toNumber(values[6]);
|
||||
var extraData = recovery < 35 ? [] : [Bytes.fromNumber(recovery - 35 >> 1), "0x", "0x"];
|
||||
var signingData = values.slice(0, 6).concat(extraData);
|
||||
var signingDataHex = rlp.encode(signingData);
|
||||
return recover(keccak256(signingDataHex), signature);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
create: create,
|
||||
toChecksum: toChecksum,
|
||||
fromPrivate: fromPrivate,
|
||||
sign: sign,
|
||||
recover: recover,
|
||||
signTransaction: signTransaction,
|
||||
recoverTransaction: recoverTransaction,
|
||||
transactionSigningData: transactionSigningData,
|
||||
encodeSignature: encodeSignature,
|
||||
decodeSignature: decodeSignature
|
||||
};
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
|
||||
|
||||
var Api = function Api(provider) {
|
||||
var Nat = require("./nat");
|
||||
var Map = require("./map");
|
||||
var Bytes = require("./bytes");
|
||||
var keccak256s = require("./hash").keccak256s;
|
||||
var send = function send(method) {
|
||||
return function () {
|
||||
for (var _len = arguments.length, params = Array(_len), _key = 0; _key < _len; _key++) {
|
||||
params[_key] = arguments[_key];
|
||||
}
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
return provider.send(method, params, function (err, result) {
|
||||
return err ? reject(err) : resolve(result);
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
// TODO check inputs
|
||||
// TODO move to proper file
|
||||
var encodeABI = function encodeABI(type, value) {
|
||||
if (type === "bytes") {
|
||||
var length = Bytes.length(value);
|
||||
var nextMul32 = (((length - 1) / 32 | 0) + 1) * 32;
|
||||
var lengthEncoded = encodeABI("uint256", Nat.fromNumber(length)).data;
|
||||
var bytesEncoded = Bytes.padRight(nextMul32, value);
|
||||
return { data: Bytes.concat(lengthEncoded, bytesEncoded), dynamic: true };
|
||||
} else if (type === "uint256" || type === "bytes32") {
|
||||
return { data: Bytes.pad(32, value), dynamic: false };
|
||||
} else {
|
||||
throw "Eth-lib can't encode ABI type " + type + " yet.";
|
||||
}
|
||||
};
|
||||
|
||||
var sendTransaction = send("eth_sendTransaction");
|
||||
var sendRawTransaction = send("eth_sendRawTransaction");
|
||||
var getTransactionReceipt = send("eth_getTransactionReceipt");
|
||||
var compileSolidity = send("eth_compileSolidity");
|
||||
var call = send("eth_call");
|
||||
var getBalance = send("eth_getBalance");
|
||||
var accounts = send("eth_accounts");
|
||||
|
||||
var removeEmptyTo = function removeEmptyTo(tx) {
|
||||
return tx.to === "" || tx.to === "0x" ? Map.remove("to")(tx) : tx;
|
||||
};
|
||||
|
||||
var waitTransactionReceipt = getTransactionReceipt; // TODO: implement correctly
|
||||
|
||||
var addTransactionDefaults = function addTransactionDefaults(tx) {
|
||||
return (
|
||||
// Get basic defaults
|
||||
Promise.all([tx.chainId || send("net_version")(), tx.gasPrice || send("eth_gasPrice")(), tx.nonce || send("eth_getTransactionCount")(tx.from, "latest"), tx.value || "0x0", tx.data || "0x"])
|
||||
// Add them to tx
|
||||
.then(function (_ref) {
|
||||
var _ref2 = _slicedToArray(_ref, 5),
|
||||
chainId = _ref2[0],
|
||||
gasPrice = _ref2[1],
|
||||
nonce = _ref2[2],
|
||||
value = _ref2[3],
|
||||
data = _ref2[4];
|
||||
|
||||
return Map.merge(tx)({ chainId: Nat.fromNumber(chainId), gasPrice: gasPrice, nonce: nonce, value: value, data: data });
|
||||
}).then(function (tx) {
|
||||
// Add gas default by estimating
|
||||
if (!tx.gas) {
|
||||
var estimateTx = {};
|
||||
estimateTx.from = tx.from;
|
||||
if (tx.to !== "" && tx.to !== "0x") estimateTx.to = tx.to;
|
||||
estimateTx.value = tx.value;
|
||||
estimateTx.nonce = tx.nonce;
|
||||
estimateTx.data = tx.data;
|
||||
return send("eth_estimateGas")(estimateTx).then(function (usedGas) {
|
||||
return Map.merge(tx)({ gas: Nat.div(Nat.mul(usedGas, "0x6"), "0x5") });
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve(tx);
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
var sendTransactionWithDefaults = function sendTransactionWithDefaults(tx) {
|
||||
return addTransactionDefaults(tx).then(function (tx) {
|
||||
return sendTransaction(removeEmptyTo(tx));
|
||||
});
|
||||
};
|
||||
|
||||
var callWithDefaults = function callWithDefaults(tx, block) {
|
||||
return addTransactionDefaults(tx).then(function (tx) {
|
||||
return call(removeEmptyTo(tx), block || "latest");
|
||||
});
|
||||
};
|
||||
|
||||
var callMethodData = function callMethodData(method) {
|
||||
return function () {
|
||||
for (var _len2 = arguments.length, params = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
|
||||
params[_key2] = arguments[_key2];
|
||||
}
|
||||
|
||||
var methodSig = method.name + "(" + method.inputs.map(function (i) {
|
||||
return i.type;
|
||||
}).join(",") + ")";
|
||||
var methodHash = keccak256s(methodSig).slice(0, 10);
|
||||
var encodedParams = params.map(function (param, i) {
|
||||
return encodeABI(method.inputs[i].type, param);
|
||||
});
|
||||
var headBlock = "0x";
|
||||
var dataBlock = "0x";
|
||||
for (var i = 0; i < encodedParams.length; ++i) {
|
||||
if (encodedParams[i].dynamic) {
|
||||
var dataLoc = encodedParams.length * 32 + Bytes.length(dataBlock);
|
||||
headBlock = Bytes.concat(headBlock, Bytes.pad(32, Nat.fromNumber(dataLoc)));
|
||||
dataBlock = Bytes.concat(dataBlock, encodedParams[i].data);
|
||||
} else {
|
||||
headBlock = Bytes.concat(headBlock, encodedParams[i].data);
|
||||
}
|
||||
}
|
||||
return Bytes.flatten([methodHash, headBlock, dataBlock]);
|
||||
};
|
||||
};
|
||||
|
||||
// Address, Address, ContractInterface -> Contract
|
||||
var contract = function contract(from, address, contractInterface) {
|
||||
var contract = {};
|
||||
contract._address = address;
|
||||
contract._from = from;
|
||||
contract.broadcast = {};
|
||||
contractInterface.forEach(function (method) {
|
||||
if (method && method.name) {
|
||||
var _call = function _call(type, value) {
|
||||
return function () {
|
||||
var transaction = {
|
||||
from: from,
|
||||
to: address,
|
||||
value: value,
|
||||
data: callMethodData(method).apply(undefined, arguments)
|
||||
};
|
||||
return type === "data" ? Promise.resolve(transaction) : method.constant ? callWithDefaults(transaction) : sendTransactionWithDefaults(transaction).then(type === "receipt" ? waitTransactionReceipt : function (x) {
|
||||
return x;
|
||||
});
|
||||
};
|
||||
};
|
||||
contract[method.name] = _call("receipt", "0x0");
|
||||
if (!method.constant) {
|
||||
contract[method.name + "_data"] = _call("data", "0x0");
|
||||
contract[method.name + "_pay"] = function (value) {
|
||||
return function () {
|
||||
return _call("receipt", value).apply(undefined, arguments);
|
||||
};
|
||||
};
|
||||
contract[method.name + "_pay_txHash"] = function (value) {
|
||||
return function () {
|
||||
return _call("txHash", value).apply(undefined, arguments);
|
||||
};
|
||||
};
|
||||
contract[method.name + "_txHash"] = _call("txHash", "0x0");
|
||||
}
|
||||
}
|
||||
});
|
||||
return contract;
|
||||
};
|
||||
|
||||
// Address, Bytecode -> TxHash
|
||||
var deployBytecode_txHash = function deployBytecode_txHash(from, code) {
|
||||
return sendTransactionWithDefaults({ from: from, data: code, to: "" });
|
||||
};
|
||||
|
||||
// Address, Bytecode -> Receipt
|
||||
var deployBytecode = function deployBytecode(from, code) {
|
||||
return deployBytecode_txHash(from, code).then(waitTransactionReceipt);
|
||||
};
|
||||
|
||||
// Address, Bytecode, ContractInterface
|
||||
var deployBytecodeContract = function deployBytecodeContract(from, code, contractInterface) {
|
||||
return deployBytecode(from, code).then(function (receipt) {
|
||||
return contract(from, receipt.contractAddress, contractInterface);
|
||||
});
|
||||
};
|
||||
|
||||
// Address, String, Address -> Contract
|
||||
var solidityContract = function solidityContract(from, source, at) {
|
||||
return compileSolidity(source).then(function (_ref3) {
|
||||
var abiDefinition = _ref3.info.abiDefinition;
|
||||
return contract(from, at, abiDefinition);
|
||||
});
|
||||
};
|
||||
|
||||
// Address, String -> TxHash
|
||||
var deploySolidity_txHash = function deploySolidity_txHash(from, source) {
|
||||
return compileSolidity(source).then(function (_ref4) {
|
||||
var code = _ref4.code;
|
||||
return deployBytecode_txHash(from, code);
|
||||
});
|
||||
};
|
||||
|
||||
// Address, String -> Receipt
|
||||
var deploySolidity = function deploySolidity(from, source) {
|
||||
return deploySolidity_txHash(from, source).then(waitTransactionReceipt);
|
||||
};
|
||||
|
||||
// Address, String -> Contract
|
||||
var deploySolidityContract = function deploySolidityContract(from, source) {
|
||||
return compileSolidity(source).then(function (_ref5) {
|
||||
var code = _ref5.code,
|
||||
abiDefinition = _ref5.info.abiDefinition;
|
||||
return deployBytecodeContract(from, code, abiDefinition);
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
send: send,
|
||||
|
||||
sendTransaction: sendTransaction,
|
||||
sendRawTransaction: sendRawTransaction,
|
||||
getTransactionReceipt: getTransactionReceipt,
|
||||
call: call,
|
||||
getBalance: getBalance,
|
||||
accounts: accounts,
|
||||
|
||||
waitTransactionReceipt: waitTransactionReceipt,
|
||||
addTransactionDefaults: addTransactionDefaults,
|
||||
sendTransactionWithDefaults: sendTransactionWithDefaults,
|
||||
callWithDefaults: callWithDefaults,
|
||||
callMethodData: callMethodData,
|
||||
|
||||
contract: contract,
|
||||
deployBytecode_txHash: deployBytecode_txHash,
|
||||
deployBytecode: deployBytecode,
|
||||
deployBytecodeContract: deployBytecodeContract,
|
||||
|
||||
compileSolidity: compileSolidity,
|
||||
solidityContract: solidityContract,
|
||||
deploySolidity_txHash: deploySolidity_txHash,
|
||||
deploySolidity: deploySolidity,
|
||||
deploySolidityContract: deploySolidityContract
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = Api;
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
var generate = function generate(num, fn) {
|
||||
var a = [];
|
||||
for (var i = 0; i < num; ++i) {
|
||||
a.push(fn(i));
|
||||
}return a;
|
||||
};
|
||||
|
||||
var replicate = function replicate(num, val) {
|
||||
return generate(num, function () {
|
||||
return val;
|
||||
});
|
||||
};
|
||||
|
||||
var concat = function concat(a, b) {
|
||||
return a.concat(b);
|
||||
};
|
||||
|
||||
var flatten = function flatten(a) {
|
||||
var r = [];
|
||||
for (var j = 0, J = a.length; j < J; ++j) {
|
||||
for (var i = 0, I = a[j].length; i < I; ++i) {
|
||||
r.push(a[j][i]);
|
||||
}
|
||||
}return r;
|
||||
};
|
||||
|
||||
var chunksOf = function chunksOf(n, a) {
|
||||
var b = [];
|
||||
for (var i = 0, l = a.length; i < l; i += n) {
|
||||
b.push(a.slice(i, i + n));
|
||||
}return b;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
generate: generate,
|
||||
replicate: replicate,
|
||||
concat: concat,
|
||||
flatten: flatten,
|
||||
chunksOf: chunksOf
|
||||
};
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
var A = require("./array.js");
|
||||
|
||||
var at = function at(bytes, index) {
|
||||
return parseInt(bytes.slice(index * 2 + 2, index * 2 + 4), 16);
|
||||
};
|
||||
|
||||
var random = function random(bytes) {
|
||||
var rnd = void 0;
|
||||
if (typeof window !== "undefined" && window.crypto && window.crypto.getRandomValues) rnd = window.crypto.getRandomValues(new Uint8Array(bytes));else if (typeof require !== "undefined") rnd = require("c" + "rypto").randomBytes(bytes);else throw "Safe random numbers not available.";
|
||||
var hex = "0x";
|
||||
for (var i = 0; i < bytes; ++i) {
|
||||
hex += ("00" + rnd[i].toString(16)).slice(-2);
|
||||
}return hex;
|
||||
};
|
||||
|
||||
var length = function length(a) {
|
||||
return (a.length - 2) / 2;
|
||||
};
|
||||
|
||||
var flatten = function flatten(a) {
|
||||
return "0x" + a.reduce(function (r, s) {
|
||||
return r + s.slice(2);
|
||||
}, "");
|
||||
};
|
||||
|
||||
var slice = function slice(i, j, bs) {
|
||||
return "0x" + bs.slice(i * 2 + 2, j * 2 + 2);
|
||||
};
|
||||
|
||||
var reverse = function reverse(hex) {
|
||||
var rev = "0x";
|
||||
for (var i = 0, l = length(hex); i < l; ++i) {
|
||||
rev += hex.slice((l - i) * 2, (l - i + 1) * 2);
|
||||
}
|
||||
return rev;
|
||||
};
|
||||
|
||||
var pad = function pad(l, hex) {
|
||||
return hex.length === l * 2 + 2 ? hex : pad(l, "0x" + "0" + hex.slice(2));
|
||||
};
|
||||
|
||||
var padRight = function padRight(l, hex) {
|
||||
return hex.length === l * 2 + 2 ? hex : padRight(l, hex + "0");
|
||||
};
|
||||
|
||||
var toArray = function toArray(hex) {
|
||||
var arr = [];
|
||||
for (var i = 2, l = hex.length; i < l; i += 2) {
|
||||
arr.push(parseInt(hex.slice(i, i + 2), 16));
|
||||
}return arr;
|
||||
};
|
||||
|
||||
var fromArray = function fromArray(arr) {
|
||||
var hex = "0x";
|
||||
for (var i = 0, l = arr.length; i < l; ++i) {
|
||||
var b = arr[i];
|
||||
hex += (b < 16 ? "0" : "") + b.toString(16);
|
||||
}
|
||||
return hex;
|
||||
};
|
||||
|
||||
var toUint8Array = function toUint8Array(hex) {
|
||||
return new Uint8Array(toArray(hex));
|
||||
};
|
||||
|
||||
var fromUint8Array = function fromUint8Array(arr) {
|
||||
return fromArray([].slice.call(arr, 0));
|
||||
};
|
||||
|
||||
var fromNumber = function fromNumber(num) {
|
||||
var hex = num.toString(16);
|
||||
return hex.length % 2 === 0 ? "0x" + hex : "0x0" + hex;
|
||||
};
|
||||
|
||||
var toNumber = function toNumber(hex) {
|
||||
return parseInt(hex.slice(2), 16);
|
||||
};
|
||||
|
||||
var concat = function concat(a, b) {
|
||||
return a.concat(b.slice(2));
|
||||
};
|
||||
|
||||
var fromNat = function fromNat(bn) {
|
||||
return bn === "0x0" ? "0x" : bn.length % 2 === 0 ? bn : "0x0" + bn.slice(2);
|
||||
};
|
||||
|
||||
var toNat = function toNat(bn) {
|
||||
return bn[2] === "0" ? "0x" + bn.slice(3) : bn;
|
||||
};
|
||||
|
||||
var fromAscii = function fromAscii(ascii) {
|
||||
var hex = "0x";
|
||||
for (var i = 0; i < ascii.length; ++i) {
|
||||
hex += ("00" + ascii.charCodeAt(i).toString(16)).slice(-2);
|
||||
}return hex;
|
||||
};
|
||||
|
||||
var toAscii = function toAscii(hex) {
|
||||
var ascii = "";
|
||||
for (var i = 2; i < hex.length; i += 2) {
|
||||
ascii += String.fromCharCode(parseInt(hex.slice(i, i + 2), 16));
|
||||
}return ascii;
|
||||
};
|
||||
|
||||
// From https://gist.github.com/pascaldekloe/62546103a1576803dade9269ccf76330
|
||||
var fromString = function fromString(s) {
|
||||
var makeByte = function makeByte(uint8) {
|
||||
var b = uint8.toString(16);
|
||||
return b.length < 2 ? "0" + b : b;
|
||||
};
|
||||
var bytes = "0x";
|
||||
for (var ci = 0; ci != s.length; ci++) {
|
||||
var c = s.charCodeAt(ci);
|
||||
if (c < 128) {
|
||||
bytes += makeByte(c);
|
||||
continue;
|
||||
}
|
||||
if (c < 2048) {
|
||||
bytes += makeByte(c >> 6 | 192);
|
||||
} else {
|
||||
if (c > 0xd7ff && c < 0xdc00) {
|
||||
if (++ci == s.length) return null;
|
||||
var c2 = s.charCodeAt(ci);
|
||||
if (c2 < 0xdc00 || c2 > 0xdfff) return null;
|
||||
c = 0x10000 + ((c & 0x03ff) << 10) + (c2 & 0x03ff);
|
||||
bytes += makeByte(c >> 18 | 240);
|
||||
bytes += makeByte(c >> 12 & 63 | 128);
|
||||
} else {
|
||||
// c <= 0xffff
|
||||
bytes += makeByte(c >> 12 | 224);
|
||||
}
|
||||
bytes += makeByte(c >> 6 & 63 | 128);
|
||||
}
|
||||
bytes += makeByte(c & 63 | 128);
|
||||
}
|
||||
return bytes;
|
||||
};
|
||||
|
||||
var toString = function toString(bytes) {
|
||||
var s = '';
|
||||
var i = 0;
|
||||
var l = length(bytes);
|
||||
while (i < l) {
|
||||
var c = at(bytes, i++);
|
||||
if (c > 127) {
|
||||
if (c > 191 && c < 224) {
|
||||
if (i >= l) return null;
|
||||
c = (c & 31) << 6 | at(bytes, i) & 63;
|
||||
} else if (c > 223 && c < 240) {
|
||||
if (i + 1 >= l) return null;
|
||||
c = (c & 15) << 12 | (at(bytes, i) & 63) << 6 | at(bytes, ++i) & 63;
|
||||
} else if (c > 239 && c < 248) {
|
||||
if (i + 2 >= l) return null;
|
||||
c = (c & 7) << 18 | (at(bytes, i) & 63) << 12 | (at(bytes, ++i) & 63) << 6 | at(bytes, ++i) & 63;
|
||||
} else return null;
|
||||
++i;
|
||||
}
|
||||
if (c <= 0xffff) s += String.fromCharCode(c);else if (c <= 0x10ffff) {
|
||||
c -= 0x10000;
|
||||
s += String.fromCharCode(c >> 10 | 0xd800);
|
||||
s += String.fromCharCode(c & 0x3FF | 0xdc00);
|
||||
} else return null;
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
random: random,
|
||||
length: length,
|
||||
concat: concat,
|
||||
flatten: flatten,
|
||||
slice: slice,
|
||||
reverse: reverse,
|
||||
pad: pad,
|
||||
padRight: padRight,
|
||||
fromAscii: fromAscii,
|
||||
toAscii: toAscii,
|
||||
fromString: fromString,
|
||||
toString: toString,
|
||||
fromNumber: fromNumber,
|
||||
toNumber: toNumber,
|
||||
fromNat: fromNat,
|
||||
toNat: toNat,
|
||||
fromArray: fromArray,
|
||||
toArray: toArray,
|
||||
fromUint8Array: fromUint8Array,
|
||||
toUint8Array: toUint8Array
|
||||
};
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
var Bytes = require("./bytes");
|
||||
|
||||
var inis = "pbtdkgxjfvlrmnsz".split("");
|
||||
|
||||
var mids = "auie".split("");
|
||||
|
||||
var back = [inis, mids].map(function (chars) {
|
||||
var map = {};
|
||||
for (var i = 0; i < chars.length; ++i) {
|
||||
map[chars[i]] = i;
|
||||
}return map;
|
||||
});
|
||||
|
||||
var syllableFromB64 = function syllableFromB64(b64) {
|
||||
var ini = b64 >>> 2 & 15;
|
||||
var mid = b64 >>> 0 & 3;
|
||||
return inis[ini] + mids[mid];
|
||||
};
|
||||
|
||||
var syllableToB64 = function syllableToB64(syllable) {
|
||||
var ini = back[0][syllable[0]];
|
||||
var mid = back[1][syllable[1]];
|
||||
return ini * 4 + mid;
|
||||
};
|
||||
|
||||
var b64sFromBytes = function b64sFromBytes(bytes) {
|
||||
//BoooooBoooooBoooooBooooo
|
||||
//BoooooooBoooooooBooooooo
|
||||
var b64s = [],
|
||||
b64 = void 0;
|
||||
for (var i = 0, l = Math.ceil(bytes.length * 8 / 6); i < l; ++i) {
|
||||
var j = i / 8 * 6 | 0;
|
||||
b64s.push(i % 4 === 0 ? bytes[j + 0] / 4 | 0 : i % 4 === 1 ? bytes[j + 0] % 4 * 16 + (bytes[j + 1] / 16 | 0) : i % 4 === 2 ? bytes[j + 0] % 16 * 4 + (bytes[j + 1] / 64 | 0) : bytes[j + 0] % 64 * 1);
|
||||
}
|
||||
return b64s;
|
||||
};
|
||||
|
||||
var b64sToBytes = function b64sToBytes(b64s) {
|
||||
var bytes = [];
|
||||
for (var i = 0, l = Math.floor(b64s.length * 6 / 8); i < l; ++i) {
|
||||
var j = i / 6 * 8 | 0;
|
||||
bytes.push(i % 3 === 0 ? b64s[j + 0] % 64 * 4 + (b64s[j + 1] / 16 | 0) : i % 3 === 1 ? b64s[j + 0] % 16 * 16 + (b64s[j + 1] / 4 | 0) : b64s[j + 0] % 4 * 64 + (b64s[j + 1] / 1 | 0));
|
||||
}
|
||||
return bytes;
|
||||
};
|
||||
|
||||
var fromBytes = function fromBytes(bytes) {
|
||||
return b64sFromBytes(Bytes.toArray(bytes)).map(syllableFromB64).join("");
|
||||
};
|
||||
|
||||
var toBytes = function toBytes(syllables) {
|
||||
return Bytes.fromArray(b64sToBytes(syllables.match(/\w\w/g).map(syllableToB64)));
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
fromBytes: fromBytes,
|
||||
toBytes: toBytes
|
||||
};
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
var times = function times(n, f, x) {
|
||||
for (var i = 0; i < n; ++i) {
|
||||
x = f(x);
|
||||
}
|
||||
return x;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
times: times
|
||||
};
|
||||
+339
@@ -0,0 +1,339 @@
|
||||
// This was ported from https://github.com/emn178/js-sha3, with some minor
|
||||
// modifications and pruning. It is licensed under MIT:
|
||||
//
|
||||
// Copyright 2015-2016 Chen, Yi-Cyuan
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
var HEX_CHARS = '0123456789abcdef'.split('');
|
||||
var KECCAK_PADDING = [1, 256, 65536, 16777216];
|
||||
var SHIFT = [0, 8, 16, 24];
|
||||
var RC = [1, 0, 32898, 0, 32906, 2147483648, 2147516416, 2147483648, 32907, 0, 2147483649, 0, 2147516545, 2147483648, 32777, 2147483648, 138, 0, 136, 0, 2147516425, 0, 2147483658, 0, 2147516555, 0, 139, 2147483648, 32905, 2147483648, 32771, 2147483648, 32770, 2147483648, 128, 2147483648, 32778, 0, 2147483658, 2147483648, 2147516545, 2147483648, 32896, 2147483648, 2147483649, 0, 2147516424, 2147483648];
|
||||
|
||||
var Keccak = function Keccak(bits) {
|
||||
return {
|
||||
blocks: [],
|
||||
reset: true,
|
||||
block: 0,
|
||||
start: 0,
|
||||
blockCount: 1600 - (bits << 1) >> 5,
|
||||
outputBlocks: bits >> 5,
|
||||
s: function (s) {
|
||||
return [].concat(s, s, s, s, s);
|
||||
}([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
||||
};
|
||||
};
|
||||
|
||||
var update = function update(state, message) {
|
||||
var length = message.length,
|
||||
blocks = state.blocks,
|
||||
byteCount = state.blockCount << 2,
|
||||
blockCount = state.blockCount,
|
||||
outputBlocks = state.outputBlocks,
|
||||
s = state.s,
|
||||
index = 0,
|
||||
i,
|
||||
code;
|
||||
|
||||
// update
|
||||
while (index < length) {
|
||||
if (state.reset) {
|
||||
state.reset = false;
|
||||
blocks[0] = state.block;
|
||||
for (i = 1; i < blockCount + 1; ++i) {
|
||||
blocks[i] = 0;
|
||||
}
|
||||
}
|
||||
if (typeof message !== "string") {
|
||||
for (i = state.start; index < length && i < byteCount; ++index) {
|
||||
blocks[i >> 2] |= message[index] << SHIFT[i++ & 3];
|
||||
}
|
||||
} else {
|
||||
for (i = state.start; index < length && i < byteCount; ++index) {
|
||||
code = message.charCodeAt(index);
|
||||
if (code < 0x80) {
|
||||
blocks[i >> 2] |= code << SHIFT[i++ & 3];
|
||||
} else if (code < 0x800) {
|
||||
blocks[i >> 2] |= (0xc0 | code >> 6) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | code & 0x3f) << SHIFT[i++ & 3];
|
||||
} else if (code < 0xd800 || code >= 0xe000) {
|
||||
blocks[i >> 2] |= (0xe0 | code >> 12) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | code >> 6 & 0x3f) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | code & 0x3f) << SHIFT[i++ & 3];
|
||||
} else {
|
||||
code = 0x10000 + ((code & 0x3ff) << 10 | message.charCodeAt(++index) & 0x3ff);
|
||||
blocks[i >> 2] |= (0xf0 | code >> 18) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | code >> 12 & 0x3f) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | code >> 6 & 0x3f) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | code & 0x3f) << SHIFT[i++ & 3];
|
||||
}
|
||||
}
|
||||
}
|
||||
state.lastByteIndex = i;
|
||||
if (i >= byteCount) {
|
||||
state.start = i - byteCount;
|
||||
state.block = blocks[blockCount];
|
||||
for (i = 0; i < blockCount; ++i) {
|
||||
s[i] ^= blocks[i];
|
||||
}
|
||||
f(s);
|
||||
state.reset = true;
|
||||
} else {
|
||||
state.start = i;
|
||||
}
|
||||
}
|
||||
|
||||
// finalize
|
||||
i = state.lastByteIndex;
|
||||
blocks[i >> 2] |= KECCAK_PADDING[i & 3];
|
||||
if (state.lastByteIndex === byteCount) {
|
||||
blocks[0] = blocks[blockCount];
|
||||
for (i = 1; i < blockCount + 1; ++i) {
|
||||
blocks[i] = 0;
|
||||
}
|
||||
}
|
||||
blocks[blockCount - 1] |= 0x80000000;
|
||||
for (i = 0; i < blockCount; ++i) {
|
||||
s[i] ^= blocks[i];
|
||||
}
|
||||
f(s);
|
||||
|
||||
// toString
|
||||
var hex = '',
|
||||
i = 0,
|
||||
j = 0,
|
||||
block;
|
||||
while (j < outputBlocks) {
|
||||
for (i = 0; i < blockCount && j < outputBlocks; ++i, ++j) {
|
||||
block = s[i];
|
||||
hex += HEX_CHARS[block >> 4 & 0x0F] + HEX_CHARS[block & 0x0F] + HEX_CHARS[block >> 12 & 0x0F] + HEX_CHARS[block >> 8 & 0x0F] + HEX_CHARS[block >> 20 & 0x0F] + HEX_CHARS[block >> 16 & 0x0F] + HEX_CHARS[block >> 28 & 0x0F] + HEX_CHARS[block >> 24 & 0x0F];
|
||||
}
|
||||
if (j % blockCount === 0) {
|
||||
f(s);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
return "0x" + hex;
|
||||
};
|
||||
|
||||
var f = function f(s) {
|
||||
var h, l, n, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30, b31, b32, b33, b34, b35, b36, b37, b38, b39, b40, b41, b42, b43, b44, b45, b46, b47, b48, b49;
|
||||
|
||||
for (n = 0; n < 48; n += 2) {
|
||||
c0 = s[0] ^ s[10] ^ s[20] ^ s[30] ^ s[40];
|
||||
c1 = s[1] ^ s[11] ^ s[21] ^ s[31] ^ s[41];
|
||||
c2 = s[2] ^ s[12] ^ s[22] ^ s[32] ^ s[42];
|
||||
c3 = s[3] ^ s[13] ^ s[23] ^ s[33] ^ s[43];
|
||||
c4 = s[4] ^ s[14] ^ s[24] ^ s[34] ^ s[44];
|
||||
c5 = s[5] ^ s[15] ^ s[25] ^ s[35] ^ s[45];
|
||||
c6 = s[6] ^ s[16] ^ s[26] ^ s[36] ^ s[46];
|
||||
c7 = s[7] ^ s[17] ^ s[27] ^ s[37] ^ s[47];
|
||||
c8 = s[8] ^ s[18] ^ s[28] ^ s[38] ^ s[48];
|
||||
c9 = s[9] ^ s[19] ^ s[29] ^ s[39] ^ s[49];
|
||||
|
||||
h = c8 ^ (c2 << 1 | c3 >>> 31);
|
||||
l = c9 ^ (c3 << 1 | c2 >>> 31);
|
||||
s[0] ^= h;
|
||||
s[1] ^= l;
|
||||
s[10] ^= h;
|
||||
s[11] ^= l;
|
||||
s[20] ^= h;
|
||||
s[21] ^= l;
|
||||
s[30] ^= h;
|
||||
s[31] ^= l;
|
||||
s[40] ^= h;
|
||||
s[41] ^= l;
|
||||
h = c0 ^ (c4 << 1 | c5 >>> 31);
|
||||
l = c1 ^ (c5 << 1 | c4 >>> 31);
|
||||
s[2] ^= h;
|
||||
s[3] ^= l;
|
||||
s[12] ^= h;
|
||||
s[13] ^= l;
|
||||
s[22] ^= h;
|
||||
s[23] ^= l;
|
||||
s[32] ^= h;
|
||||
s[33] ^= l;
|
||||
s[42] ^= h;
|
||||
s[43] ^= l;
|
||||
h = c2 ^ (c6 << 1 | c7 >>> 31);
|
||||
l = c3 ^ (c7 << 1 | c6 >>> 31);
|
||||
s[4] ^= h;
|
||||
s[5] ^= l;
|
||||
s[14] ^= h;
|
||||
s[15] ^= l;
|
||||
s[24] ^= h;
|
||||
s[25] ^= l;
|
||||
s[34] ^= h;
|
||||
s[35] ^= l;
|
||||
s[44] ^= h;
|
||||
s[45] ^= l;
|
||||
h = c4 ^ (c8 << 1 | c9 >>> 31);
|
||||
l = c5 ^ (c9 << 1 | c8 >>> 31);
|
||||
s[6] ^= h;
|
||||
s[7] ^= l;
|
||||
s[16] ^= h;
|
||||
s[17] ^= l;
|
||||
s[26] ^= h;
|
||||
s[27] ^= l;
|
||||
s[36] ^= h;
|
||||
s[37] ^= l;
|
||||
s[46] ^= h;
|
||||
s[47] ^= l;
|
||||
h = c6 ^ (c0 << 1 | c1 >>> 31);
|
||||
l = c7 ^ (c1 << 1 | c0 >>> 31);
|
||||
s[8] ^= h;
|
||||
s[9] ^= l;
|
||||
s[18] ^= h;
|
||||
s[19] ^= l;
|
||||
s[28] ^= h;
|
||||
s[29] ^= l;
|
||||
s[38] ^= h;
|
||||
s[39] ^= l;
|
||||
s[48] ^= h;
|
||||
s[49] ^= l;
|
||||
|
||||
b0 = s[0];
|
||||
b1 = s[1];
|
||||
b32 = s[11] << 4 | s[10] >>> 28;
|
||||
b33 = s[10] << 4 | s[11] >>> 28;
|
||||
b14 = s[20] << 3 | s[21] >>> 29;
|
||||
b15 = s[21] << 3 | s[20] >>> 29;
|
||||
b46 = s[31] << 9 | s[30] >>> 23;
|
||||
b47 = s[30] << 9 | s[31] >>> 23;
|
||||
b28 = s[40] << 18 | s[41] >>> 14;
|
||||
b29 = s[41] << 18 | s[40] >>> 14;
|
||||
b20 = s[2] << 1 | s[3] >>> 31;
|
||||
b21 = s[3] << 1 | s[2] >>> 31;
|
||||
b2 = s[13] << 12 | s[12] >>> 20;
|
||||
b3 = s[12] << 12 | s[13] >>> 20;
|
||||
b34 = s[22] << 10 | s[23] >>> 22;
|
||||
b35 = s[23] << 10 | s[22] >>> 22;
|
||||
b16 = s[33] << 13 | s[32] >>> 19;
|
||||
b17 = s[32] << 13 | s[33] >>> 19;
|
||||
b48 = s[42] << 2 | s[43] >>> 30;
|
||||
b49 = s[43] << 2 | s[42] >>> 30;
|
||||
b40 = s[5] << 30 | s[4] >>> 2;
|
||||
b41 = s[4] << 30 | s[5] >>> 2;
|
||||
b22 = s[14] << 6 | s[15] >>> 26;
|
||||
b23 = s[15] << 6 | s[14] >>> 26;
|
||||
b4 = s[25] << 11 | s[24] >>> 21;
|
||||
b5 = s[24] << 11 | s[25] >>> 21;
|
||||
b36 = s[34] << 15 | s[35] >>> 17;
|
||||
b37 = s[35] << 15 | s[34] >>> 17;
|
||||
b18 = s[45] << 29 | s[44] >>> 3;
|
||||
b19 = s[44] << 29 | s[45] >>> 3;
|
||||
b10 = s[6] << 28 | s[7] >>> 4;
|
||||
b11 = s[7] << 28 | s[6] >>> 4;
|
||||
b42 = s[17] << 23 | s[16] >>> 9;
|
||||
b43 = s[16] << 23 | s[17] >>> 9;
|
||||
b24 = s[26] << 25 | s[27] >>> 7;
|
||||
b25 = s[27] << 25 | s[26] >>> 7;
|
||||
b6 = s[36] << 21 | s[37] >>> 11;
|
||||
b7 = s[37] << 21 | s[36] >>> 11;
|
||||
b38 = s[47] << 24 | s[46] >>> 8;
|
||||
b39 = s[46] << 24 | s[47] >>> 8;
|
||||
b30 = s[8] << 27 | s[9] >>> 5;
|
||||
b31 = s[9] << 27 | s[8] >>> 5;
|
||||
b12 = s[18] << 20 | s[19] >>> 12;
|
||||
b13 = s[19] << 20 | s[18] >>> 12;
|
||||
b44 = s[29] << 7 | s[28] >>> 25;
|
||||
b45 = s[28] << 7 | s[29] >>> 25;
|
||||
b26 = s[38] << 8 | s[39] >>> 24;
|
||||
b27 = s[39] << 8 | s[38] >>> 24;
|
||||
b8 = s[48] << 14 | s[49] >>> 18;
|
||||
b9 = s[49] << 14 | s[48] >>> 18;
|
||||
|
||||
s[0] = b0 ^ ~b2 & b4;
|
||||
s[1] = b1 ^ ~b3 & b5;
|
||||
s[10] = b10 ^ ~b12 & b14;
|
||||
s[11] = b11 ^ ~b13 & b15;
|
||||
s[20] = b20 ^ ~b22 & b24;
|
||||
s[21] = b21 ^ ~b23 & b25;
|
||||
s[30] = b30 ^ ~b32 & b34;
|
||||
s[31] = b31 ^ ~b33 & b35;
|
||||
s[40] = b40 ^ ~b42 & b44;
|
||||
s[41] = b41 ^ ~b43 & b45;
|
||||
s[2] = b2 ^ ~b4 & b6;
|
||||
s[3] = b3 ^ ~b5 & b7;
|
||||
s[12] = b12 ^ ~b14 & b16;
|
||||
s[13] = b13 ^ ~b15 & b17;
|
||||
s[22] = b22 ^ ~b24 & b26;
|
||||
s[23] = b23 ^ ~b25 & b27;
|
||||
s[32] = b32 ^ ~b34 & b36;
|
||||
s[33] = b33 ^ ~b35 & b37;
|
||||
s[42] = b42 ^ ~b44 & b46;
|
||||
s[43] = b43 ^ ~b45 & b47;
|
||||
s[4] = b4 ^ ~b6 & b8;
|
||||
s[5] = b5 ^ ~b7 & b9;
|
||||
s[14] = b14 ^ ~b16 & b18;
|
||||
s[15] = b15 ^ ~b17 & b19;
|
||||
s[24] = b24 ^ ~b26 & b28;
|
||||
s[25] = b25 ^ ~b27 & b29;
|
||||
s[34] = b34 ^ ~b36 & b38;
|
||||
s[35] = b35 ^ ~b37 & b39;
|
||||
s[44] = b44 ^ ~b46 & b48;
|
||||
s[45] = b45 ^ ~b47 & b49;
|
||||
s[6] = b6 ^ ~b8 & b0;
|
||||
s[7] = b7 ^ ~b9 & b1;
|
||||
s[16] = b16 ^ ~b18 & b10;
|
||||
s[17] = b17 ^ ~b19 & b11;
|
||||
s[26] = b26 ^ ~b28 & b20;
|
||||
s[27] = b27 ^ ~b29 & b21;
|
||||
s[36] = b36 ^ ~b38 & b30;
|
||||
s[37] = b37 ^ ~b39 & b31;
|
||||
s[46] = b46 ^ ~b48 & b40;
|
||||
s[47] = b47 ^ ~b49 & b41;
|
||||
s[8] = b8 ^ ~b0 & b2;
|
||||
s[9] = b9 ^ ~b1 & b3;
|
||||
s[18] = b18 ^ ~b10 & b12;
|
||||
s[19] = b19 ^ ~b11 & b13;
|
||||
s[28] = b28 ^ ~b20 & b22;
|
||||
s[29] = b29 ^ ~b21 & b23;
|
||||
s[38] = b38 ^ ~b30 & b32;
|
||||
s[39] = b39 ^ ~b31 & b33;
|
||||
s[48] = b48 ^ ~b40 & b42;
|
||||
s[49] = b49 ^ ~b41 & b43;
|
||||
|
||||
s[0] ^= RC[n];
|
||||
s[1] ^= RC[n + 1];
|
||||
}
|
||||
};
|
||||
|
||||
var keccak = function keccak(bits) {
|
||||
return function (str) {
|
||||
var msg;
|
||||
if (str.slice(0, 2) === "0x") {
|
||||
msg = [];
|
||||
for (var i = 2, l = str.length; i < l; i += 2) {
|
||||
msg.push(parseInt(str.slice(i, i + 2), 16));
|
||||
}
|
||||
} else {
|
||||
msg = str;
|
||||
}
|
||||
return update(Keccak(bits, bits), msg);
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
keccak256: keccak(256),
|
||||
keccak512: keccak(512),
|
||||
keccak256s: keccak(256),
|
||||
keccak512s: keccak(512)
|
||||
};
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
Account: require("./account.js"),
|
||||
Nat: require("./nat.js"),
|
||||
Bytes: require("./bytes.js"),
|
||||
Hash: require("./hash.js"),
|
||||
RLP: require("./rlp.js"),
|
||||
Api: require("./api.js"),
|
||||
Provider: require("./provider.js"),
|
||||
Desubits: require("./desubits.js"),
|
||||
Passphrase: require("./passphrase.js")
|
||||
};
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
var merge = function merge(a) {
|
||||
return function (b) {
|
||||
var c = {};
|
||||
for (var key in a) {
|
||||
c[key] = a[key];
|
||||
}for (var _key in b) {
|
||||
c[_key] = b[_key];
|
||||
}return c;
|
||||
};
|
||||
};
|
||||
|
||||
var remove = function remove(removeKey) {
|
||||
return function (a) {
|
||||
var b = {};
|
||||
for (var key in a) {
|
||||
if (key !== removeKey) b[key] = a[key];
|
||||
}return b;
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
merge: merge,
|
||||
remove: remove
|
||||
};
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
var BN = require("bn.js");
|
||||
var Bytes = require("./bytes");
|
||||
|
||||
var fromBN = function fromBN(bn) {
|
||||
return "0x" + bn.toString("hex");
|
||||
};
|
||||
|
||||
var toBN = function toBN(str) {
|
||||
return new BN(str.slice(2), 16);
|
||||
};
|
||||
|
||||
var fromString = function fromString(str) {
|
||||
var bn = "0x" + (str.slice(0, 2) === "0x" ? new BN(str.slice(2), 16) : new BN(str, 10)).toString("hex");
|
||||
return bn === "0x0" ? "0x" : bn;
|
||||
};
|
||||
|
||||
var toEther = function toEther(wei) {
|
||||
return toNumber(div(wei, fromString("10000000000"))) / 100000000;
|
||||
};
|
||||
|
||||
var fromEther = function fromEther(eth) {
|
||||
return mul(fromNumber(Math.floor(eth * 100000000)), fromString("10000000000"));
|
||||
};
|
||||
|
||||
var toString = function toString(a) {
|
||||
return toBN(a).toString(10);
|
||||
};
|
||||
|
||||
var fromNumber = function fromNumber(a) {
|
||||
return typeof a === "string" ? /^0x/.test(a) ? a : "0x" + a : "0x" + new BN(a).toString("hex");
|
||||
};
|
||||
|
||||
var toNumber = function toNumber(a) {
|
||||
return toBN(a).toNumber();
|
||||
};
|
||||
|
||||
var toUint256 = function toUint256(a) {
|
||||
return Bytes.pad(32, a);
|
||||
};
|
||||
|
||||
var bin = function bin(method) {
|
||||
return function (a, b) {
|
||||
return fromBN(toBN(a)[method](toBN(b)));
|
||||
};
|
||||
};
|
||||
|
||||
var add = bin("add");
|
||||
var mul = bin("mul");
|
||||
var div = bin("div");
|
||||
var sub = bin("sub");
|
||||
|
||||
module.exports = {
|
||||
toString: toString,
|
||||
fromString: fromString,
|
||||
toNumber: toNumber,
|
||||
fromNumber: fromNumber,
|
||||
toEther: toEther,
|
||||
fromEther: fromEther,
|
||||
toUint256: toUint256,
|
||||
add: add,
|
||||
mul: mul,
|
||||
div: div,
|
||||
sub: sub
|
||||
};
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
var Hash = require("./hash");
|
||||
var Bytes = require("./bytes");
|
||||
var Desubits = require("./desubits");
|
||||
|
||||
// Bytes -> Bytes
|
||||
var bytesAddChecksum = function bytesAddChecksum(bytes) {
|
||||
var hash = Hash.keccak256(bytes);
|
||||
var sum = Bytes.slice(0, 1, hash);
|
||||
return Bytes.concat(bytes, sum);
|
||||
};
|
||||
|
||||
// Bytes -> Bool
|
||||
var bytesChecksum = function bytesChecksum(bytes) {
|
||||
var length = Bytes.length(bytes);
|
||||
var prefix = Bytes.slice(0, length - 1, bytes);
|
||||
return bytesAddChecksum(prefix) === bytes;
|
||||
};
|
||||
|
||||
// () ~> Passphrase
|
||||
var create = function create() {
|
||||
var bytes = bytesAddChecksum(Bytes.random(11));
|
||||
var seed = Desubits.fromBytes(bytes);
|
||||
var passphrase = seed.replace(/([a-z]{8})/g, "$1 ");
|
||||
return passphrase;
|
||||
};
|
||||
|
||||
// Passphrase -> Bytes
|
||||
var toBytes = function toBytes(passphrase) {
|
||||
var seed = passphrase.replace(/ /g, "");
|
||||
var bytes = Desubits.toBytes(passphrase);
|
||||
return bytes;
|
||||
};
|
||||
|
||||
// Passphrase -> Bool
|
||||
var checksum = function checksum(passphrase) {
|
||||
return bytesChecksum(toBytes(passphrase));
|
||||
};
|
||||
|
||||
// Passphrase -> Bytes
|
||||
var toMasterKey = function toMasterKey(passphrase) {
|
||||
var hash = Hash.keccak256;
|
||||
var bytes = toBytes(passphrase);
|
||||
for (var i = 0, l = Math.pow(2, 12); i < l; ++i) {
|
||||
bytes = hash(bytes);
|
||||
}return bytes;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
create: create,
|
||||
checksum: checksum,
|
||||
toMasterKey: toMasterKey
|
||||
};
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
|
||||
|
||||
var njsp = require("nano-json-stream-parser");
|
||||
var request = require("xhr-request-promise");
|
||||
|
||||
var EthereumProvider = function EthereumProvider(url, intercept) {
|
||||
intercept = intercept || function () {};
|
||||
|
||||
var api = {};
|
||||
var onResponse = {};
|
||||
var callbacks = {};
|
||||
var nextId = 0;
|
||||
var send = void 0;
|
||||
|
||||
var makeSender = function makeSender(send) {
|
||||
var P = function P(fn) {
|
||||
return function () {
|
||||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
|
||||
args[_key] = arguments[_key];
|
||||
}
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
return fn.apply(undefined, _toConsumableArray(args.concat(function (err, res) {
|
||||
return err ? reject(err) : resolve(res);
|
||||
})));
|
||||
});
|
||||
};
|
||||
};
|
||||
var sender = function sender(intercept) {
|
||||
return function (method, params, callback) {
|
||||
var intercepted = intercept(method, params, P(sender(function () {})));
|
||||
if (intercepted) {
|
||||
intercepted.then(function (response) {
|
||||
return callback(null, response);
|
||||
});
|
||||
} else {
|
||||
send(method, params, callback);
|
||||
}
|
||||
};
|
||||
};
|
||||
return sender(intercept);
|
||||
};
|
||||
|
||||
var parseResponse = njsp(function (json) {
|
||||
onResponse[json.id] && onResponse[json.id](null, json.result);
|
||||
});
|
||||
|
||||
var genPayload = function genPayload(method, params) {
|
||||
return {
|
||||
jsonrpc: "2.0",
|
||||
id: ++nextId,
|
||||
method: method,
|
||||
params: params
|
||||
};
|
||||
};
|
||||
|
||||
api.on = function (name, callback) {
|
||||
callbacks[name] = callback;
|
||||
};
|
||||
|
||||
if (/^ws/.test(url)) {
|
||||
var WebSocket = require("w" + "s");
|
||||
var ws = new WebSocket(url);
|
||||
api.send = makeSender(function (method, params, callback) {
|
||||
var intercepted = intercept(method, params, P(send(function () {})));
|
||||
if (intercepted) {
|
||||
intercepted.then(function (response) {
|
||||
return callback(null, response);
|
||||
});
|
||||
} else {
|
||||
var payload = genPayload(method, params);
|
||||
onResponse[payload.id] = callback;
|
||||
ws.send(JSON.stringify(payload));
|
||||
}
|
||||
});
|
||||
ws.on("message", parseResponse);
|
||||
ws.on("open", function () {
|
||||
return callbacks.connect && callbacks.connect(eth);
|
||||
});
|
||||
ws.on("close", function () {
|
||||
return callbacks.disconnect && callbacks.disconnect();
|
||||
});
|
||||
} else if (/^http/.test(url)) {
|
||||
api.send = makeSender(function (method, params, callback) {
|
||||
request(url, {
|
||||
method: "POST",
|
||||
contentType: "application/json-rpc",
|
||||
body: JSON.stringify(genPayload(method, params)) }).then(function (answer) {
|
||||
var resp = JSON.parse(answer);
|
||||
if (resp.error) {
|
||||
callback(resp.error.message);
|
||||
} else {
|
||||
callback(null, resp.result);
|
||||
}
|
||||
}).catch(function (err) {
|
||||
return callback("Couldn't connect to Ethereum node.");
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(function () {
|
||||
callbacks.connect && callbacks.connect();
|
||||
}, 1);
|
||||
} else {
|
||||
throw "IPC not supported yet.";
|
||||
}
|
||||
|
||||
return api;
|
||||
};
|
||||
|
||||
module.exports = EthereumProvider;
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
// The RLP format
|
||||
// Serialization and deserialization for the BytesTree type, under the following grammar:
|
||||
// | First byte | Meaning |
|
||||
// | ---------- | -------------------------------------------------------------------------- |
|
||||
// | 0 to 127 | HEX(leaf) |
|
||||
// | 128 to 183 | HEX(length_of_leaf + 128) + HEX(leaf) |
|
||||
// | 184 to 191 | HEX(length_of_length_of_leaf + 128 + 55) + HEX(length_of_leaf) + HEX(leaf) |
|
||||
// | 192 to 247 | HEX(length_of_node + 192) + HEX(node) |
|
||||
// | 248 to 255 | HEX(length_of_length_of_node + 128 + 55) + HEX(length_of_node) + HEX(node) |
|
||||
|
||||
var encode = function encode(tree) {
|
||||
var padEven = function padEven(str) {
|
||||
return str.length % 2 === 0 ? str : "0" + str;
|
||||
};
|
||||
|
||||
var uint = function uint(num) {
|
||||
return padEven(num.toString(16));
|
||||
};
|
||||
|
||||
var length = function length(len, add) {
|
||||
return len < 56 ? uint(add + len) : uint(add + uint(len).length / 2 + 55) + uint(len);
|
||||
};
|
||||
|
||||
var dataTree = function dataTree(tree) {
|
||||
if (typeof tree === "string") {
|
||||
var hex = tree.slice(2);
|
||||
var pre = hex.length != 2 || hex >= "80" ? length(hex.length / 2, 128) : "";
|
||||
return pre + hex;
|
||||
} else {
|
||||
var _hex = tree.map(dataTree).join("");
|
||||
var _pre = length(_hex.length / 2, 192);
|
||||
return _pre + _hex;
|
||||
}
|
||||
};
|
||||
|
||||
return "0x" + dataTree(tree);
|
||||
};
|
||||
|
||||
var decode = function decode(hex) {
|
||||
var i = 2;
|
||||
|
||||
var parseTree = function parseTree() {
|
||||
if (i >= hex.length) throw "";
|
||||
var head = hex.slice(i, i + 2);
|
||||
return head < "80" ? (i += 2, "0x" + head) : head < "c0" ? parseHex() : parseList();
|
||||
};
|
||||
|
||||
var parseLength = function parseLength() {
|
||||
var len = parseInt(hex.slice(i, i += 2), 16) % 64;
|
||||
return len < 56 ? len : parseInt(hex.slice(i, i += (len - 55) * 2), 16);
|
||||
};
|
||||
|
||||
var parseHex = function parseHex() {
|
||||
var len = parseLength();
|
||||
return "0x" + hex.slice(i, i += len * 2);
|
||||
};
|
||||
|
||||
var parseList = function parseList() {
|
||||
var lim = parseLength() * 2 + i;
|
||||
var list = [];
|
||||
while (i < lim) {
|
||||
list.push(parseTree());
|
||||
}return list;
|
||||
};
|
||||
|
||||
try {
|
||||
return parseTree();
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { encode: encode, decode: decode };
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
var F = require("forall");
|
||||
var A = require("./array");
|
||||
var B = require("./bytes");
|
||||
var Account = require("./account");
|
||||
|
||||
F.Bytes = F(F.Type, {
|
||||
form: "a JavaScript String starting with a `0x`, followed by an even number of low-case hex chars (i.e., `0123456789abcdef`)",
|
||||
rand: function rand() {
|
||||
return "0x" + A.generate((Math.random() * 16 | 0) * 2, function () {
|
||||
return (Math.random() * 16 | 0).toString(16);
|
||||
}).join("");
|
||||
},
|
||||
test: function test(value) {
|
||||
return typeof value === "string" && /^0x([0-9a-f][0-9a-f])*$/.test(value);
|
||||
}
|
||||
}).__name("Bytes").__desc("any arbitrary data");
|
||||
|
||||
F.NBytes = function (bytes) {
|
||||
return F(F.Type, {
|
||||
form: "a JavaScript String starting with a `0x`, followed by " + bytes * 2 + " low-case hex chars (i.e., `0123456789abcdef`)",
|
||||
test: function test(value) {
|
||||
return F.Bytes.test(value) && value.length === bytes * 2 + 2;
|
||||
},
|
||||
rand: function rand() {
|
||||
return "0x" + A.generate(bytes * 2, function () {
|
||||
return (Math.random() * 16 | 0).toString(16);
|
||||
}).join("");
|
||||
}
|
||||
}).__name("NBytes(" + bytes + ")").__desc("any arbitrary data of exactly " + bytes + "-byte" + (bytes > 1 ? "s" : ""));
|
||||
};
|
||||
|
||||
F.Nat = F(F.Type, {
|
||||
form: "a JavaScript String starting with a `0x`, followed by at least one low-case hex char different from 0, followed by any number of low-case hex chars (i.e., `0123456789abcdef`)",
|
||||
test: function test(value) {
|
||||
return typeof value === "string" && /^0x[1-9a-f]([0-9a-f])*$/.test(value);
|
||||
},
|
||||
rand: function rand() {
|
||||
return "0x" + (Math.random() * Math.pow(2, 50) | 0).toString(16);
|
||||
}
|
||||
}).__name("Nat").__desc("an arbitrarily long non-negative integer number");
|
||||
|
||||
F.Address = F(F.Type, {
|
||||
form: "a JavaScript String starting with a `0x`, followed by 40 hex chars (i.e., `0123456789abcdefABCDEF`), with the nth hex being uppercase iff the nth hex of the keccak256 of the lowercase address in ASCII is > 7",
|
||||
test: function test(address) {
|
||||
return (/^(0x)?[0-9a-f]{40}$/i.test(address) && Account.toChecksum(address.toLowerCase()) === address
|
||||
);
|
||||
},
|
||||
rand: function rand() {
|
||||
return F.Account.rand().address;
|
||||
}
|
||||
}).__name("Address").__desc("an Ethereum public address");
|
||||
|
||||
F.Hash = F(F.Type, {
|
||||
form: F.NBytes(32).form,
|
||||
test: F.NBytes(32).test,
|
||||
rand: F.NBytes(32).rand
|
||||
}).__name("Hash").__desc("a Keccak-256 hash");
|
||||
|
||||
F.PrivateKey = F(F.Type, {
|
||||
form: F.NBytes(32).form,
|
||||
test: F.NBytes(32).test,
|
||||
rand: F.NBytes(32).rand
|
||||
}).__name("PrivateKey").__desc("an Ethereum private key");
|
||||
|
||||
F.Account = function () {
|
||||
var base = F.Struct({
|
||||
address: F.Address,
|
||||
privateKey: F.PrivateKey
|
||||
});
|
||||
return F(F.Type, {
|
||||
form: base.form,
|
||||
test: base.test,
|
||||
rand: function rand() {
|
||||
return Account.create("");
|
||||
}
|
||||
});
|
||||
}().__name("Account").__desc("an Ethereum account");
|
||||
|
||||
F.BytesTree = F(F.Type, {
|
||||
form: "either " + F.Bytes.form + ", or a tree of nested JavaScript Arrays of BytesTrees",
|
||||
test: function test(value) {
|
||||
return F.Bytes.test(value) || value instanceof Array && value.reduce(function (r, v) {
|
||||
return F.BytesTree.test(v) && r;
|
||||
}, true);
|
||||
},
|
||||
rand: function rand() {
|
||||
var list = [];
|
||||
while (Math.random() < 0.8) {
|
||||
if (Math.random() < 0.8) list.push(F.Bytes.rand());else list.push(F.BytesTree.rand());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}).__name("BytesTree").__desc("a tree of arbitrary binary data");
|
||||
|
||||
module.exports = F;
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/*!
|
||||
* ws: a node.js websocket client
|
||||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const WebSocket = require('./lib/WebSocket');
|
||||
|
||||
WebSocket.Server = require('./lib/WebSocketServer');
|
||||
WebSocket.Receiver = require('./lib/Receiver');
|
||||
WebSocket.Sender = require('./lib/Sender');
|
||||
|
||||
module.exports = WebSocket;
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*!
|
||||
* ws: a node.js websocket client
|
||||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const safeBuffer = require('safe-buffer');
|
||||
|
||||
const Buffer = safeBuffer.Buffer;
|
||||
|
||||
/**
|
||||
* Merges an array of buffers into a new buffer.
|
||||
*
|
||||
* @param {Buffer[]} list The array of buffers to concat
|
||||
* @param {Number} totalLength The total length of buffers in the list
|
||||
* @return {Buffer} The resulting buffer
|
||||
* @public
|
||||
*/
|
||||
const concat = (list, totalLength) => {
|
||||
const target = Buffer.allocUnsafe(totalLength);
|
||||
var offset = 0;
|
||||
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
const buf = list[i];
|
||||
buf.copy(target, offset);
|
||||
offset += buf.length;
|
||||
}
|
||||
|
||||
return target;
|
||||
};
|
||||
|
||||
try {
|
||||
const bufferUtil = require('bufferutil');
|
||||
|
||||
module.exports = Object.assign({ concat }, bufferUtil.BufferUtil || bufferUtil);
|
||||
} catch (e) /* istanbul ignore next */ {
|
||||
/**
|
||||
* Masks a buffer using the given mask.
|
||||
*
|
||||
* @param {Buffer} source The buffer to mask
|
||||
* @param {Buffer} mask The mask to use
|
||||
* @param {Buffer} output The buffer where to store the result
|
||||
* @param {Number} offset The offset at which to start writing
|
||||
* @param {Number} length The number of bytes to mask.
|
||||
* @public
|
||||
*/
|
||||
const mask = (source, mask, output, offset, length) => {
|
||||
for (var i = 0; i < length; i++) {
|
||||
output[offset + i] = source[i] ^ mask[i & 3];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Unmasks a buffer using the given mask.
|
||||
*
|
||||
* @param {Buffer} buffer The buffer to unmask
|
||||
* @param {Buffer} mask The mask to use
|
||||
* @public
|
||||
*/
|
||||
const unmask = (buffer, mask) => {
|
||||
// Required until https://github.com/nodejs/node/issues/9006 is resolved.
|
||||
const length = buffer.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
buffer[i] ^= mask[i & 3];
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { concat, mask, unmask };
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
const safeBuffer = require('safe-buffer');
|
||||
|
||||
const Buffer = safeBuffer.Buffer;
|
||||
|
||||
exports.BINARY_TYPES = ['nodebuffer', 'arraybuffer', 'fragments'];
|
||||
exports.GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
|
||||
exports.EMPTY_BUFFER = Buffer.alloc(0);
|
||||
exports.NOOP = () => {};
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*!
|
||||
* ws: a node.js websocket client
|
||||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
isValidErrorCode: function (code) {
|
||||
return (code >= 1000 && code <= 1013 && code !== 1004 && code !== 1005 && code !== 1006) ||
|
||||
(code >= 3000 && code <= 4999);
|
||||
},
|
||||
1000: 'normal',
|
||||
1001: 'going away',
|
||||
1002: 'protocol error',
|
||||
1003: 'unsupported data',
|
||||
1004: 'reserved',
|
||||
1005: 'reserved for extensions',
|
||||
1006: 'reserved for extensions',
|
||||
1007: 'inconsistent or invalid data',
|
||||
1008: 'policy violation',
|
||||
1009: 'message too big',
|
||||
1010: 'extension handshake missing',
|
||||
1011: 'an unexpected condition prevented the request from being fulfilled',
|
||||
1012: 'service restart',
|
||||
1013: 'try again later'
|
||||
};
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Class representing an event.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
class Event {
|
||||
/**
|
||||
* Create a new `Event`.
|
||||
*
|
||||
* @param {String} type The name of the event
|
||||
* @param {Object} target A reference to the target to which the event was dispatched
|
||||
*/
|
||||
constructor (type, target) {
|
||||
this.target = target;
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing a message event.
|
||||
*
|
||||
* @extends Event
|
||||
* @private
|
||||
*/
|
||||
class MessageEvent extends Event {
|
||||
/**
|
||||
* Create a new `MessageEvent`.
|
||||
*
|
||||
* @param {(String|Buffer|ArrayBuffer|Buffer[])} data The received data
|
||||
* @param {WebSocket} target A reference to the target to which the event was dispatched
|
||||
*/
|
||||
constructor (data, target) {
|
||||
super('message', target);
|
||||
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing a close event.
|
||||
*
|
||||
* @extends Event
|
||||
* @private
|
||||
*/
|
||||
class CloseEvent extends Event {
|
||||
/**
|
||||
* Create a new `CloseEvent`.
|
||||
*
|
||||
* @param {Number} code The status code explaining why the connection is being closed
|
||||
* @param {String} reason A human-readable string explaining why the connection is closing
|
||||
* @param {WebSocket} target A reference to the target to which the event was dispatched
|
||||
*/
|
||||
constructor (code, reason, target) {
|
||||
super('close', target);
|
||||
|
||||
this.wasClean = target._closeFrameReceived && target._closeFrameSent;
|
||||
this.reason = reason;
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing an open event.
|
||||
*
|
||||
* @extends Event
|
||||
* @private
|
||||
*/
|
||||
class OpenEvent extends Event {
|
||||
/**
|
||||
* Create a new `OpenEvent`.
|
||||
*
|
||||
* @param {WebSocket} target A reference to the target to which the event was dispatched
|
||||
*/
|
||||
constructor (target) {
|
||||
super('open', target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This provides methods for emulating the `EventTarget` interface. It's not
|
||||
* meant to be used directly.
|
||||
*
|
||||
* @mixin
|
||||
*/
|
||||
const EventTarget = {
|
||||
/**
|
||||
* Register an event listener.
|
||||
*
|
||||
* @param {String} method A string representing the event type to listen for
|
||||
* @param {Function} listener The listener to add
|
||||
* @public
|
||||
*/
|
||||
addEventListener (method, listener) {
|
||||
if (typeof listener !== 'function') return;
|
||||
|
||||
function onMessage (data) {
|
||||
listener.call(this, new MessageEvent(data, this));
|
||||
}
|
||||
|
||||
function onClose (code, message) {
|
||||
listener.call(this, new CloseEvent(code, message, this));
|
||||
}
|
||||
|
||||
function onError (event) {
|
||||
event.type = 'error';
|
||||
event.target = this;
|
||||
listener.call(this, event);
|
||||
}
|
||||
|
||||
function onOpen () {
|
||||
listener.call(this, new OpenEvent(this));
|
||||
}
|
||||
|
||||
if (method === 'message') {
|
||||
onMessage._listener = listener;
|
||||
this.on(method, onMessage);
|
||||
} else if (method === 'close') {
|
||||
onClose._listener = listener;
|
||||
this.on(method, onClose);
|
||||
} else if (method === 'error') {
|
||||
onError._listener = listener;
|
||||
this.on(method, onError);
|
||||
} else if (method === 'open') {
|
||||
onOpen._listener = listener;
|
||||
this.on(method, onOpen);
|
||||
} else {
|
||||
this.on(method, listener);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an event listener.
|
||||
*
|
||||
* @param {String} method A string representing the event type to remove
|
||||
* @param {Function} listener The listener to remove
|
||||
* @public
|
||||
*/
|
||||
removeEventListener (method, listener) {
|
||||
const listeners = this.listeners(method);
|
||||
|
||||
for (var i = 0; i < listeners.length; i++) {
|
||||
if (listeners[i] === listener || listeners[i]._listener === listener) {
|
||||
this.removeListener(method, listeners[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = EventTarget;
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
'use strict';
|
||||
|
||||
//
|
||||
// Allowed token characters:
|
||||
//
|
||||
// '!', '#', '$', '%', '&', ''', '*', '+', '-',
|
||||
// '.', 0-9, A-Z, '^', '_', '`', a-z, '|', '~'
|
||||
//
|
||||
// tokenChars[32] === 0 // ' '
|
||||
// tokenChars[33] === 1 // '!'
|
||||
// tokenChars[34] === 0 // '"'
|
||||
// ...
|
||||
//
|
||||
const tokenChars = [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31
|
||||
0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 - 47
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80 - 95
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0 // 112 - 127
|
||||
];
|
||||
|
||||
/**
|
||||
* Adds an offer to the map of extension offers or a parameter to the map of
|
||||
* parameters.
|
||||
*
|
||||
* @param {Object} dest The map of extension offers or parameters
|
||||
* @param {String} name The extension or parameter name
|
||||
* @param {(Object|Boolean|String)} elem The extension parameters or the
|
||||
* parameter value
|
||||
* @private
|
||||
*/
|
||||
function push (dest, name, elem) {
|
||||
if (Object.prototype.hasOwnProperty.call(dest, name)) dest[name].push(elem);
|
||||
else dest[name] = [elem];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the `Sec-WebSocket-Extensions` header into an object.
|
||||
*
|
||||
* @param {String} header The field value of the header
|
||||
* @return {Object} The parsed object
|
||||
* @public
|
||||
*/
|
||||
function parse (header) {
|
||||
const offers = {};
|
||||
|
||||
if (header === undefined || header === '') return offers;
|
||||
|
||||
var params = {};
|
||||
var mustUnescape = false;
|
||||
var isEscaping = false;
|
||||
var inQuotes = false;
|
||||
var extensionName;
|
||||
var paramName;
|
||||
var start = -1;
|
||||
var end = -1;
|
||||
|
||||
for (var i = 0; i < header.length; i++) {
|
||||
const code = header.charCodeAt(i);
|
||||
|
||||
if (extensionName === undefined) {
|
||||
if (end === -1 && tokenChars[code] === 1) {
|
||||
if (start === -1) start = i;
|
||||
} else if (code === 0x20/* ' ' */|| code === 0x09/* '\t' */) {
|
||||
if (end === -1 && start !== -1) end = i;
|
||||
} else if (code === 0x3b/* ';' */ || code === 0x2c/* ',' */) {
|
||||
if (start === -1) throw new Error(`unexpected character at index ${i}`);
|
||||
|
||||
if (end === -1) end = i;
|
||||
const name = header.slice(start, end);
|
||||
if (code === 0x2c) {
|
||||
push(offers, name, params);
|
||||
params = {};
|
||||
} else {
|
||||
extensionName = name;
|
||||
}
|
||||
|
||||
start = end = -1;
|
||||
} else {
|
||||
throw new Error(`unexpected character at index ${i}`);
|
||||
}
|
||||
} else if (paramName === undefined) {
|
||||
if (end === -1 && tokenChars[code] === 1) {
|
||||
if (start === -1) start = i;
|
||||
} else if (code === 0x20 || code === 0x09) {
|
||||
if (end === -1 && start !== -1) end = i;
|
||||
} else if (code === 0x3b || code === 0x2c) {
|
||||
if (start === -1) throw new Error(`unexpected character at index ${i}`);
|
||||
|
||||
if (end === -1) end = i;
|
||||
push(params, header.slice(start, end), true);
|
||||
if (code === 0x2c) {
|
||||
push(offers, extensionName, params);
|
||||
params = {};
|
||||
extensionName = undefined;
|
||||
}
|
||||
|
||||
start = end = -1;
|
||||
} else if (code === 0x3d/* '=' */&& start !== -1 && end === -1) {
|
||||
paramName = header.slice(start, i);
|
||||
start = end = -1;
|
||||
} else {
|
||||
throw new Error(`unexpected character at index ${i}`);
|
||||
}
|
||||
} else {
|
||||
//
|
||||
// The value of a quoted-string after unescaping must conform to the
|
||||
// token ABNF, so only token characters are valid.
|
||||
// Ref: https://tools.ietf.org/html/rfc6455#section-9.1
|
||||
//
|
||||
if (isEscaping) {
|
||||
if (tokenChars[code] !== 1) {
|
||||
throw new Error(`unexpected character at index ${i}`);
|
||||
}
|
||||
if (start === -1) start = i;
|
||||
else if (!mustUnescape) mustUnescape = true;
|
||||
isEscaping = false;
|
||||
} else if (inQuotes) {
|
||||
if (tokenChars[code] === 1) {
|
||||
if (start === -1) start = i;
|
||||
} else if (code === 0x22/* '"' */ && start !== -1) {
|
||||
inQuotes = false;
|
||||
end = i;
|
||||
} else if (code === 0x5c/* '\' */) {
|
||||
isEscaping = true;
|
||||
} else {
|
||||
throw new Error(`unexpected character at index ${i}`);
|
||||
}
|
||||
} else if (code === 0x22 && header.charCodeAt(i - 1) === 0x3d) {
|
||||
inQuotes = true;
|
||||
} else if (end === -1 && tokenChars[code] === 1) {
|
||||
if (start === -1) start = i;
|
||||
} else if (start !== -1 && (code === 0x20 || code === 0x09)) {
|
||||
if (end === -1) end = i;
|
||||
} else if (code === 0x3b || code === 0x2c) {
|
||||
if (start === -1) throw new Error(`unexpected character at index ${i}`);
|
||||
|
||||
if (end === -1) end = i;
|
||||
var value = header.slice(start, end);
|
||||
if (mustUnescape) {
|
||||
value = value.replace(/\\/g, '');
|
||||
mustUnescape = false;
|
||||
}
|
||||
push(params, paramName, value);
|
||||
if (code === 0x2c) {
|
||||
push(offers, extensionName, params);
|
||||
params = {};
|
||||
extensionName = undefined;
|
||||
}
|
||||
|
||||
paramName = undefined;
|
||||
start = end = -1;
|
||||
} else {
|
||||
throw new Error(`unexpected character at index ${i}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (start === -1 || inQuotes) throw new Error('unexpected end of input');
|
||||
|
||||
if (end === -1) end = i;
|
||||
const token = header.slice(start, end);
|
||||
if (extensionName === undefined) {
|
||||
push(offers, token, {});
|
||||
} else {
|
||||
if (paramName === undefined) {
|
||||
push(params, token, true);
|
||||
} else if (mustUnescape) {
|
||||
push(params, paramName, token.replace(/\\/g, ''));
|
||||
} else {
|
||||
push(params, paramName, token);
|
||||
}
|
||||
push(offers, extensionName, params);
|
||||
}
|
||||
|
||||
return offers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a parsed `Sec-WebSocket-Extensions` header to a string.
|
||||
*
|
||||
* @param {Object} value The object to format
|
||||
* @return {String} A string representing the given value
|
||||
* @public
|
||||
*/
|
||||
function format (value) {
|
||||
return Object.keys(value).map((token) => {
|
||||
var paramsList = value[token];
|
||||
if (!Array.isArray(paramsList)) paramsList = [paramsList];
|
||||
return paramsList.map((params) => {
|
||||
return [token].concat(Object.keys(params).map((k) => {
|
||||
var p = params[k];
|
||||
if (!Array.isArray(p)) p = [p];
|
||||
return p.map((v) => v === true ? k : `${k}=${v}`).join('; ');
|
||||
})).join('; ');
|
||||
}).join(', ');
|
||||
}).join(', ');
|
||||
}
|
||||
|
||||
module.exports = { format, parse };
|
||||
+507
@@ -0,0 +1,507 @@
|
||||
'use strict';
|
||||
|
||||
const safeBuffer = require('safe-buffer');
|
||||
const Limiter = require('async-limiter');
|
||||
const zlib = require('zlib');
|
||||
|
||||
const bufferUtil = require('./BufferUtil');
|
||||
|
||||
const Buffer = safeBuffer.Buffer;
|
||||
|
||||
const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);
|
||||
const EMPTY_BLOCK = Buffer.from([0x00]);
|
||||
|
||||
const kWriteInProgress = Symbol('write-in-progress');
|
||||
const kPendingClose = Symbol('pending-close');
|
||||
const kTotalLength = Symbol('total-length');
|
||||
const kCallback = Symbol('callback');
|
||||
const kBuffers = Symbol('buffers');
|
||||
const kError = Symbol('error');
|
||||
const kOwner = Symbol('owner');
|
||||
|
||||
//
|
||||
// We limit zlib concurrency, which prevents severe memory fragmentation
|
||||
// as documented in https://github.com/nodejs/node/issues/8871#issuecomment-250915913
|
||||
// and https://github.com/websockets/ws/issues/1202
|
||||
//
|
||||
// Intentionally global; it's the global thread pool that's an issue.
|
||||
//
|
||||
let zlibLimiter;
|
||||
|
||||
/**
|
||||
* permessage-deflate implementation.
|
||||
*/
|
||||
class PerMessageDeflate {
|
||||
/**
|
||||
* Creates a PerMessageDeflate instance.
|
||||
*
|
||||
* @param {Object} options Configuration options
|
||||
* @param {Boolean} options.serverNoContextTakeover Request/accept disabling
|
||||
* of server context takeover
|
||||
* @param {Boolean} options.clientNoContextTakeover Advertise/acknowledge
|
||||
* disabling of client context takeover
|
||||
* @param {(Boolean|Number)} options.serverMaxWindowBits Request/confirm the
|
||||
* use of a custom server window size
|
||||
* @param {(Boolean|Number)} options.clientMaxWindowBits Advertise support
|
||||
* for, or request, a custom client window size
|
||||
* @param {Number} options.level The value of zlib's `level` param
|
||||
* @param {Number} options.memLevel The value of zlib's `memLevel` param
|
||||
* @param {Number} options.threshold Size (in bytes) below which messages
|
||||
* should not be compressed
|
||||
* @param {Number} options.concurrencyLimit The number of concurrent calls to
|
||||
* zlib
|
||||
* @param {Boolean} isServer Create the instance in either server or client
|
||||
* mode
|
||||
* @param {Number} maxPayload The maximum allowed message length
|
||||
*/
|
||||
constructor (options, isServer, maxPayload) {
|
||||
this._maxPayload = maxPayload | 0;
|
||||
this._options = options || {};
|
||||
this._threshold = this._options.threshold !== undefined
|
||||
? this._options.threshold
|
||||
: 1024;
|
||||
this._isServer = !!isServer;
|
||||
this._deflate = null;
|
||||
this._inflate = null;
|
||||
|
||||
this.params = null;
|
||||
|
||||
if (!zlibLimiter) {
|
||||
const concurrency = this._options.concurrencyLimit !== undefined
|
||||
? this._options.concurrencyLimit
|
||||
: 10;
|
||||
zlibLimiter = new Limiter({ concurrency });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {String}
|
||||
*/
|
||||
static get extensionName () {
|
||||
return 'permessage-deflate';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create extension parameters offer.
|
||||
*
|
||||
* @return {Object} Extension parameters
|
||||
* @public
|
||||
*/
|
||||
offer () {
|
||||
const params = {};
|
||||
|
||||
if (this._options.serverNoContextTakeover) {
|
||||
params.server_no_context_takeover = true;
|
||||
}
|
||||
if (this._options.clientNoContextTakeover) {
|
||||
params.client_no_context_takeover = true;
|
||||
}
|
||||
if (this._options.serverMaxWindowBits) {
|
||||
params.server_max_window_bits = this._options.serverMaxWindowBits;
|
||||
}
|
||||
if (this._options.clientMaxWindowBits) {
|
||||
params.client_max_window_bits = this._options.clientMaxWindowBits;
|
||||
} else if (this._options.clientMaxWindowBits == null) {
|
||||
params.client_max_window_bits = true;
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept extension offer.
|
||||
*
|
||||
* @param {Array} paramsList Extension parameters
|
||||
* @return {Object} Accepted configuration
|
||||
* @public
|
||||
*/
|
||||
accept (paramsList) {
|
||||
paramsList = this.normalizeParams(paramsList);
|
||||
|
||||
var params;
|
||||
if (this._isServer) {
|
||||
params = this.acceptAsServer(paramsList);
|
||||
} else {
|
||||
params = this.acceptAsClient(paramsList);
|
||||
}
|
||||
|
||||
this.params = params;
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases all resources used by the extension.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
cleanup () {
|
||||
if (this._inflate) {
|
||||
if (this._inflate[kWriteInProgress]) {
|
||||
this._inflate[kPendingClose] = true;
|
||||
} else {
|
||||
this._inflate.close();
|
||||
this._inflate = null;
|
||||
}
|
||||
}
|
||||
if (this._deflate) {
|
||||
if (this._deflate[kWriteInProgress]) {
|
||||
this._deflate[kPendingClose] = true;
|
||||
} else {
|
||||
this._deflate.close();
|
||||
this._deflate = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept extension offer from client.
|
||||
*
|
||||
* @param {Array} paramsList Extension parameters
|
||||
* @return {Object} Accepted configuration
|
||||
* @private
|
||||
*/
|
||||
acceptAsServer (paramsList) {
|
||||
const accepted = {};
|
||||
const result = paramsList.some((params) => {
|
||||
if (
|
||||
(this._options.serverNoContextTakeover === false &&
|
||||
params.server_no_context_takeover) ||
|
||||
(this._options.serverMaxWindowBits === false &&
|
||||
params.server_max_window_bits) ||
|
||||
(typeof this._options.serverMaxWindowBits === 'number' &&
|
||||
typeof params.server_max_window_bits === 'number' &&
|
||||
this._options.serverMaxWindowBits > params.server_max_window_bits) ||
|
||||
(typeof this._options.clientMaxWindowBits === 'number' &&
|
||||
!params.client_max_window_bits)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this._options.serverNoContextTakeover ||
|
||||
params.server_no_context_takeover
|
||||
) {
|
||||
accepted.server_no_context_takeover = true;
|
||||
}
|
||||
if (
|
||||
this._options.clientNoContextTakeover ||
|
||||
(this._options.clientNoContextTakeover !== false &&
|
||||
params.client_no_context_takeover)
|
||||
) {
|
||||
accepted.client_no_context_takeover = true;
|
||||
}
|
||||
if (typeof this._options.serverMaxWindowBits === 'number') {
|
||||
accepted.server_max_window_bits = this._options.serverMaxWindowBits;
|
||||
} else if (typeof params.server_max_window_bits === 'number') {
|
||||
accepted.server_max_window_bits = params.server_max_window_bits;
|
||||
}
|
||||
if (typeof this._options.clientMaxWindowBits === 'number') {
|
||||
accepted.client_max_window_bits = this._options.clientMaxWindowBits;
|
||||
} else if (
|
||||
this._options.clientMaxWindowBits !== false &&
|
||||
typeof params.client_max_window_bits === 'number'
|
||||
) {
|
||||
accepted.client_max_window_bits = params.client_max_window_bits;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!result) throw new Error("Doesn't support the offered configuration");
|
||||
|
||||
return accepted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept extension response from server.
|
||||
*
|
||||
* @param {Array} paramsList Extension parameters
|
||||
* @return {Object} Accepted configuration
|
||||
* @private
|
||||
*/
|
||||
acceptAsClient (paramsList) {
|
||||
const params = paramsList[0];
|
||||
|
||||
if (
|
||||
this._options.clientNoContextTakeover === false &&
|
||||
params.client_no_context_takeover
|
||||
) {
|
||||
throw new Error('Invalid value for "client_no_context_takeover"');
|
||||
}
|
||||
|
||||
if (
|
||||
(typeof this._options.clientMaxWindowBits === 'number' &&
|
||||
(!params.client_max_window_bits ||
|
||||
params.client_max_window_bits > this._options.clientMaxWindowBits)) ||
|
||||
(this._options.clientMaxWindowBits === false &&
|
||||
params.client_max_window_bits)
|
||||
) {
|
||||
throw new Error('Invalid value for "client_max_window_bits"');
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize extensions parameters.
|
||||
*
|
||||
* @param {Array} paramsList Extension parameters
|
||||
* @return {Array} Normalized extensions parameters
|
||||
* @private
|
||||
*/
|
||||
normalizeParams (paramsList) {
|
||||
return paramsList.map((params) => {
|
||||
Object.keys(params).forEach((key) => {
|
||||
var value = params[key];
|
||||
if (value.length > 1) {
|
||||
throw new Error(`Multiple extension parameters for ${key}`);
|
||||
}
|
||||
|
||||
value = value[0];
|
||||
|
||||
switch (key) {
|
||||
case 'server_no_context_takeover':
|
||||
case 'client_no_context_takeover':
|
||||
if (value !== true) {
|
||||
throw new Error(`invalid extension parameter value for ${key} (${value})`);
|
||||
}
|
||||
params[key] = true;
|
||||
break;
|
||||
case 'server_max_window_bits':
|
||||
case 'client_max_window_bits':
|
||||
if (typeof value === 'string') {
|
||||
value = parseInt(value, 10);
|
||||
if (
|
||||
Number.isNaN(value) ||
|
||||
value < zlib.Z_MIN_WINDOWBITS ||
|
||||
value > zlib.Z_MAX_WINDOWBITS
|
||||
) {
|
||||
throw new Error(`invalid extension parameter value for ${key} (${value})`);
|
||||
}
|
||||
}
|
||||
if (!this._isServer && value === true) {
|
||||
throw new Error(`Missing extension parameter value for ${key}`);
|
||||
}
|
||||
params[key] = value;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Not defined extension parameter (${key})`);
|
||||
}
|
||||
});
|
||||
return params;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompress data. Concurrency limited by async-limiter.
|
||||
*
|
||||
* @param {Buffer} data Compressed data
|
||||
* @param {Boolean} fin Specifies whether or not this is the last fragment
|
||||
* @param {Function} callback Callback
|
||||
* @public
|
||||
*/
|
||||
decompress (data, fin, callback) {
|
||||
zlibLimiter.push((done) => {
|
||||
this._decompress(data, fin, (err, result) => {
|
||||
done();
|
||||
callback(err, result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compress data. Concurrency limited by async-limiter.
|
||||
*
|
||||
* @param {Buffer} data Data to compress
|
||||
* @param {Boolean} fin Specifies whether or not this is the last fragment
|
||||
* @param {Function} callback Callback
|
||||
* @public
|
||||
*/
|
||||
compress (data, fin, callback) {
|
||||
zlibLimiter.push((done) => {
|
||||
this._compress(data, fin, (err, result) => {
|
||||
done();
|
||||
callback(err, result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompress data.
|
||||
*
|
||||
* @param {Buffer} data Compressed data
|
||||
* @param {Boolean} fin Specifies whether or not this is the last fragment
|
||||
* @param {Function} callback Callback
|
||||
* @private
|
||||
*/
|
||||
_decompress (data, fin, callback) {
|
||||
const endpoint = this._isServer ? 'client' : 'server';
|
||||
|
||||
if (!this._inflate) {
|
||||
const key = `${endpoint}_max_window_bits`;
|
||||
const windowBits = typeof this.params[key] !== 'number'
|
||||
? zlib.Z_DEFAULT_WINDOWBITS
|
||||
: this.params[key];
|
||||
|
||||
this._inflate = zlib.createInflateRaw({ windowBits });
|
||||
this._inflate[kTotalLength] = 0;
|
||||
this._inflate[kBuffers] = [];
|
||||
this._inflate[kOwner] = this;
|
||||
this._inflate.on('error', inflateOnError);
|
||||
this._inflate.on('data', inflateOnData);
|
||||
}
|
||||
|
||||
this._inflate[kCallback] = callback;
|
||||
this._inflate[kWriteInProgress] = true;
|
||||
|
||||
this._inflate.write(data);
|
||||
if (fin) this._inflate.write(TRAILER);
|
||||
|
||||
this._inflate.flush(() => {
|
||||
const err = this._inflate[kError];
|
||||
|
||||
if (err) {
|
||||
this._inflate.close();
|
||||
this._inflate = null;
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = bufferUtil.concat(
|
||||
this._inflate[kBuffers],
|
||||
this._inflate[kTotalLength]
|
||||
);
|
||||
|
||||
if (
|
||||
(fin && this.params[`${endpoint}_no_context_takeover`]) ||
|
||||
this._inflate[kPendingClose]
|
||||
) {
|
||||
this._inflate.close();
|
||||
this._inflate = null;
|
||||
} else {
|
||||
this._inflate[kWriteInProgress] = false;
|
||||
this._inflate[kTotalLength] = 0;
|
||||
this._inflate[kBuffers] = [];
|
||||
}
|
||||
|
||||
callback(null, data);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compress data.
|
||||
*
|
||||
* @param {Buffer} data Data to compress
|
||||
* @param {Boolean} fin Specifies whether or not this is the last fragment
|
||||
* @param {Function} callback Callback
|
||||
* @private
|
||||
*/
|
||||
_compress (data, fin, callback) {
|
||||
if (!data || data.length === 0) {
|
||||
process.nextTick(callback, null, EMPTY_BLOCK);
|
||||
return;
|
||||
}
|
||||
|
||||
const endpoint = this._isServer ? 'server' : 'client';
|
||||
|
||||
if (!this._deflate) {
|
||||
const key = `${endpoint}_max_window_bits`;
|
||||
const windowBits = typeof this.params[key] !== 'number'
|
||||
? zlib.Z_DEFAULT_WINDOWBITS
|
||||
: this.params[key];
|
||||
|
||||
this._deflate = zlib.createDeflateRaw({
|
||||
memLevel: this._options.memLevel,
|
||||
level: this._options.level,
|
||||
flush: zlib.Z_SYNC_FLUSH,
|
||||
windowBits
|
||||
});
|
||||
|
||||
this._deflate[kTotalLength] = 0;
|
||||
this._deflate[kBuffers] = [];
|
||||
|
||||
//
|
||||
// `zlib.DeflateRaw` emits an `'error'` event only when an attempt to use
|
||||
// it is made after it has already been closed. This cannot happen here,
|
||||
// so we only add a listener for the `'data'` event.
|
||||
//
|
||||
this._deflate.on('data', deflateOnData);
|
||||
}
|
||||
|
||||
this._deflate[kWriteInProgress] = true;
|
||||
|
||||
this._deflate.write(data);
|
||||
this._deflate.flush(zlib.Z_SYNC_FLUSH, () => {
|
||||
var data = bufferUtil.concat(
|
||||
this._deflate[kBuffers],
|
||||
this._deflate[kTotalLength]
|
||||
);
|
||||
|
||||
if (fin) data = data.slice(0, data.length - 4);
|
||||
|
||||
if (
|
||||
(fin && this.params[`${endpoint}_no_context_takeover`]) ||
|
||||
this._deflate[kPendingClose]
|
||||
) {
|
||||
this._deflate.close();
|
||||
this._deflate = null;
|
||||
} else {
|
||||
this._deflate[kWriteInProgress] = false;
|
||||
this._deflate[kTotalLength] = 0;
|
||||
this._deflate[kBuffers] = [];
|
||||
}
|
||||
|
||||
callback(null, data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PerMessageDeflate;
|
||||
|
||||
/**
|
||||
* The listener of the `zlib.DeflateRaw` stream `'data'` event.
|
||||
*
|
||||
* @param {Buffer} chunk A chunk of data
|
||||
* @private
|
||||
*/
|
||||
function deflateOnData (chunk) {
|
||||
this[kBuffers].push(chunk);
|
||||
this[kTotalLength] += chunk.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* The listener of the `zlib.InflateRaw` stream `'data'` event.
|
||||
*
|
||||
* @param {Buffer} chunk A chunk of data
|
||||
* @private
|
||||
*/
|
||||
function inflateOnData (chunk) {
|
||||
this[kTotalLength] += chunk.length;
|
||||
|
||||
if (
|
||||
this[kOwner]._maxPayload < 1 ||
|
||||
this[kTotalLength] <= this[kOwner]._maxPayload
|
||||
) {
|
||||
this[kBuffers].push(chunk);
|
||||
return;
|
||||
}
|
||||
|
||||
this[kError] = new Error('max payload size exceeded');
|
||||
this[kError].closeCode = 1009;
|
||||
this.removeListener('data', inflateOnData);
|
||||
this.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* The listener of the `zlib.InflateRaw` stream `'error'` event.
|
||||
*
|
||||
* @param {Error} err The emitted error
|
||||
* @private
|
||||
*/
|
||||
function inflateOnError (err) {
|
||||
//
|
||||
// There is no need to call `Zlib#close()` as the handle is automatically
|
||||
// closed when an error is emitted.
|
||||
//
|
||||
this[kOwner]._inflate = null;
|
||||
this[kCallback](err);
|
||||
}
|
||||
+553
@@ -0,0 +1,553 @@
|
||||
/*!
|
||||
* ws: a node.js websocket client
|
||||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const safeBuffer = require('safe-buffer');
|
||||
|
||||
const PerMessageDeflate = require('./PerMessageDeflate');
|
||||
const isValidUTF8 = require('./Validation');
|
||||
const bufferUtil = require('./BufferUtil');
|
||||
const ErrorCodes = require('./ErrorCodes');
|
||||
const constants = require('./Constants');
|
||||
|
||||
const Buffer = safeBuffer.Buffer;
|
||||
|
||||
const GET_INFO = 0;
|
||||
const GET_PAYLOAD_LENGTH_16 = 1;
|
||||
const GET_PAYLOAD_LENGTH_64 = 2;
|
||||
const GET_MASK = 3;
|
||||
const GET_DATA = 4;
|
||||
const INFLATING = 5;
|
||||
|
||||
/**
|
||||
* HyBi Receiver implementation.
|
||||
*/
|
||||
class Receiver {
|
||||
/**
|
||||
* Creates a Receiver instance.
|
||||
*
|
||||
* @param {Object} extensions An object containing the negotiated extensions
|
||||
* @param {Number} maxPayload The maximum allowed message length
|
||||
* @param {String} binaryType The type for binary data
|
||||
*/
|
||||
constructor (extensions, maxPayload, binaryType) {
|
||||
this._binaryType = binaryType || constants.BINARY_TYPES[0];
|
||||
this._extensions = extensions || {};
|
||||
this._maxPayload = maxPayload | 0;
|
||||
|
||||
this._bufferedBytes = 0;
|
||||
this._buffers = [];
|
||||
|
||||
this._compressed = false;
|
||||
this._payloadLength = 0;
|
||||
this._fragmented = 0;
|
||||
this._masked = false;
|
||||
this._fin = false;
|
||||
this._mask = null;
|
||||
this._opcode = 0;
|
||||
|
||||
this._totalPayloadLength = 0;
|
||||
this._messageLength = 0;
|
||||
this._fragments = [];
|
||||
|
||||
this._cleanupCallback = null;
|
||||
this._hadError = false;
|
||||
this._dead = false;
|
||||
this._loop = false;
|
||||
|
||||
this.onmessage = null;
|
||||
this.onclose = null;
|
||||
this.onerror = null;
|
||||
this.onping = null;
|
||||
this.onpong = null;
|
||||
|
||||
this._state = GET_INFO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes bytes from the available buffered data.
|
||||
*
|
||||
* @param {Number} bytes The number of bytes to consume
|
||||
* @return {Buffer} Consumed bytes
|
||||
* @private
|
||||
*/
|
||||
readBuffer (bytes) {
|
||||
var offset = 0;
|
||||
var dst;
|
||||
var l;
|
||||
|
||||
this._bufferedBytes -= bytes;
|
||||
|
||||
if (bytes === this._buffers[0].length) return this._buffers.shift();
|
||||
|
||||
if (bytes < this._buffers[0].length) {
|
||||
dst = this._buffers[0].slice(0, bytes);
|
||||
this._buffers[0] = this._buffers[0].slice(bytes);
|
||||
return dst;
|
||||
}
|
||||
|
||||
dst = Buffer.allocUnsafe(bytes);
|
||||
|
||||
while (bytes > 0) {
|
||||
l = this._buffers[0].length;
|
||||
|
||||
if (bytes >= l) {
|
||||
this._buffers[0].copy(dst, offset);
|
||||
offset += l;
|
||||
this._buffers.shift();
|
||||
} else {
|
||||
this._buffers[0].copy(dst, offset, 0, bytes);
|
||||
this._buffers[0] = this._buffers[0].slice(bytes);
|
||||
}
|
||||
|
||||
bytes -= l;
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the number of buffered bytes is bigger or equal than `n` and
|
||||
* calls `cleanup` if necessary.
|
||||
*
|
||||
* @param {Number} n The number of bytes to check against
|
||||
* @return {Boolean} `true` if `bufferedBytes >= n`, else `false`
|
||||
* @private
|
||||
*/
|
||||
hasBufferedBytes (n) {
|
||||
if (this._bufferedBytes >= n) return true;
|
||||
|
||||
this._loop = false;
|
||||
if (this._dead) this.cleanup(this._cleanupCallback);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new data to the parser.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
add (data) {
|
||||
if (this._dead) return;
|
||||
|
||||
this._bufferedBytes += data.length;
|
||||
this._buffers.push(data);
|
||||
this.startLoop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the parsing loop.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
startLoop () {
|
||||
this._loop = true;
|
||||
|
||||
while (this._loop) {
|
||||
switch (this._state) {
|
||||
case GET_INFO:
|
||||
this.getInfo();
|
||||
break;
|
||||
case GET_PAYLOAD_LENGTH_16:
|
||||
this.getPayloadLength16();
|
||||
break;
|
||||
case GET_PAYLOAD_LENGTH_64:
|
||||
this.getPayloadLength64();
|
||||
break;
|
||||
case GET_MASK:
|
||||
this.getMask();
|
||||
break;
|
||||
case GET_DATA:
|
||||
this.getData();
|
||||
break;
|
||||
default: // `INFLATING`
|
||||
this._loop = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the first two bytes of a frame.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
getInfo () {
|
||||
if (!this.hasBufferedBytes(2)) return;
|
||||
|
||||
const buf = this.readBuffer(2);
|
||||
|
||||
if ((buf[0] & 0x30) !== 0x00) {
|
||||
this.error(new Error('RSV2 and RSV3 must be clear'), 1002);
|
||||
return;
|
||||
}
|
||||
|
||||
const compressed = (buf[0] & 0x40) === 0x40;
|
||||
|
||||
if (compressed && !this._extensions[PerMessageDeflate.extensionName]) {
|
||||
this.error(new Error('RSV1 must be clear'), 1002);
|
||||
return;
|
||||
}
|
||||
|
||||
this._fin = (buf[0] & 0x80) === 0x80;
|
||||
this._opcode = buf[0] & 0x0f;
|
||||
this._payloadLength = buf[1] & 0x7f;
|
||||
|
||||
if (this._opcode === 0x00) {
|
||||
if (compressed) {
|
||||
this.error(new Error('RSV1 must be clear'), 1002);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._fragmented) {
|
||||
this.error(new Error(`invalid opcode: ${this._opcode}`), 1002);
|
||||
return;
|
||||
} else {
|
||||
this._opcode = this._fragmented;
|
||||
}
|
||||
} else if (this._opcode === 0x01 || this._opcode === 0x02) {
|
||||
if (this._fragmented) {
|
||||
this.error(new Error(`invalid opcode: ${this._opcode}`), 1002);
|
||||
return;
|
||||
}
|
||||
|
||||
this._compressed = compressed;
|
||||
} else if (this._opcode > 0x07 && this._opcode < 0x0b) {
|
||||
if (!this._fin) {
|
||||
this.error(new Error('FIN must be set'), 1002);
|
||||
return;
|
||||
}
|
||||
|
||||
if (compressed) {
|
||||
this.error(new Error('RSV1 must be clear'), 1002);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._payloadLength > 0x7d) {
|
||||
this.error(new Error('invalid payload length'), 1002);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this.error(new Error(`invalid opcode: ${this._opcode}`), 1002);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._fin && !this._fragmented) this._fragmented = this._opcode;
|
||||
|
||||
this._masked = (buf[1] & 0x80) === 0x80;
|
||||
|
||||
if (this._payloadLength === 126) this._state = GET_PAYLOAD_LENGTH_16;
|
||||
else if (this._payloadLength === 127) this._state = GET_PAYLOAD_LENGTH_64;
|
||||
else this.haveLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets extended payload length (7+16).
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
getPayloadLength16 () {
|
||||
if (!this.hasBufferedBytes(2)) return;
|
||||
|
||||
this._payloadLength = this.readBuffer(2).readUInt16BE(0, true);
|
||||
this.haveLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets extended payload length (7+64).
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
getPayloadLength64 () {
|
||||
if (!this.hasBufferedBytes(8)) return;
|
||||
|
||||
const buf = this.readBuffer(8);
|
||||
const num = buf.readUInt32BE(0, true);
|
||||
|
||||
//
|
||||
// The maximum safe integer in JavaScript is 2^53 - 1. An error is returned
|
||||
// if payload length is greater than this number.
|
||||
//
|
||||
if (num > Math.pow(2, 53 - 32) - 1) {
|
||||
this.error(new Error('max payload size exceeded'), 1009);
|
||||
return;
|
||||
}
|
||||
|
||||
this._payloadLength = (num * Math.pow(2, 32)) + buf.readUInt32BE(4, true);
|
||||
this.haveLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Payload length has been read.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
haveLength () {
|
||||
if (this._opcode < 0x08 && this.maxPayloadExceeded(this._payloadLength)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._masked) this._state = GET_MASK;
|
||||
else this._state = GET_DATA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads mask bytes.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
getMask () {
|
||||
if (!this.hasBufferedBytes(4)) return;
|
||||
|
||||
this._mask = this.readBuffer(4);
|
||||
this._state = GET_DATA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads data bytes.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
getData () {
|
||||
var data = constants.EMPTY_BUFFER;
|
||||
|
||||
if (this._payloadLength) {
|
||||
if (!this.hasBufferedBytes(this._payloadLength)) return;
|
||||
|
||||
data = this.readBuffer(this._payloadLength);
|
||||
if (this._masked) bufferUtil.unmask(data, this._mask);
|
||||
}
|
||||
|
||||
if (this._opcode > 0x07) {
|
||||
this.controlMessage(data);
|
||||
} else if (this._compressed) {
|
||||
this._state = INFLATING;
|
||||
this.decompress(data);
|
||||
} else if (this.pushFragment(data)) {
|
||||
this.dataMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses data.
|
||||
*
|
||||
* @param {Buffer} data Compressed data
|
||||
* @private
|
||||
*/
|
||||
decompress (data) {
|
||||
const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
|
||||
|
||||
perMessageDeflate.decompress(data, this._fin, (err, buf) => {
|
||||
if (err) {
|
||||
this.error(err, err.closeCode === 1009 ? 1009 : 1007);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.pushFragment(buf)) this.dataMessage();
|
||||
this.startLoop();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a data message.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
dataMessage () {
|
||||
if (this._fin) {
|
||||
const messageLength = this._messageLength;
|
||||
const fragments = this._fragments;
|
||||
|
||||
this._totalPayloadLength = 0;
|
||||
this._messageLength = 0;
|
||||
this._fragmented = 0;
|
||||
this._fragments = [];
|
||||
|
||||
if (this._opcode === 2) {
|
||||
var data;
|
||||
|
||||
if (this._binaryType === 'nodebuffer') {
|
||||
data = toBuffer(fragments, messageLength);
|
||||
} else if (this._binaryType === 'arraybuffer') {
|
||||
data = toArrayBuffer(toBuffer(fragments, messageLength));
|
||||
} else {
|
||||
data = fragments;
|
||||
}
|
||||
|
||||
this.onmessage(data);
|
||||
} else {
|
||||
const buf = toBuffer(fragments, messageLength);
|
||||
|
||||
if (!isValidUTF8(buf)) {
|
||||
this.error(new Error('invalid utf8 sequence'), 1007);
|
||||
return;
|
||||
}
|
||||
|
||||
this.onmessage(buf.toString());
|
||||
}
|
||||
}
|
||||
|
||||
this._state = GET_INFO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a control message.
|
||||
*
|
||||
* @param {Buffer} data Data to handle
|
||||
* @private
|
||||
*/
|
||||
controlMessage (data) {
|
||||
if (this._opcode === 0x08) {
|
||||
if (data.length === 0) {
|
||||
this.onclose(1000, '');
|
||||
this._loop = false;
|
||||
this.cleanup(this._cleanupCallback);
|
||||
} else if (data.length === 1) {
|
||||
this.error(new Error('invalid payload length'), 1002);
|
||||
} else {
|
||||
const code = data.readUInt16BE(0, true);
|
||||
|
||||
if (!ErrorCodes.isValidErrorCode(code)) {
|
||||
this.error(new Error(`invalid status code: ${code}`), 1002);
|
||||
return;
|
||||
}
|
||||
|
||||
const buf = data.slice(2);
|
||||
|
||||
if (!isValidUTF8(buf)) {
|
||||
this.error(new Error('invalid utf8 sequence'), 1007);
|
||||
return;
|
||||
}
|
||||
|
||||
this.onclose(code, buf.toString());
|
||||
this._loop = false;
|
||||
this.cleanup(this._cleanupCallback);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._opcode === 0x09) this.onping(data);
|
||||
else this.onpong(data);
|
||||
|
||||
this._state = GET_INFO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an error.
|
||||
*
|
||||
* @param {Error} err The error
|
||||
* @param {Number} code Close code
|
||||
* @private
|
||||
*/
|
||||
error (err, code) {
|
||||
this.onerror(err, code);
|
||||
this._hadError = true;
|
||||
this._loop = false;
|
||||
this.cleanup(this._cleanupCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks payload size, disconnects socket when it exceeds `maxPayload`.
|
||||
*
|
||||
* @param {Number} length Payload length
|
||||
* @private
|
||||
*/
|
||||
maxPayloadExceeded (length) {
|
||||
if (length === 0 || this._maxPayload < 1) return false;
|
||||
|
||||
const fullLength = this._totalPayloadLength + length;
|
||||
|
||||
if (fullLength <= this._maxPayload) {
|
||||
this._totalPayloadLength = fullLength;
|
||||
return false;
|
||||
}
|
||||
|
||||
this.error(new Error('max payload size exceeded'), 1009);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a fragment in the fragments array after checking that the sum of
|
||||
* fragment lengths does not exceed `maxPayload`.
|
||||
*
|
||||
* @param {Buffer} fragment The fragment to add
|
||||
* @return {Boolean} `true` if `maxPayload` is not exceeded, else `false`
|
||||
* @private
|
||||
*/
|
||||
pushFragment (fragment) {
|
||||
if (fragment.length === 0) return true;
|
||||
|
||||
const totalLength = this._messageLength + fragment.length;
|
||||
|
||||
if (this._maxPayload < 1 || totalLength <= this._maxPayload) {
|
||||
this._messageLength = totalLength;
|
||||
this._fragments.push(fragment);
|
||||
return true;
|
||||
}
|
||||
|
||||
this.error(new Error('max payload size exceeded'), 1009);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases resources used by the receiver.
|
||||
*
|
||||
* @param {Function} cb Callback
|
||||
* @public
|
||||
*/
|
||||
cleanup (cb) {
|
||||
this._dead = true;
|
||||
|
||||
if (!this._hadError && (this._loop || this._state === INFLATING)) {
|
||||
this._cleanupCallback = cb;
|
||||
} else {
|
||||
this._extensions = null;
|
||||
this._fragments = null;
|
||||
this._buffers = null;
|
||||
this._mask = null;
|
||||
|
||||
this._cleanupCallback = null;
|
||||
this.onmessage = null;
|
||||
this.onclose = null;
|
||||
this.onerror = null;
|
||||
this.onping = null;
|
||||
this.onpong = null;
|
||||
|
||||
if (cb) cb();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Receiver;
|
||||
|
||||
/**
|
||||
* Makes a buffer from a list of fragments.
|
||||
*
|
||||
* @param {Buffer[]} fragments The list of fragments composing the message
|
||||
* @param {Number} messageLength The length of the message
|
||||
* @return {Buffer}
|
||||
* @private
|
||||
*/
|
||||
function toBuffer (fragments, messageLength) {
|
||||
if (fragments.length === 1) return fragments[0];
|
||||
if (fragments.length > 1) return bufferUtil.concat(fragments, messageLength);
|
||||
return constants.EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a buffer to an `ArrayBuffer`.
|
||||
*
|
||||
* @param {Buffer} The buffer to convert
|
||||
* @return {ArrayBuffer} Converted buffer
|
||||
*/
|
||||
function toArrayBuffer (buf) {
|
||||
if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {
|
||||
return buf.buffer;
|
||||
}
|
||||
|
||||
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
||||
}
|
||||
+412
@@ -0,0 +1,412 @@
|
||||
/*!
|
||||
* ws: a node.js websocket client
|
||||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const safeBuffer = require('safe-buffer');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const PerMessageDeflate = require('./PerMessageDeflate');
|
||||
const bufferUtil = require('./BufferUtil');
|
||||
const ErrorCodes = require('./ErrorCodes');
|
||||
const constants = require('./Constants');
|
||||
|
||||
const Buffer = safeBuffer.Buffer;
|
||||
|
||||
/**
|
||||
* HyBi Sender implementation.
|
||||
*/
|
||||
class Sender {
|
||||
/**
|
||||
* Creates a Sender instance.
|
||||
*
|
||||
* @param {net.Socket} socket The connection socket
|
||||
* @param {Object} extensions An object containing the negotiated extensions
|
||||
*/
|
||||
constructor (socket, extensions) {
|
||||
this._extensions = extensions || {};
|
||||
this._socket = socket;
|
||||
|
||||
this._firstFragment = true;
|
||||
this._compress = false;
|
||||
|
||||
this._bufferedBytes = 0;
|
||||
this._deflating = false;
|
||||
this._queue = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Frames a piece of data according to the HyBi WebSocket protocol.
|
||||
*
|
||||
* @param {Buffer} data The data to frame
|
||||
* @param {Object} options Options object
|
||||
* @param {Number} options.opcode The opcode
|
||||
* @param {Boolean} options.readOnly Specifies whether `data` can be modified
|
||||
* @param {Boolean} options.fin Specifies whether or not to set the FIN bit
|
||||
* @param {Boolean} options.mask Specifies whether or not to mask `data`
|
||||
* @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit
|
||||
* @return {Buffer[]} The framed data as a list of `Buffer` instances
|
||||
* @public
|
||||
*/
|
||||
static frame (data, options) {
|
||||
const merge = data.length < 1024 || (options.mask && options.readOnly);
|
||||
var offset = options.mask ? 6 : 2;
|
||||
var payloadLength = data.length;
|
||||
|
||||
if (data.length >= 65536) {
|
||||
offset += 8;
|
||||
payloadLength = 127;
|
||||
} else if (data.length > 125) {
|
||||
offset += 2;
|
||||
payloadLength = 126;
|
||||
}
|
||||
|
||||
const target = Buffer.allocUnsafe(merge ? data.length + offset : offset);
|
||||
|
||||
target[0] = options.fin ? options.opcode | 0x80 : options.opcode;
|
||||
if (options.rsv1) target[0] |= 0x40;
|
||||
|
||||
if (payloadLength === 126) {
|
||||
target.writeUInt16BE(data.length, 2, true);
|
||||
} else if (payloadLength === 127) {
|
||||
target.writeUInt32BE(0, 2, true);
|
||||
target.writeUInt32BE(data.length, 6, true);
|
||||
}
|
||||
|
||||
if (!options.mask) {
|
||||
target[1] = payloadLength;
|
||||
if (merge) {
|
||||
data.copy(target, offset);
|
||||
return [target];
|
||||
}
|
||||
|
||||
return [target, data];
|
||||
}
|
||||
|
||||
const mask = crypto.randomBytes(4);
|
||||
|
||||
target[1] = payloadLength | 0x80;
|
||||
target[offset - 4] = mask[0];
|
||||
target[offset - 3] = mask[1];
|
||||
target[offset - 2] = mask[2];
|
||||
target[offset - 1] = mask[3];
|
||||
|
||||
if (merge) {
|
||||
bufferUtil.mask(data, mask, target, offset, data.length);
|
||||
return [target];
|
||||
}
|
||||
|
||||
bufferUtil.mask(data, mask, data, 0, data.length);
|
||||
return [target, data];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a close message to the other peer.
|
||||
*
|
||||
* @param {(Number|undefined)} code The status code component of the body
|
||||
* @param {String} data The message component of the body
|
||||
* @param {Boolean} mask Specifies whether or not to mask the message
|
||||
* @param {Function} cb Callback
|
||||
* @public
|
||||
*/
|
||||
close (code, data, mask, cb) {
|
||||
var buf;
|
||||
|
||||
if (code === undefined) {
|
||||
code = 1000;
|
||||
} else if (typeof code !== 'number' || !ErrorCodes.isValidErrorCode(code)) {
|
||||
throw new Error('first argument must be a valid error code number');
|
||||
}
|
||||
|
||||
if (data === undefined || data === '') {
|
||||
if (code === 1000) {
|
||||
buf = constants.EMPTY_BUFFER;
|
||||
} else {
|
||||
buf = Buffer.allocUnsafe(2);
|
||||
buf.writeUInt16BE(code, 0, true);
|
||||
}
|
||||
} else {
|
||||
buf = Buffer.allocUnsafe(2 + Buffer.byteLength(data));
|
||||
buf.writeUInt16BE(code, 0, true);
|
||||
buf.write(data, 2);
|
||||
}
|
||||
|
||||
if (this._deflating) {
|
||||
this.enqueue([this.doClose, buf, mask, cb]);
|
||||
} else {
|
||||
this.doClose(buf, mask, cb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Frames and sends a close message.
|
||||
*
|
||||
* @param {Buffer} data The message to send
|
||||
* @param {Boolean} mask Specifies whether or not to mask `data`
|
||||
* @param {Function} cb Callback
|
||||
* @private
|
||||
*/
|
||||
doClose (data, mask, cb) {
|
||||
this.sendFrame(Sender.frame(data, {
|
||||
fin: true,
|
||||
rsv1: false,
|
||||
opcode: 0x08,
|
||||
mask,
|
||||
readOnly: false
|
||||
}), cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a ping message to the other peer.
|
||||
*
|
||||
* @param {*} data The message to send
|
||||
* @param {Boolean} mask Specifies whether or not to mask `data`
|
||||
* @public
|
||||
*/
|
||||
ping (data, mask) {
|
||||
var readOnly = true;
|
||||
|
||||
if (!Buffer.isBuffer(data)) {
|
||||
if (data instanceof ArrayBuffer) {
|
||||
data = Buffer.from(data);
|
||||
} else if (ArrayBuffer.isView(data)) {
|
||||
data = viewToBuffer(data);
|
||||
} else {
|
||||
data = Buffer.from(data);
|
||||
readOnly = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this._deflating) {
|
||||
this.enqueue([this.doPing, data, mask, readOnly]);
|
||||
} else {
|
||||
this.doPing(data, mask, readOnly);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Frames and sends a ping message.
|
||||
*
|
||||
* @param {*} data The message to send
|
||||
* @param {Boolean} mask Specifies whether or not to mask `data`
|
||||
* @param {Boolean} readOnly Specifies whether `data` can be modified
|
||||
* @private
|
||||
*/
|
||||
doPing (data, mask, readOnly) {
|
||||
this.sendFrame(Sender.frame(data, {
|
||||
fin: true,
|
||||
rsv1: false,
|
||||
opcode: 0x09,
|
||||
mask,
|
||||
readOnly
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a pong message to the other peer.
|
||||
*
|
||||
* @param {*} data The message to send
|
||||
* @param {Boolean} mask Specifies whether or not to mask `data`
|
||||
* @public
|
||||
*/
|
||||
pong (data, mask) {
|
||||
var readOnly = true;
|
||||
|
||||
if (!Buffer.isBuffer(data)) {
|
||||
if (data instanceof ArrayBuffer) {
|
||||
data = Buffer.from(data);
|
||||
} else if (ArrayBuffer.isView(data)) {
|
||||
data = viewToBuffer(data);
|
||||
} else {
|
||||
data = Buffer.from(data);
|
||||
readOnly = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this._deflating) {
|
||||
this.enqueue([this.doPong, data, mask, readOnly]);
|
||||
} else {
|
||||
this.doPong(data, mask, readOnly);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Frames and sends a pong message.
|
||||
*
|
||||
* @param {*} data The message to send
|
||||
* @param {Boolean} mask Specifies whether or not to mask `data`
|
||||
* @param {Boolean} readOnly Specifies whether `data` can be modified
|
||||
* @private
|
||||
*/
|
||||
doPong (data, mask, readOnly) {
|
||||
this.sendFrame(Sender.frame(data, {
|
||||
fin: true,
|
||||
rsv1: false,
|
||||
opcode: 0x0a,
|
||||
mask,
|
||||
readOnly
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a data message to the other peer.
|
||||
*
|
||||
* @param {*} data The message to send
|
||||
* @param {Object} options Options object
|
||||
* @param {Boolean} options.compress Specifies whether or not to compress `data`
|
||||
* @param {Boolean} options.binary Specifies whether `data` is binary or text
|
||||
* @param {Boolean} options.fin Specifies whether the fragment is the last one
|
||||
* @param {Boolean} options.mask Specifies whether or not to mask `data`
|
||||
* @param {Function} cb Callback
|
||||
* @public
|
||||
*/
|
||||
send (data, options, cb) {
|
||||
var opcode = options.binary ? 2 : 1;
|
||||
var rsv1 = options.compress;
|
||||
var readOnly = true;
|
||||
|
||||
if (!Buffer.isBuffer(data)) {
|
||||
if (data instanceof ArrayBuffer) {
|
||||
data = Buffer.from(data);
|
||||
} else if (ArrayBuffer.isView(data)) {
|
||||
data = viewToBuffer(data);
|
||||
} else {
|
||||
data = Buffer.from(data);
|
||||
readOnly = false;
|
||||
}
|
||||
}
|
||||
|
||||
const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
|
||||
|
||||
if (this._firstFragment) {
|
||||
this._firstFragment = false;
|
||||
if (rsv1 && perMessageDeflate) {
|
||||
rsv1 = data.length >= perMessageDeflate._threshold;
|
||||
}
|
||||
this._compress = rsv1;
|
||||
} else {
|
||||
rsv1 = false;
|
||||
opcode = 0;
|
||||
}
|
||||
|
||||
if (options.fin) this._firstFragment = true;
|
||||
|
||||
if (perMessageDeflate) {
|
||||
const opts = {
|
||||
fin: options.fin,
|
||||
rsv1,
|
||||
opcode,
|
||||
mask: options.mask,
|
||||
readOnly
|
||||
};
|
||||
|
||||
if (this._deflating) {
|
||||
this.enqueue([this.dispatch, data, this._compress, opts, cb]);
|
||||
} else {
|
||||
this.dispatch(data, this._compress, opts, cb);
|
||||
}
|
||||
} else {
|
||||
this.sendFrame(Sender.frame(data, {
|
||||
fin: options.fin,
|
||||
rsv1: false,
|
||||
opcode,
|
||||
mask: options.mask,
|
||||
readOnly
|
||||
}), cb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches a data message.
|
||||
*
|
||||
* @param {Buffer} data The message to send
|
||||
* @param {Boolean} compress Specifies whether or not to compress `data`
|
||||
* @param {Object} options Options object
|
||||
* @param {Number} options.opcode The opcode
|
||||
* @param {Boolean} options.readOnly Specifies whether `data` can be modified
|
||||
* @param {Boolean} options.fin Specifies whether or not to set the FIN bit
|
||||
* @param {Boolean} options.mask Specifies whether or not to mask `data`
|
||||
* @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit
|
||||
* @param {Function} cb Callback
|
||||
* @private
|
||||
*/
|
||||
dispatch (data, compress, options, cb) {
|
||||
if (!compress) {
|
||||
this.sendFrame(Sender.frame(data, options), cb);
|
||||
return;
|
||||
}
|
||||
|
||||
const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
|
||||
|
||||
this._deflating = true;
|
||||
perMessageDeflate.compress(data, options.fin, (_, buf) => {
|
||||
options.readOnly = false;
|
||||
this.sendFrame(Sender.frame(buf, options), cb);
|
||||
this._deflating = false;
|
||||
this.dequeue();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes queued send operations.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
dequeue () {
|
||||
while (!this._deflating && this._queue.length) {
|
||||
const params = this._queue.shift();
|
||||
|
||||
this._bufferedBytes -= params[1].length;
|
||||
params[0].apply(this, params.slice(1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues a send operation.
|
||||
*
|
||||
* @param {Array} params Send operation parameters.
|
||||
* @private
|
||||
*/
|
||||
enqueue (params) {
|
||||
this._bufferedBytes += params[1].length;
|
||||
this._queue.push(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a frame.
|
||||
*
|
||||
* @param {Buffer[]} list The frame to send
|
||||
* @param {Function} cb Callback
|
||||
* @private
|
||||
*/
|
||||
sendFrame (list, cb) {
|
||||
if (list.length === 2) {
|
||||
this._socket.write(list[0]);
|
||||
this._socket.write(list[1], cb);
|
||||
} else {
|
||||
this._socket.write(list[0], cb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Sender;
|
||||
|
||||
/**
|
||||
* Converts an `ArrayBuffer` view into a buffer.
|
||||
*
|
||||
* @param {(DataView|TypedArray)} view The view to convert
|
||||
* @return {Buffer} Converted view
|
||||
* @private
|
||||
*/
|
||||
function viewToBuffer (view) {
|
||||
const buf = Buffer.from(view.buffer);
|
||||
|
||||
if (view.byteLength !== view.buffer.byteLength) {
|
||||
return buf.slice(view.byteOffset, view.byteOffset + view.byteLength);
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/*!
|
||||
* ws: a node.js websocket client
|
||||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
try {
|
||||
const isValidUTF8 = require('utf-8-validate');
|
||||
|
||||
module.exports = typeof isValidUTF8 === 'object'
|
||||
? isValidUTF8.Validation.isValidUTF8 // utf-8-validate@<3.0.0
|
||||
: isValidUTF8;
|
||||
} catch (e) /* istanbul ignore next */ {
|
||||
module.exports = () => true;
|
||||
}
|
||||
+717
@@ -0,0 +1,717 @@
|
||||
/*!
|
||||
* ws: a node.js websocket client
|
||||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const EventEmitter = require('events');
|
||||
const crypto = require('crypto');
|
||||
const Ultron = require('ultron');
|
||||
const https = require('https');
|
||||
const http = require('http');
|
||||
const url = require('url');
|
||||
|
||||
const PerMessageDeflate = require('./PerMessageDeflate');
|
||||
const EventTarget = require('./EventTarget');
|
||||
const Extensions = require('./Extensions');
|
||||
const constants = require('./Constants');
|
||||
const Receiver = require('./Receiver');
|
||||
const Sender = require('./Sender');
|
||||
|
||||
const protocolVersions = [8, 13];
|
||||
const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly.
|
||||
|
||||
/**
|
||||
* Class representing a WebSocket.
|
||||
*
|
||||
* @extends EventEmitter
|
||||
*/
|
||||
class WebSocket extends EventEmitter {
|
||||
/**
|
||||
* Create a new `WebSocket`.
|
||||
*
|
||||
* @param {String} address The URL to which to connect
|
||||
* @param {(String|String[])} protocols The subprotocols
|
||||
* @param {Object} options Connection options
|
||||
*/
|
||||
constructor (address, protocols, options) {
|
||||
super();
|
||||
|
||||
if (!protocols) {
|
||||
protocols = [];
|
||||
} else if (typeof protocols === 'string') {
|
||||
protocols = [protocols];
|
||||
} else if (!Array.isArray(protocols)) {
|
||||
options = protocols;
|
||||
protocols = [];
|
||||
}
|
||||
|
||||
this.readyState = WebSocket.CONNECTING;
|
||||
this.bytesReceived = 0;
|
||||
this.extensions = {};
|
||||
this.protocol = '';
|
||||
|
||||
this._binaryType = constants.BINARY_TYPES[0];
|
||||
this._finalize = this.finalize.bind(this);
|
||||
this._closeFrameReceived = false;
|
||||
this._closeFrameSent = false;
|
||||
this._closeMessage = '';
|
||||
this._closeTimer = null;
|
||||
this._finalized = false;
|
||||
this._closeCode = 1006;
|
||||
this._receiver = null;
|
||||
this._sender = null;
|
||||
this._socket = null;
|
||||
this._ultron = null;
|
||||
|
||||
if (Array.isArray(address)) {
|
||||
initAsServerClient.call(this, address[0], address[1], options);
|
||||
} else {
|
||||
initAsClient.call(this, address, protocols, options);
|
||||
}
|
||||
}
|
||||
|
||||
get CONNECTING () { return WebSocket.CONNECTING; }
|
||||
get CLOSING () { return WebSocket.CLOSING; }
|
||||
get CLOSED () { return WebSocket.CLOSED; }
|
||||
get OPEN () { return WebSocket.OPEN; }
|
||||
|
||||
/**
|
||||
* @type {Number}
|
||||
*/
|
||||
get bufferedAmount () {
|
||||
var amount = 0;
|
||||
|
||||
if (this._socket) {
|
||||
amount = this._socket.bufferSize + this._sender._bufferedBytes;
|
||||
}
|
||||
return amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* This deviates from the WHATWG interface since ws doesn't support the required
|
||||
* default "blob" type (instead we define a custom "nodebuffer" type).
|
||||
*
|
||||
* @type {String}
|
||||
*/
|
||||
get binaryType () {
|
||||
return this._binaryType;
|
||||
}
|
||||
|
||||
set binaryType (type) {
|
||||
if (constants.BINARY_TYPES.indexOf(type) < 0) return;
|
||||
|
||||
this._binaryType = type;
|
||||
|
||||
//
|
||||
// Allow to change `binaryType` on the fly.
|
||||
//
|
||||
if (this._receiver) this._receiver._binaryType = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the socket and the internal resources.
|
||||
*
|
||||
* @param {net.Socket} socket The network socket between the server and client
|
||||
* @param {Buffer} head The first packet of the upgraded stream
|
||||
* @private
|
||||
*/
|
||||
setSocket (socket, head) {
|
||||
socket.setTimeout(0);
|
||||
socket.setNoDelay();
|
||||
|
||||
this._receiver = new Receiver(this.extensions, this._maxPayload, this.binaryType);
|
||||
this._sender = new Sender(socket, this.extensions);
|
||||
this._ultron = new Ultron(socket);
|
||||
this._socket = socket;
|
||||
|
||||
this._ultron.on('close', this._finalize);
|
||||
this._ultron.on('error', this._finalize);
|
||||
this._ultron.on('end', this._finalize);
|
||||
|
||||
if (head.length > 0) socket.unshift(head);
|
||||
|
||||
this._ultron.on('data', (data) => {
|
||||
this.bytesReceived += data.length;
|
||||
this._receiver.add(data);
|
||||
});
|
||||
|
||||
this._receiver.onmessage = (data) => this.emit('message', data);
|
||||
this._receiver.onping = (data) => {
|
||||
this.pong(data, !this._isServer, true);
|
||||
this.emit('ping', data);
|
||||
};
|
||||
this._receiver.onpong = (data) => this.emit('pong', data);
|
||||
this._receiver.onclose = (code, reason) => {
|
||||
this._closeFrameReceived = true;
|
||||
this._closeMessage = reason;
|
||||
this._closeCode = code;
|
||||
if (!this._finalized) this.close(code, reason);
|
||||
};
|
||||
this._receiver.onerror = (error, code) => {
|
||||
this._closeMessage = '';
|
||||
this._closeCode = code;
|
||||
|
||||
//
|
||||
// Ensure that the error is emitted even if `WebSocket#finalize()` has
|
||||
// already been called.
|
||||
//
|
||||
this.readyState = WebSocket.CLOSING;
|
||||
this.emit('error', error);
|
||||
this.finalize(true);
|
||||
};
|
||||
|
||||
this.readyState = WebSocket.OPEN;
|
||||
this.emit('open');
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up and release internal resources.
|
||||
*
|
||||
* @param {(Boolean|Error)} error Indicates whether or not an error occurred
|
||||
* @private
|
||||
*/
|
||||
finalize (error) {
|
||||
if (this._finalized) return;
|
||||
|
||||
this.readyState = WebSocket.CLOSING;
|
||||
this._finalized = true;
|
||||
|
||||
if (typeof error === 'object') this.emit('error', error);
|
||||
if (!this._socket) return this.emitClose();
|
||||
|
||||
clearTimeout(this._closeTimer);
|
||||
this._closeTimer = null;
|
||||
|
||||
this._ultron.destroy();
|
||||
this._ultron = null;
|
||||
|
||||
this._socket.on('error', constants.NOOP);
|
||||
|
||||
if (!error) this._socket.end();
|
||||
else this._socket.destroy();
|
||||
|
||||
this._socket = null;
|
||||
this._sender = null;
|
||||
|
||||
this._receiver.cleanup(() => this.emitClose());
|
||||
this._receiver = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit the `close` event.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
emitClose () {
|
||||
this.readyState = WebSocket.CLOSED;
|
||||
|
||||
this.emit('close', this._closeCode, this._closeMessage);
|
||||
|
||||
if (this.extensions[PerMessageDeflate.extensionName]) {
|
||||
this.extensions[PerMessageDeflate.extensionName].cleanup();
|
||||
}
|
||||
|
||||
this.extensions = null;
|
||||
|
||||
this.removeAllListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause the socket stream.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
pause () {
|
||||
if (this.readyState !== WebSocket.OPEN) throw new Error('not opened');
|
||||
|
||||
this._socket.pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume the socket stream
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
resume () {
|
||||
if (this.readyState !== WebSocket.OPEN) throw new Error('not opened');
|
||||
|
||||
this._socket.resume();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a closing handshake.
|
||||
*
|
||||
* +----------+ +-----------+ +----------+
|
||||
* + - - -|ws.close()|---->|close frame|-->|ws.close()|- - - -
|
||||
* +----------+ +-----------+ +----------+ |
|
||||
* | +----------+ +-----------+ |
|
||||
* |ws.close()|<----|close frame|<--------+ |
|
||||
* +----------+ +-----------+ |
|
||||
* CLOSING | +---+ | CLOSING
|
||||
* | +---|fin|<------------+
|
||||
* | | | +---+ |
|
||||
* | | +---+ +-------------+
|
||||
* | +----------+-->|fin|----->|ws.finalize()| - - +
|
||||
* | +---+ +-------------+
|
||||
* | +-------------+ |
|
||||
* - - -|ws.finalize()|<--+
|
||||
* +-------------+
|
||||
*
|
||||
* @param {Number} code Status code explaining why the connection is closing
|
||||
* @param {String} data A string explaining why the connection is closing
|
||||
* @public
|
||||
*/
|
||||
close (code, data) {
|
||||
if (this.readyState === WebSocket.CLOSED) return;
|
||||
if (this.readyState === WebSocket.CONNECTING) {
|
||||
this._req.abort();
|
||||
this.finalize(new Error('closed before the connection is established'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.readyState === WebSocket.CLOSING) {
|
||||
if (this._closeFrameSent && this._closeFrameReceived) this._socket.end();
|
||||
return;
|
||||
}
|
||||
|
||||
this.readyState = WebSocket.CLOSING;
|
||||
this._sender.close(code, data, !this._isServer, (err) => {
|
||||
//
|
||||
// This error is handled by the `'error'` listener on the socket. We only
|
||||
// want to know if the close frame has been sent here.
|
||||
//
|
||||
if (err) return;
|
||||
|
||||
this._closeFrameSent = true;
|
||||
|
||||
if (!this._finalized) {
|
||||
if (this._closeFrameReceived) this._socket.end();
|
||||
|
||||
//
|
||||
// Ensure that the connection is cleaned up even when the closing
|
||||
// handshake fails.
|
||||
//
|
||||
this._closeTimer = setTimeout(this._finalize, closeTimeout, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a ping message.
|
||||
*
|
||||
* @param {*} data The message to send
|
||||
* @param {Boolean} mask Indicates whether or not to mask `data`
|
||||
* @param {Boolean} failSilently Indicates whether or not to throw if `readyState` isn't `OPEN`
|
||||
* @public
|
||||
*/
|
||||
ping (data, mask, failSilently) {
|
||||
if (this.readyState !== WebSocket.OPEN) {
|
||||
if (failSilently) return;
|
||||
throw new Error('not opened');
|
||||
}
|
||||
|
||||
if (typeof data === 'number') data = data.toString();
|
||||
if (mask === undefined) mask = !this._isServer;
|
||||
this._sender.ping(data || constants.EMPTY_BUFFER, mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a pong message.
|
||||
*
|
||||
* @param {*} data The message to send
|
||||
* @param {Boolean} mask Indicates whether or not to mask `data`
|
||||
* @param {Boolean} failSilently Indicates whether or not to throw if `readyState` isn't `OPEN`
|
||||
* @public
|
||||
*/
|
||||
pong (data, mask, failSilently) {
|
||||
if (this.readyState !== WebSocket.OPEN) {
|
||||
if (failSilently) return;
|
||||
throw new Error('not opened');
|
||||
}
|
||||
|
||||
if (typeof data === 'number') data = data.toString();
|
||||
if (mask === undefined) mask = !this._isServer;
|
||||
this._sender.pong(data || constants.EMPTY_BUFFER, mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a data message.
|
||||
*
|
||||
* @param {*} data The message to send
|
||||
* @param {Object} options Options object
|
||||
* @param {Boolean} options.compress Specifies whether or not to compress `data`
|
||||
* @param {Boolean} options.binary Specifies whether `data` is binary or text
|
||||
* @param {Boolean} options.fin Specifies whether the fragment is the last one
|
||||
* @param {Boolean} options.mask Specifies whether or not to mask `data`
|
||||
* @param {Function} cb Callback which is executed when data is written out
|
||||
* @public
|
||||
*/
|
||||
send (data, options, cb) {
|
||||
if (typeof options === 'function') {
|
||||
cb = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
if (this.readyState !== WebSocket.OPEN) {
|
||||
if (cb) cb(new Error('not opened'));
|
||||
else throw new Error('not opened');
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof data === 'number') data = data.toString();
|
||||
|
||||
const opts = Object.assign({
|
||||
binary: typeof data !== 'string',
|
||||
mask: !this._isServer,
|
||||
compress: true,
|
||||
fin: true
|
||||
}, options);
|
||||
|
||||
if (!this.extensions[PerMessageDeflate.extensionName]) {
|
||||
opts.compress = false;
|
||||
}
|
||||
|
||||
this._sender.send(data || constants.EMPTY_BUFFER, opts, cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forcibly close the connection.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
terminate () {
|
||||
if (this.readyState === WebSocket.CLOSED) return;
|
||||
if (this.readyState === WebSocket.CONNECTING) {
|
||||
this._req.abort();
|
||||
this.finalize(new Error('closed before the connection is established'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.finalize(true);
|
||||
}
|
||||
}
|
||||
|
||||
WebSocket.CONNECTING = 0;
|
||||
WebSocket.OPEN = 1;
|
||||
WebSocket.CLOSING = 2;
|
||||
WebSocket.CLOSED = 3;
|
||||
|
||||
//
|
||||
// Add the `onopen`, `onerror`, `onclose`, and `onmessage` attributes.
|
||||
// See https://html.spec.whatwg.org/multipage/comms.html#the-websocket-interface
|
||||
//
|
||||
['open', 'error', 'close', 'message'].forEach((method) => {
|
||||
Object.defineProperty(WebSocket.prototype, `on${method}`, {
|
||||
/**
|
||||
* Return the listener of the event.
|
||||
*
|
||||
* @return {(Function|undefined)} The event listener or `undefined`
|
||||
* @public
|
||||
*/
|
||||
get () {
|
||||
const listeners = this.listeners(method);
|
||||
for (var i = 0; i < listeners.length; i++) {
|
||||
if (listeners[i]._listener) return listeners[i]._listener;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Add a listener for the event.
|
||||
*
|
||||
* @param {Function} listener The listener to add
|
||||
* @public
|
||||
*/
|
||||
set (listener) {
|
||||
const listeners = this.listeners(method);
|
||||
for (var i = 0; i < listeners.length; i++) {
|
||||
//
|
||||
// Remove only the listeners added via `addEventListener`.
|
||||
//
|
||||
if (listeners[i]._listener) this.removeListener(method, listeners[i]);
|
||||
}
|
||||
this.addEventListener(method, listener);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
WebSocket.prototype.addEventListener = EventTarget.addEventListener;
|
||||
WebSocket.prototype.removeEventListener = EventTarget.removeEventListener;
|
||||
|
||||
module.exports = WebSocket;
|
||||
|
||||
/**
|
||||
* Initialize a WebSocket server client.
|
||||
*
|
||||
* @param {http.IncomingMessage} req The request object
|
||||
* @param {net.Socket} socket The network socket between the server and client
|
||||
* @param {Buffer} head The first packet of the upgraded stream
|
||||
* @param {Object} options WebSocket attributes
|
||||
* @param {Number} options.protocolVersion The WebSocket protocol version
|
||||
* @param {Object} options.extensions The negotiated extensions
|
||||
* @param {Number} options.maxPayload The maximum allowed message size
|
||||
* @param {String} options.protocol The chosen subprotocol
|
||||
* @private
|
||||
*/
|
||||
function initAsServerClient (socket, head, options) {
|
||||
this.protocolVersion = options.protocolVersion;
|
||||
this._maxPayload = options.maxPayload;
|
||||
this.extensions = options.extensions;
|
||||
this.protocol = options.protocol;
|
||||
|
||||
this._isServer = true;
|
||||
|
||||
this.setSocket(socket, head);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a WebSocket client.
|
||||
*
|
||||
* @param {String} address The URL to which to connect
|
||||
* @param {String[]} protocols The list of subprotocols
|
||||
* @param {Object} options Connection options
|
||||
* @param {String} options.protocol Value of the `Sec-WebSocket-Protocol` header
|
||||
* @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate
|
||||
* @param {Number} options.handshakeTimeout Timeout in milliseconds for the handshake request
|
||||
* @param {String} options.localAddress Local interface to bind for network connections
|
||||
* @param {Number} options.protocolVersion Value of the `Sec-WebSocket-Version` header
|
||||
* @param {Object} options.headers An object containing request headers
|
||||
* @param {String} options.origin Value of the `Origin` or `Sec-WebSocket-Origin` header
|
||||
* @param {http.Agent} options.agent Use the specified Agent
|
||||
* @param {String} options.host Value of the `Host` header
|
||||
* @param {Number} options.family IP address family to use during hostname lookup (4 or 6).
|
||||
* @param {Function} options.checkServerIdentity A function to validate the server hostname
|
||||
* @param {Boolean} options.rejectUnauthorized Verify or not the server certificate
|
||||
* @param {String} options.passphrase The passphrase for the private key or pfx
|
||||
* @param {String} options.ciphers The ciphers to use or exclude
|
||||
* @param {String} options.ecdhCurve The curves for ECDH key agreement to use or exclude
|
||||
* @param {(String|String[]|Buffer|Buffer[])} options.cert The certificate key
|
||||
* @param {(String|String[]|Buffer|Buffer[])} options.key The private key
|
||||
* @param {(String|Buffer)} options.pfx The private key, certificate, and CA certs
|
||||
* @param {(String|String[]|Buffer|Buffer[])} options.ca Trusted certificates
|
||||
* @private
|
||||
*/
|
||||
function initAsClient (address, protocols, options) {
|
||||
options = Object.assign({
|
||||
protocolVersion: protocolVersions[1],
|
||||
protocol: protocols.join(','),
|
||||
perMessageDeflate: true,
|
||||
handshakeTimeout: null,
|
||||
localAddress: null,
|
||||
headers: null,
|
||||
family: null,
|
||||
origin: null,
|
||||
agent: null,
|
||||
host: null,
|
||||
|
||||
//
|
||||
// SSL options.
|
||||
//
|
||||
checkServerIdentity: null,
|
||||
rejectUnauthorized: null,
|
||||
passphrase: null,
|
||||
ciphers: null,
|
||||
ecdhCurve: null,
|
||||
cert: null,
|
||||
key: null,
|
||||
pfx: null,
|
||||
ca: null
|
||||
}, options);
|
||||
|
||||
if (protocolVersions.indexOf(options.protocolVersion) === -1) {
|
||||
throw new Error(
|
||||
`unsupported protocol version: ${options.protocolVersion} ` +
|
||||
`(supported versions: ${protocolVersions.join(', ')})`
|
||||
);
|
||||
}
|
||||
|
||||
this.protocolVersion = options.protocolVersion;
|
||||
this._isServer = false;
|
||||
this.url = address;
|
||||
|
||||
const serverUrl = url.parse(address);
|
||||
const isUnixSocket = serverUrl.protocol === 'ws+unix:';
|
||||
|
||||
if (!serverUrl.host && (!isUnixSocket || !serverUrl.path)) {
|
||||
throw new Error('invalid url');
|
||||
}
|
||||
|
||||
const isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:';
|
||||
const key = crypto.randomBytes(16).toString('base64');
|
||||
const httpObj = isSecure ? https : http;
|
||||
var perMessageDeflate;
|
||||
|
||||
const requestOptions = {
|
||||
port: serverUrl.port || (isSecure ? 443 : 80),
|
||||
host: serverUrl.hostname,
|
||||
path: '/',
|
||||
headers: {
|
||||
'Sec-WebSocket-Version': options.protocolVersion,
|
||||
'Sec-WebSocket-Key': key,
|
||||
'Connection': 'Upgrade',
|
||||
'Upgrade': 'websocket'
|
||||
}
|
||||
};
|
||||
|
||||
if (options.headers) Object.assign(requestOptions.headers, options.headers);
|
||||
if (options.perMessageDeflate) {
|
||||
perMessageDeflate = new PerMessageDeflate(
|
||||
options.perMessageDeflate !== true ? options.perMessageDeflate : {},
|
||||
false
|
||||
);
|
||||
requestOptions.headers['Sec-WebSocket-Extensions'] = Extensions.format({
|
||||
[PerMessageDeflate.extensionName]: perMessageDeflate.offer()
|
||||
});
|
||||
}
|
||||
if (options.protocol) {
|
||||
requestOptions.headers['Sec-WebSocket-Protocol'] = options.protocol;
|
||||
}
|
||||
if (options.origin) {
|
||||
if (options.protocolVersion < 13) {
|
||||
requestOptions.headers['Sec-WebSocket-Origin'] = options.origin;
|
||||
} else {
|
||||
requestOptions.headers.Origin = options.origin;
|
||||
}
|
||||
}
|
||||
if (options.host) requestOptions.headers.Host = options.host;
|
||||
if (serverUrl.auth) requestOptions.auth = serverUrl.auth;
|
||||
|
||||
if (options.localAddress) requestOptions.localAddress = options.localAddress;
|
||||
if (options.family) requestOptions.family = options.family;
|
||||
|
||||
if (isUnixSocket) {
|
||||
const parts = serverUrl.path.split(':');
|
||||
|
||||
requestOptions.socketPath = parts[0];
|
||||
requestOptions.path = parts[1];
|
||||
} else if (serverUrl.path) {
|
||||
//
|
||||
// Make sure that path starts with `/`.
|
||||
//
|
||||
if (serverUrl.path.charAt(0) !== '/') {
|
||||
requestOptions.path = `/${serverUrl.path}`;
|
||||
} else {
|
||||
requestOptions.path = serverUrl.path;
|
||||
}
|
||||
}
|
||||
|
||||
var agent = options.agent;
|
||||
|
||||
//
|
||||
// A custom agent is required for these options.
|
||||
//
|
||||
if (
|
||||
options.rejectUnauthorized != null ||
|
||||
options.checkServerIdentity ||
|
||||
options.passphrase ||
|
||||
options.ciphers ||
|
||||
options.ecdhCurve ||
|
||||
options.cert ||
|
||||
options.key ||
|
||||
options.pfx ||
|
||||
options.ca
|
||||
) {
|
||||
if (options.passphrase) requestOptions.passphrase = options.passphrase;
|
||||
if (options.ciphers) requestOptions.ciphers = options.ciphers;
|
||||
if (options.ecdhCurve) requestOptions.ecdhCurve = options.ecdhCurve;
|
||||
if (options.cert) requestOptions.cert = options.cert;
|
||||
if (options.key) requestOptions.key = options.key;
|
||||
if (options.pfx) requestOptions.pfx = options.pfx;
|
||||
if (options.ca) requestOptions.ca = options.ca;
|
||||
if (options.checkServerIdentity) {
|
||||
requestOptions.checkServerIdentity = options.checkServerIdentity;
|
||||
}
|
||||
if (options.rejectUnauthorized != null) {
|
||||
requestOptions.rejectUnauthorized = options.rejectUnauthorized;
|
||||
}
|
||||
|
||||
if (!agent) agent = new httpObj.Agent(requestOptions);
|
||||
}
|
||||
|
||||
if (agent) requestOptions.agent = agent;
|
||||
|
||||
this._req = httpObj.get(requestOptions);
|
||||
|
||||
if (options.handshakeTimeout) {
|
||||
this._req.setTimeout(options.handshakeTimeout, () => {
|
||||
this._req.abort();
|
||||
this.finalize(new Error('opening handshake has timed out'));
|
||||
});
|
||||
}
|
||||
|
||||
this._req.on('error', (error) => {
|
||||
if (this._req.aborted) return;
|
||||
|
||||
this._req = null;
|
||||
this.finalize(error);
|
||||
});
|
||||
|
||||
this._req.on('response', (res) => {
|
||||
if (!this.emit('unexpected-response', this._req, res)) {
|
||||
this._req.abort();
|
||||
this.finalize(new Error(`unexpected server response (${res.statusCode})`));
|
||||
}
|
||||
});
|
||||
|
||||
this._req.on('upgrade', (res, socket, head) => {
|
||||
this.emit('headers', res.headers, res);
|
||||
|
||||
//
|
||||
// The user may have closed the connection from a listener of the `headers`
|
||||
// event.
|
||||
//
|
||||
if (this.readyState !== WebSocket.CONNECTING) return;
|
||||
|
||||
this._req = null;
|
||||
|
||||
const digest = crypto.createHash('sha1')
|
||||
.update(key + constants.GUID, 'binary')
|
||||
.digest('base64');
|
||||
|
||||
if (res.headers['sec-websocket-accept'] !== digest) {
|
||||
socket.destroy();
|
||||
return this.finalize(new Error('invalid server key'));
|
||||
}
|
||||
|
||||
const serverProt = res.headers['sec-websocket-protocol'];
|
||||
const protList = (options.protocol || '').split(/, */);
|
||||
var protError;
|
||||
|
||||
if (!options.protocol && serverProt) {
|
||||
protError = 'server sent a subprotocol even though none requested';
|
||||
} else if (options.protocol && !serverProt) {
|
||||
protError = 'server sent no subprotocol even though requested';
|
||||
} else if (serverProt && protList.indexOf(serverProt) === -1) {
|
||||
protError = 'server responded with an invalid protocol';
|
||||
}
|
||||
|
||||
if (protError) {
|
||||
socket.destroy();
|
||||
return this.finalize(new Error(protError));
|
||||
}
|
||||
|
||||
if (serverProt) this.protocol = serverProt;
|
||||
|
||||
if (perMessageDeflate) {
|
||||
try {
|
||||
const serverExtensions = Extensions.parse(
|
||||
res.headers['sec-websocket-extensions']
|
||||
);
|
||||
|
||||
if (serverExtensions[PerMessageDeflate.extensionName]) {
|
||||
perMessageDeflate.accept(
|
||||
serverExtensions[PerMessageDeflate.extensionName]
|
||||
);
|
||||
this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
|
||||
}
|
||||
} catch (err) {
|
||||
socket.destroy();
|
||||
this.finalize(new Error('invalid Sec-WebSocket-Extensions header'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.setSocket(socket, head);
|
||||
});
|
||||
}
|
||||
+326
@@ -0,0 +1,326 @@
|
||||
/*!
|
||||
* ws: a node.js websocket client
|
||||
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const safeBuffer = require('safe-buffer');
|
||||
const EventEmitter = require('events');
|
||||
const crypto = require('crypto');
|
||||
const Ultron = require('ultron');
|
||||
const http = require('http');
|
||||
const url = require('url');
|
||||
|
||||
const PerMessageDeflate = require('./PerMessageDeflate');
|
||||
const Extensions = require('./Extensions');
|
||||
const constants = require('./Constants');
|
||||
const WebSocket = require('./WebSocket');
|
||||
|
||||
const Buffer = safeBuffer.Buffer;
|
||||
|
||||
/**
|
||||
* Class representing a WebSocket server.
|
||||
*
|
||||
* @extends EventEmitter
|
||||
*/
|
||||
class WebSocketServer extends EventEmitter {
|
||||
/**
|
||||
* Create a `WebSocketServer` instance.
|
||||
*
|
||||
* @param {Object} options Configuration options
|
||||
* @param {String} options.host The hostname where to bind the server
|
||||
* @param {Number} options.port The port where to bind the server
|
||||
* @param {http.Server} options.server A pre-created HTTP/S server to use
|
||||
* @param {Function} options.verifyClient An hook to reject connections
|
||||
* @param {Function} options.handleProtocols An hook to handle protocols
|
||||
* @param {String} options.path Accept only connections matching this path
|
||||
* @param {Boolean} options.noServer Enable no server mode
|
||||
* @param {Boolean} options.clientTracking Specifies whether or not to track clients
|
||||
* @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate
|
||||
* @param {Number} options.maxPayload The maximum allowed message size
|
||||
* @param {Function} callback A listener for the `listening` event
|
||||
*/
|
||||
constructor (options, callback) {
|
||||
super();
|
||||
|
||||
options = Object.assign({
|
||||
maxPayload: 100 * 1024 * 1024,
|
||||
perMessageDeflate: false,
|
||||
handleProtocols: null,
|
||||
clientTracking: true,
|
||||
verifyClient: null,
|
||||
noServer: false,
|
||||
backlog: null, // use default (511 as implemented in net.js)
|
||||
server: null,
|
||||
host: null,
|
||||
path: null,
|
||||
port: null
|
||||
}, options);
|
||||
|
||||
if (options.port == null && !options.server && !options.noServer) {
|
||||
throw new TypeError('missing or invalid options');
|
||||
}
|
||||
|
||||
if (options.port != null) {
|
||||
this._server = http.createServer((req, res) => {
|
||||
const body = http.STATUS_CODES[426];
|
||||
|
||||
res.writeHead(426, {
|
||||
'Content-Length': body.length,
|
||||
'Content-Type': 'text/plain'
|
||||
});
|
||||
res.end(body);
|
||||
});
|
||||
this._server.listen(options.port, options.host, options.backlog, callback);
|
||||
} else if (options.server) {
|
||||
this._server = options.server;
|
||||
}
|
||||
|
||||
if (this._server) {
|
||||
this._ultron = new Ultron(this._server);
|
||||
this._ultron.on('listening', () => this.emit('listening'));
|
||||
this._ultron.on('error', (err) => this.emit('error', err));
|
||||
this._ultron.on('upgrade', (req, socket, head) => {
|
||||
this.handleUpgrade(req, socket, head, (client) => {
|
||||
this.emit('connection', client, req);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (options.perMessageDeflate === true) options.perMessageDeflate = {};
|
||||
if (options.clientTracking) this.clients = new Set();
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the server.
|
||||
*
|
||||
* @param {Function} cb Callback
|
||||
* @public
|
||||
*/
|
||||
close (cb) {
|
||||
//
|
||||
// Terminate all associated clients.
|
||||
//
|
||||
if (this.clients) {
|
||||
for (const client of this.clients) client.terminate();
|
||||
}
|
||||
|
||||
const server = this._server;
|
||||
|
||||
if (server) {
|
||||
this._ultron.destroy();
|
||||
this._ultron = this._server = null;
|
||||
|
||||
//
|
||||
// Close the http server if it was internally created.
|
||||
//
|
||||
if (this.options.port != null) return server.close(cb);
|
||||
}
|
||||
|
||||
if (cb) cb();
|
||||
}
|
||||
|
||||
/**
|
||||
* See if a given request should be handled by this server instance.
|
||||
*
|
||||
* @param {http.IncomingMessage} req Request object to inspect
|
||||
* @return {Boolean} `true` if the request is valid, else `false`
|
||||
* @public
|
||||
*/
|
||||
shouldHandle (req) {
|
||||
if (this.options.path && url.parse(req.url).pathname !== this.options.path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a HTTP Upgrade request.
|
||||
*
|
||||
* @param {http.IncomingMessage} req The request object
|
||||
* @param {net.Socket} socket The network socket between the server and client
|
||||
* @param {Buffer} head The first packet of the upgraded stream
|
||||
* @param {Function} cb Callback
|
||||
* @public
|
||||
*/
|
||||
handleUpgrade (req, socket, head, cb) {
|
||||
socket.on('error', socketError);
|
||||
|
||||
const version = +req.headers['sec-websocket-version'];
|
||||
const extensions = {};
|
||||
|
||||
if (
|
||||
req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket' ||
|
||||
!req.headers['sec-websocket-key'] || (version !== 8 && version !== 13) ||
|
||||
!this.shouldHandle(req)
|
||||
) {
|
||||
return abortConnection(socket, 400);
|
||||
}
|
||||
|
||||
if (this.options.perMessageDeflate) {
|
||||
const perMessageDeflate = new PerMessageDeflate(
|
||||
this.options.perMessageDeflate,
|
||||
true,
|
||||
this.options.maxPayload
|
||||
);
|
||||
|
||||
try {
|
||||
const offers = Extensions.parse(
|
||||
req.headers['sec-websocket-extensions']
|
||||
);
|
||||
|
||||
if (offers[PerMessageDeflate.extensionName]) {
|
||||
perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]);
|
||||
extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
|
||||
}
|
||||
} catch (err) {
|
||||
return abortConnection(socket, 400);
|
||||
}
|
||||
}
|
||||
|
||||
var protocol = (req.headers['sec-websocket-protocol'] || '').split(/, */);
|
||||
|
||||
//
|
||||
// Optionally call external protocol selection handler.
|
||||
//
|
||||
if (this.options.handleProtocols) {
|
||||
protocol = this.options.handleProtocols(protocol, req);
|
||||
if (protocol === false) return abortConnection(socket, 401);
|
||||
} else {
|
||||
protocol = protocol[0];
|
||||
}
|
||||
|
||||
//
|
||||
// Optionally call external client verification handler.
|
||||
//
|
||||
if (this.options.verifyClient) {
|
||||
const info = {
|
||||
origin: req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`],
|
||||
secure: !!(req.connection.authorized || req.connection.encrypted),
|
||||
req
|
||||
};
|
||||
|
||||
if (this.options.verifyClient.length === 2) {
|
||||
this.options.verifyClient(info, (verified, code, message) => {
|
||||
if (!verified) return abortConnection(socket, code || 401, message);
|
||||
|
||||
this.completeUpgrade(
|
||||
protocol,
|
||||
extensions,
|
||||
version,
|
||||
req,
|
||||
socket,
|
||||
head,
|
||||
cb
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.options.verifyClient(info)) return abortConnection(socket, 401);
|
||||
}
|
||||
|
||||
this.completeUpgrade(protocol, extensions, version, req, socket, head, cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade the connection to WebSocket.
|
||||
*
|
||||
* @param {String} protocol The chosen subprotocol
|
||||
* @param {Object} extensions The accepted extensions
|
||||
* @param {Number} version The WebSocket protocol version
|
||||
* @param {http.IncomingMessage} req The request object
|
||||
* @param {net.Socket} socket The network socket between the server and client
|
||||
* @param {Buffer} head The first packet of the upgraded stream
|
||||
* @param {Function} cb Callback
|
||||
* @private
|
||||
*/
|
||||
completeUpgrade (protocol, extensions, version, req, socket, head, cb) {
|
||||
//
|
||||
// Destroy the socket if the client has already sent a FIN packet.
|
||||
//
|
||||
if (!socket.readable || !socket.writable) return socket.destroy();
|
||||
|
||||
const key = crypto.createHash('sha1')
|
||||
.update(req.headers['sec-websocket-key'] + constants.GUID, 'binary')
|
||||
.digest('base64');
|
||||
|
||||
const headers = [
|
||||
'HTTP/1.1 101 Switching Protocols',
|
||||
'Upgrade: websocket',
|
||||
'Connection: Upgrade',
|
||||
`Sec-WebSocket-Accept: ${key}`
|
||||
];
|
||||
|
||||
if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`);
|
||||
if (extensions[PerMessageDeflate.extensionName]) {
|
||||
const params = extensions[PerMessageDeflate.extensionName].params;
|
||||
const value = Extensions.format({
|
||||
[PerMessageDeflate.extensionName]: [params]
|
||||
});
|
||||
headers.push(`Sec-WebSocket-Extensions: ${value}`);
|
||||
}
|
||||
|
||||
//
|
||||
// Allow external modification/inspection of handshake headers.
|
||||
//
|
||||
this.emit('headers', headers, req);
|
||||
|
||||
socket.write(headers.concat('\r\n').join('\r\n'));
|
||||
|
||||
const client = new WebSocket([socket, head], null, {
|
||||
maxPayload: this.options.maxPayload,
|
||||
protocolVersion: version,
|
||||
extensions,
|
||||
protocol
|
||||
});
|
||||
|
||||
if (this.clients) {
|
||||
this.clients.add(client);
|
||||
client.on('close', () => this.clients.delete(client));
|
||||
}
|
||||
|
||||
socket.removeListener('error', socketError);
|
||||
cb(client);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebSocketServer;
|
||||
|
||||
/**
|
||||
* Handle premature socket errors.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function socketError () {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the connection when preconditions are not fulfilled.
|
||||
*
|
||||
* @param {net.Socket} socket The socket of the upgrade request
|
||||
* @param {Number} code The HTTP response status code
|
||||
* @param {String} [message] The HTTP response body
|
||||
* @private
|
||||
*/
|
||||
function abortConnection (socket, code, message) {
|
||||
if (socket.writable) {
|
||||
message = message || http.STATUS_CODES[code];
|
||||
socket.write(
|
||||
`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` +
|
||||
'Connection: close\r\n' +
|
||||
'Content-type: text/html\r\n' +
|
||||
`Content-Length: ${Buffer.byteLength(message)}\r\n` +
|
||||
'\r\n' +
|
||||
message
|
||||
);
|
||||
}
|
||||
|
||||
socket.removeListener('error', socketError);
|
||||
socket.destroy();
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"_from": "ws@^3.0.0",
|
||||
"_id": "ws@3.3.3",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==",
|
||||
"_location": "/eth-lib/ws",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "range",
|
||||
"registry": true,
|
||||
"raw": "ws@^3.0.0",
|
||||
"name": "ws",
|
||||
"escapedName": "ws",
|
||||
"rawSpec": "^3.0.0",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "^3.0.0"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/eth-lib"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz",
|
||||
"_shasum": "f1cf84fe2d5e901ebce94efaece785f187a228f2",
|
||||
"_spec": "ws@^3.0.0",
|
||||
"_where": "C:\\Users\\Luis\\source\\repos\\AAMasters\\BackendServers\\node_modules\\eth-lib",
|
||||
"author": {
|
||||
"name": "Einar Otto Stangvik",
|
||||
"email": "einaros@gmail.com",
|
||||
"url": "http://2x.io"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/websockets/ws/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"dependencies": {
|
||||
"async-limiter": "~1.0.0",
|
||||
"safe-buffer": "~5.1.0",
|
||||
"ultron": "~1.1.0"
|
||||
},
|
||||
"deprecated": false,
|
||||
"description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
|
||||
"devDependencies": {
|
||||
"benchmark": "~2.1.2",
|
||||
"bufferutil": "~3.0.0",
|
||||
"eslint": "~4.13.0",
|
||||
"eslint-config-standard": "~10.2.0",
|
||||
"eslint-plugin-import": "~2.8.0",
|
||||
"eslint-plugin-node": "~5.2.0",
|
||||
"eslint-plugin-promise": "~3.6.0",
|
||||
"eslint-plugin-standard": "~3.0.0",
|
||||
"mocha": "~4.0.0",
|
||||
"nyc": "~11.3.0",
|
||||
"utf-8-validate": "~4.0.0"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"lib"
|
||||
],
|
||||
"homepage": "https://github.com/websockets/ws",
|
||||
"keywords": [
|
||||
"HyBi",
|
||||
"Push",
|
||||
"RFC-6455",
|
||||
"WebSocket",
|
||||
"WebSockets",
|
||||
"real-time"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"name": "ws",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/websockets/ws.git"
|
||||
},
|
||||
"scripts": {
|
||||
"integration": "eslint . && mocha test/*.integration.js",
|
||||
"lint": "eslint .",
|
||||
"test": "eslint . && nyc --reporter=html --reporter=text mocha test/*.test.js"
|
||||
},
|
||||
"version": "3.3.3"
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"_from": "eth-lib@^0.1.26",
|
||||
"_id": "eth-lib@0.1.29",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-bfttrr3/7gG4E02HoWTDUcDDslN003OlOoBxk9virpAZQ1ja/jDgwkWB8QfJF7ojuEowrqy+lzp9VcJG7/k5bQ==",
|
||||
"_location": "/eth-lib",
|
||||
"_phantomChildren": {
|
||||
"async-limiter": "1.0.1",
|
||||
"safe-buffer": "5.1.2",
|
||||
"ultron": "1.1.1"
|
||||
},
|
||||
"_requested": {
|
||||
"type": "range",
|
||||
"registry": true,
|
||||
"raw": "eth-lib@^0.1.26",
|
||||
"name": "eth-lib",
|
||||
"escapedName": "eth-lib",
|
||||
"rawSpec": "^0.1.26",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "^0.1.26"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/swarm-js"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.1.29.tgz",
|
||||
"_shasum": "0c11f5060d42da9f931eab6199084734f4dbd1d9",
|
||||
"_spec": "eth-lib@^0.1.26",
|
||||
"_where": "C:\\Users\\Luis\\source\\repos\\AAMasters\\BackendServers\\node_modules\\swarm-js",
|
||||
"author": {
|
||||
"name": "Victor Maia"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/maiavictor/eth-lib/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"dependencies": {
|
||||
"bn.js": "^4.11.6",
|
||||
"elliptic": "^6.4.0",
|
||||
"nano-json-stream-parser": "^0.1.2",
|
||||
"servify": "^0.1.12",
|
||||
"ws": "^3.0.0",
|
||||
"xhr-request-promise": "^0.1.2"
|
||||
},
|
||||
"deprecated": false,
|
||||
"description": "Lightweight Ethereum libraries",
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.24.1",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"ethjs-account": "^0.1.1",
|
||||
"ethjs-signer": "^0.1.1",
|
||||
"forall": "^0.1.1",
|
||||
"rlp": "^2.0.0"
|
||||
},
|
||||
"homepage": "https://github.com/maiavictor/eth-lib#readme",
|
||||
"keywords": [
|
||||
"ethereum"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "lib/index.js",
|
||||
"name": "eth-lib",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/maiavictor/eth-lib.git"
|
||||
},
|
||||
"scripts": {
|
||||
"babel": "babel src --out-dir=lib",
|
||||
"test": "mocha test/test.js"
|
||||
},
|
||||
"version": "0.1.29"
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
const Bytes = require("./bytes");
|
||||
const Nat = require("./nat");
|
||||
const elliptic = require("elliptic");
|
||||
const rlp = require("./rlp");
|
||||
const secp256k1 = new (elliptic.ec)("secp256k1"); // eslint-disable-line
|
||||
const {keccak256, keccak256s} = require("./hash");
|
||||
|
||||
const create = entropy => {
|
||||
const innerHex = keccak256(Bytes.concat(Bytes.random(32), entropy || Bytes.random(32)));
|
||||
const middleHex = Bytes.concat(Bytes.concat(Bytes.random(32), innerHex), Bytes.random(32));
|
||||
const outerHex = keccak256(middleHex);
|
||||
return fromPrivate(outerHex);
|
||||
}
|
||||
|
||||
const toChecksum = address => {
|
||||
const addressHash = keccak256s(address.slice(2));
|
||||
let checksumAddress = "0x";
|
||||
for (let i = 0; i < 40; i++)
|
||||
checksumAddress += parseInt(addressHash[i + 2], 16) > 7
|
||||
? address[i + 2].toUpperCase()
|
||||
: address[i + 2];
|
||||
return checksumAddress;
|
||||
}
|
||||
|
||||
const fromPrivate = privateKey => {
|
||||
const buffer = new Buffer(privateKey.slice(2), "hex");
|
||||
const ecKey = secp256k1.keyFromPrivate(buffer);
|
||||
const publicKey = "0x" + ecKey.getPublic(false, 'hex').slice(2);
|
||||
const publicHash = keccak256(publicKey);
|
||||
const address = toChecksum("0x" + publicHash.slice(-40));
|
||||
return {
|
||||
address: address,
|
||||
privateKey: privateKey
|
||||
}
|
||||
}
|
||||
|
||||
const encodeSignature = ([v, r, s]) =>
|
||||
Bytes.flatten([r,s,v]);
|
||||
|
||||
const decodeSignature = (hex) =>
|
||||
[Bytes.slice(64,65,hex), Bytes.slice(0,32,hex), Bytes.slice(32,64,hex)];
|
||||
|
||||
const makeSign = addToV => (hash, privateKey) => {
|
||||
const signature = secp256k1
|
||||
.keyFromPrivate(new Buffer(privateKey.slice(2), "hex"))
|
||||
.sign(new Buffer(hash.slice(2), "hex"), {canonical: true});
|
||||
return encodeSignature([
|
||||
Bytes.pad(1, Bytes.fromNumber(addToV + signature.recoveryParam)),
|
||||
Bytes.pad(32, Bytes.fromNat("0x" + signature.r.toString(16))),
|
||||
Bytes.pad(32, Bytes.fromNat("0x" + signature.s.toString(16)))]);
|
||||
}
|
||||
|
||||
const sign = makeSign(27); // v=27|28 instead of 0|1...
|
||||
|
||||
const recover = (hash, signature) => {
|
||||
const vals = decodeSignature(signature);
|
||||
const vrs = {v: Bytes.toNumber(vals[0]), r:vals[1].slice(2), s:vals[2].slice(2)};
|
||||
const ecPublicKey = secp256k1.recoverPubKey(new Buffer(hash.slice(2), "hex"), vrs, vrs.v < 2 ? vrs.v : 1 - (vrs.v % 2)); // because odd vals mean v=0... sadly that means v=0 means v=1... I hate that
|
||||
const publicKey = "0x" + ecPublicKey.encode("hex", false).slice(2);
|
||||
const publicHash = keccak256(publicKey);
|
||||
const address = toChecksum("0x" + publicHash.slice(-40));
|
||||
return address;
|
||||
}
|
||||
|
||||
const transactionSigningData = tx =>
|
||||
rlp.encode([
|
||||
Bytes.fromNat(tx.nonce),
|
||||
Bytes.fromNat(tx.gasPrice),
|
||||
Bytes.fromNat(tx.gas),
|
||||
tx.to.toLowerCase(),
|
||||
Bytes.fromNat(tx.value),
|
||||
tx.data,
|
||||
Bytes.fromNat(tx.chainId || "0x1"),
|
||||
"0x",
|
||||
"0x"]);
|
||||
|
||||
const signTransaction = (tx, privateKey) => {
|
||||
const signingData = transactionSigningData(tx);
|
||||
const signature = makeSign(Nat.toNumber(tx.chainId || "0x1") * 2 + 35)(keccak256(signingData), privateKey);
|
||||
const rawTransaction = rlp.decode(signingData).slice(0,6).concat(decodeSignature(signature));
|
||||
return rlp.encode(rawTransaction);
|
||||
}
|
||||
|
||||
const recoverTransaction = (rawTransaction) => {
|
||||
const values = rlp.decode(rawTransaction);
|
||||
const signature = encodeSignature(values.slice(6,9));
|
||||
const recovery = Bytes.toNumber(values[6]);
|
||||
const extraData = recovery < 35 ? [] : [Bytes.fromNumber((recovery - 35) >> 1), "0x", "0x"]
|
||||
const signingData = values.slice(0,6).concat(extraData);
|
||||
const signingDataHex = rlp.encode(signingData);
|
||||
return recover(keccak256(signingDataHex), signature);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
create,
|
||||
toChecksum,
|
||||
fromPrivate,
|
||||
sign,
|
||||
recover,
|
||||
signTransaction,
|
||||
recoverTransaction,
|
||||
transactionSigningData,
|
||||
encodeSignature,
|
||||
decodeSignature
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
const Api = provider => {
|
||||
const Nat = require("./nat");
|
||||
const Map = require("./map");
|
||||
const Bytes = require("./bytes");
|
||||
const keccak256s = require("./hash").keccak256s;
|
||||
const send = method => (...params) =>
|
||||
new Promise((resolve,reject) =>
|
||||
provider.send(method, params, (err, result) =>
|
||||
err ? reject(err) : resolve(result)));
|
||||
|
||||
// TODO check inputs
|
||||
// TODO move to proper file
|
||||
const encodeABI = (type, value) => {
|
||||
if (type === "bytes") {
|
||||
const length = Bytes.length(value);
|
||||
const nextMul32 = (((length - 1) / 32 | 0) + 1) * 32;
|
||||
const lengthEncoded = encodeABI("uint256", Nat.fromNumber(length)).data;
|
||||
const bytesEncoded = Bytes.padRight(nextMul32, value);
|
||||
return {data: Bytes.concat(lengthEncoded, bytesEncoded), dynamic: true};
|
||||
} else if (type === "uint256" || type === "bytes32") {
|
||||
return {data: Bytes.pad(32, value), dynamic: false};
|
||||
} else {
|
||||
throw "Eth-lib can't encode ABI type " + type + " yet.";
|
||||
}
|
||||
}
|
||||
|
||||
const sendTransaction = send("eth_sendTransaction");
|
||||
const sendRawTransaction = send("eth_sendRawTransaction");
|
||||
const getTransactionReceipt = send("eth_getTransactionReceipt");
|
||||
const compileSolidity = send("eth_compileSolidity");
|
||||
const call = send("eth_call");
|
||||
const getBalance = send("eth_getBalance");
|
||||
const accounts = send("eth_accounts");
|
||||
|
||||
const removeEmptyTo = tx =>
|
||||
tx.to === "" || tx.to === "0x" ? Map.remove("to")(tx) : tx;
|
||||
|
||||
const waitTransactionReceipt = getTransactionReceipt; // TODO: implement correctly
|
||||
|
||||
const addTransactionDefaults = tx =>
|
||||
// Get basic defaults
|
||||
Promise.all([
|
||||
tx.chainId || send("net_version")(),
|
||||
tx.gasPrice || send("eth_gasPrice")(),
|
||||
tx.nonce || send("eth_getTransactionCount")(tx.from,"latest"),
|
||||
tx.value || "0x0",
|
||||
tx.data || "0x"])
|
||||
// Add them to tx
|
||||
.then(([chainId, gasPrice, nonce, value, data]) => {
|
||||
return Map.merge(tx)({chainId: Nat.fromNumber(chainId), gasPrice, nonce, value, data});
|
||||
})
|
||||
.then(tx => {
|
||||
// Add gas default by estimating
|
||||
if (!tx.gas) {
|
||||
let estimateTx = {};
|
||||
estimateTx.from = tx.from;
|
||||
if (tx.to !== "" && tx.to !== "0x")
|
||||
estimateTx.to = tx.to;
|
||||
estimateTx.value = tx.value;
|
||||
estimateTx.nonce = tx.nonce;
|
||||
estimateTx.data = tx.data;
|
||||
return send("eth_estimateGas")(estimateTx)
|
||||
.then(usedGas => {
|
||||
return Map.merge(tx)({gas: Nat.div(Nat.mul(usedGas,"0x6"),"0x5")});
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve(tx);
|
||||
}
|
||||
});
|
||||
|
||||
const sendTransactionWithDefaults = tx =>
|
||||
addTransactionDefaults(tx)
|
||||
.then(tx => sendTransaction(removeEmptyTo(tx)));
|
||||
|
||||
const callWithDefaults = (tx, block) =>
|
||||
addTransactionDefaults(tx)
|
||||
.then(tx => call(removeEmptyTo(tx), block || "latest"));
|
||||
|
||||
const callMethodData = method => (...params) => {
|
||||
const methodSig = method.name + "(" + method.inputs.map(i => i.type).join(",") + ")";
|
||||
const methodHash = keccak256s(methodSig).slice(0,10);
|
||||
let encodedParams = params.map((param,i) => encodeABI(method.inputs[i].type, param));
|
||||
var headBlock = "0x";
|
||||
let dataBlock = "0x";
|
||||
for (var i = 0; i < encodedParams.length; ++i) {
|
||||
if (encodedParams[i].dynamic) {
|
||||
var dataLoc = encodedParams.length * 32 + Bytes.length(dataBlock);
|
||||
headBlock = Bytes.concat(headBlock, Bytes.pad(32, Nat.fromNumber(dataLoc)));
|
||||
dataBlock = Bytes.concat(dataBlock, encodedParams[i].data);
|
||||
} else {
|
||||
headBlock = Bytes.concat(headBlock, encodedParams[i].data);
|
||||
}
|
||||
}
|
||||
return Bytes.flatten([methodHash, headBlock, dataBlock]);
|
||||
}
|
||||
|
||||
// Address, Address, ContractInterface -> Contract
|
||||
const contract = (from, address, contractInterface) => {
|
||||
let contract = {};
|
||||
contract._address = address;
|
||||
contract._from = from;
|
||||
contract.broadcast = {};
|
||||
contractInterface.forEach(method => {
|
||||
if (method && method.name) {
|
||||
const call = (type, value) => (...params) => {
|
||||
const transaction = {
|
||||
from: from,
|
||||
to: address,
|
||||
value: value,
|
||||
data: callMethodData(method)(...params)
|
||||
};
|
||||
return type === "data"
|
||||
? Promise.resolve(transaction)
|
||||
: method.constant
|
||||
? callWithDefaults(transaction)
|
||||
: sendTransactionWithDefaults(transaction)
|
||||
.then(type === "receipt" ? waitTransactionReceipt : (x => x));
|
||||
};
|
||||
contract[method.name] = call("receipt", "0x0");
|
||||
if (!method.constant) {
|
||||
contract[method.name+"_data"] = call("data", "0x0");
|
||||
contract[method.name+"_pay"] = value => (...params) => call("receipt", value)(...params);
|
||||
contract[method.name+"_pay_txHash"] = value => (...params) => call("txHash", value)(...params);
|
||||
contract[method.name+"_txHash"] = call("txHash", "0x0");
|
||||
}
|
||||
}
|
||||
});
|
||||
return contract;
|
||||
}
|
||||
|
||||
// Address, Bytecode -> TxHash
|
||||
const deployBytecode_txHash = (from, code) =>
|
||||
sendTransactionWithDefaults({from: from, data: code, to: ""});
|
||||
|
||||
// Address, Bytecode -> Receipt
|
||||
const deployBytecode = (from, code) =>
|
||||
deployBytecode_txHash(from,code)
|
||||
.then(waitTransactionReceipt);
|
||||
|
||||
// Address, Bytecode, ContractInterface
|
||||
const deployBytecodeContract = (from, code, contractInterface) =>
|
||||
deployBytecode(from, code)
|
||||
.then(receipt => contract(from, receipt.contractAddress, contractInterface));
|
||||
|
||||
// Address, String, Address -> Contract
|
||||
const solidityContract = (from, source, at) =>
|
||||
compileSolidity(source)
|
||||
.then(({info:{abiDefinition}}) => contract(from, at, abiDefinition));
|
||||
|
||||
// Address, String -> TxHash
|
||||
const deploySolidity_txHash = (from, source) =>
|
||||
compileSolidity(source)
|
||||
.then(({code}) => deployBytecode_txHash(from, code));
|
||||
|
||||
// Address, String -> Receipt
|
||||
const deploySolidity = (from, source) =>
|
||||
deploySolidity_txHash(from, source)
|
||||
.then(waitTransactionReceipt);
|
||||
|
||||
// Address, String -> Contract
|
||||
const deploySolidityContract = (from, source) =>
|
||||
compileSolidity(source)
|
||||
.then(({code, info:{abiDefinition}}) =>
|
||||
deployBytecodeContract(from, code, abiDefinition));
|
||||
|
||||
return {
|
||||
send,
|
||||
|
||||
sendTransaction,
|
||||
sendRawTransaction,
|
||||
getTransactionReceipt,
|
||||
call,
|
||||
getBalance,
|
||||
accounts,
|
||||
|
||||
waitTransactionReceipt,
|
||||
addTransactionDefaults,
|
||||
sendTransactionWithDefaults,
|
||||
callWithDefaults,
|
||||
callMethodData,
|
||||
|
||||
contract,
|
||||
deployBytecode_txHash,
|
||||
deployBytecode,
|
||||
deployBytecodeContract,
|
||||
|
||||
compileSolidity,
|
||||
solidityContract,
|
||||
deploySolidity_txHash,
|
||||
deploySolidity,
|
||||
deploySolidityContract
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Api;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
const generate = (num, fn) => {
|
||||
let a = [];
|
||||
for (var i = 0; i < num; ++i)
|
||||
a.push(fn(i));
|
||||
return a;
|
||||
};
|
||||
|
||||
const replicate = (num, val) =>
|
||||
generate(num, () => val);
|
||||
|
||||
const concat = (a, b) =>
|
||||
a.concat(b);
|
||||
|
||||
const flatten = (a) => {
|
||||
let r = [];
|
||||
for (let j = 0, J = a.length; j < J; ++j)
|
||||
for (let i = 0, I = a[j].length; i < I; ++i)
|
||||
r.push(a[j][i]);
|
||||
return r;
|
||||
}
|
||||
|
||||
const chunksOf = (n,a) => {
|
||||
let b = [];
|
||||
for (let i = 0, l = a.length; i < l; i += n)
|
||||
b.push(a.slice(i, i + n));
|
||||
return b;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generate,
|
||||
replicate,
|
||||
concat,
|
||||
flatten,
|
||||
chunksOf
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
const A = require("./array.js");
|
||||
|
||||
const at = (bytes, index) =>
|
||||
parseInt(bytes.slice(index * 2 + 2, index * 2 + 4), 16);
|
||||
|
||||
const random = bytes => {
|
||||
let rnd;
|
||||
if (typeof window !== "undefined" && window.crypto && window.crypto.getRandomValues)
|
||||
rnd = window.crypto.getRandomValues(new Uint8Array(bytes));
|
||||
else if (typeof require !== "undefined")
|
||||
rnd = require("c" + "rypto").randomBytes(bytes);
|
||||
else
|
||||
throw "Safe random numbers not available.";
|
||||
let hex = "0x";
|
||||
for (let i = 0; i < bytes; ++i)
|
||||
hex += ("00" + rnd[i].toString(16)).slice(-2);
|
||||
return hex;
|
||||
};
|
||||
|
||||
const length = a =>
|
||||
(a.length - 2) / 2;
|
||||
|
||||
const flatten = (a) =>
|
||||
"0x" + a.reduce((r,s) => r + s.slice(2), "");
|
||||
|
||||
const slice = (i,j,bs) =>
|
||||
"0x" + bs.slice(i*2+2,j*2+2);
|
||||
|
||||
const reverse = hex => {
|
||||
let rev = "0x";
|
||||
for (let i = 0, l = length(hex); i < l; ++i) {
|
||||
rev += hex.slice((l-i)*2, (l-i+1)*2);
|
||||
}
|
||||
return rev;
|
||||
}
|
||||
|
||||
const pad = (l,hex) =>
|
||||
hex.length === l*2+2 ? hex : pad(l,"0x"+"0"+hex.slice(2));
|
||||
|
||||
const padRight = (l,hex) =>
|
||||
hex.length === l*2+2 ? hex : padRight(l,hex+"0");
|
||||
|
||||
const toArray = hex => {
|
||||
let arr = [];
|
||||
for (let i = 2, l = hex.length; i < l; i += 2)
|
||||
arr.push(parseInt(hex.slice(i, i + 2), 16));
|
||||
return arr;
|
||||
}
|
||||
|
||||
const fromArray = arr => {
|
||||
let hex = "0x";
|
||||
for (let i = 0, l = arr.length; i < l; ++i) {
|
||||
let b = arr[i];
|
||||
hex += (b < 16 ? "0" : "") + b.toString(16);
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
|
||||
const toUint8Array = hex =>
|
||||
new Uint8Array(toArray(hex));
|
||||
|
||||
const fromUint8Array = arr =>
|
||||
fromArray([].slice.call(arr, 0));
|
||||
|
||||
const fromNumber = num => {
|
||||
let hex = num.toString(16);
|
||||
return hex.length % 2 === 0 ? "0x" + hex : "0x0" + hex ;
|
||||
};
|
||||
|
||||
const toNumber = hex =>
|
||||
parseInt(hex.slice(2), 16);
|
||||
|
||||
const concat = (a, b) =>
|
||||
a.concat(b.slice(2));
|
||||
|
||||
const fromNat = bn =>
|
||||
bn === "0x0" ? "0x" : bn.length % 2 === 0 ? bn : "0x0" + bn.slice(2);
|
||||
|
||||
const toNat = bn =>
|
||||
bn[2] === "0" ? "0x" + bn.slice(3) : bn;
|
||||
|
||||
const fromAscii = ascii => {
|
||||
let hex = "0x";
|
||||
for (let i = 0; i < ascii.length; ++i)
|
||||
hex += ("00" + ascii.charCodeAt(i).toString(16)).slice(-2);
|
||||
return hex;
|
||||
};
|
||||
|
||||
const toAscii = hex => {
|
||||
let ascii = "";
|
||||
for (let i = 2; i < hex.length; i += 2)
|
||||
ascii += String.fromCharCode(parseInt(hex.slice(i, i + 2), 16));
|
||||
return ascii;
|
||||
};
|
||||
|
||||
// From https://gist.github.com/pascaldekloe/62546103a1576803dade9269ccf76330
|
||||
const fromString = s => {
|
||||
const makeByte = uint8 => {
|
||||
const b = uint8.toString(16);
|
||||
return b.length < 2 ? "0" + b : b;
|
||||
};
|
||||
let bytes = "0x";
|
||||
for (let ci = 0; ci != s.length; ci++) {
|
||||
let c = s.charCodeAt(ci);
|
||||
if (c < 128) {
|
||||
bytes += makeByte(c);
|
||||
continue;
|
||||
}
|
||||
if (c < 2048) {
|
||||
bytes += makeByte(c >> 6 | 192);
|
||||
} else {
|
||||
if (c > 0xd7ff && c < 0xdc00) {
|
||||
if (++ci == s.length) return null;
|
||||
let c2 = s.charCodeAt(ci);
|
||||
if (c2 < 0xdc00 || c2 > 0xdfff) return null;
|
||||
c = 0x10000 + ((c & 0x03ff) << 10) + (c2 & 0x03ff);
|
||||
bytes += makeByte(c >> 18 | 240);
|
||||
bytes += makeByte(c>> 12 & 63 | 128);
|
||||
} else { // c <= 0xffff
|
||||
bytes += makeByte(c >> 12 | 224);
|
||||
}
|
||||
bytes += makeByte(c >> 6 & 63 | 128);
|
||||
}
|
||||
bytes += makeByte(c & 63 | 128);
|
||||
}
|
||||
return bytes;
|
||||
};
|
||||
|
||||
const toString = (bytes) => {
|
||||
let s = '';
|
||||
let i = 0;
|
||||
let l = length(bytes);
|
||||
while (i < l) {
|
||||
let c = at(bytes, i++);
|
||||
if (c > 127) {
|
||||
if (c > 191 && c < 224) {
|
||||
if (i >= l) return null;
|
||||
c = (c & 31) << 6 | at(bytes, i) & 63;
|
||||
} else if (c > 223 && c < 240) {
|
||||
if (i + 1 >= l) return null;
|
||||
c = (c & 15) << 12 | (at(bytes, i) & 63) << 6 | at(bytes, ++i) & 63;
|
||||
} else if (c > 239 && c < 248) {
|
||||
if (i+2 >= l) return null;
|
||||
c = (c & 7) << 18 | (at(bytes, i) & 63) << 12 | (at(bytes, ++i) & 63) << 6 | at(bytes, ++i) & 63;
|
||||
} else return null;
|
||||
++i;
|
||||
}
|
||||
if (c <= 0xffff) s += String.fromCharCode(c);
|
||||
else if (c <= 0x10ffff) {
|
||||
c -= 0x10000;
|
||||
s += String.fromCharCode(c >> 10 | 0xd800)
|
||||
s += String.fromCharCode(c & 0x3FF | 0xdc00)
|
||||
} else return null;
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
random,
|
||||
length,
|
||||
concat,
|
||||
flatten,
|
||||
slice,
|
||||
reverse,
|
||||
pad,
|
||||
padRight,
|
||||
fromAscii,
|
||||
toAscii,
|
||||
fromString,
|
||||
toString,
|
||||
fromNumber,
|
||||
toNumber,
|
||||
fromNat,
|
||||
toNat,
|
||||
fromArray,
|
||||
toArray,
|
||||
fromUint8Array,
|
||||
toUint8Array
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
const Bytes = require("./bytes");
|
||||
|
||||
const inis = "pbtdkgxjfvlrmnsz".split("");
|
||||
|
||||
const mids = "auie".split("");
|
||||
|
||||
const back = [inis,mids].map(chars => {
|
||||
let map = {};
|
||||
for (let i = 0; i < chars.length; ++i)
|
||||
map[chars[i]] = i;
|
||||
return map;
|
||||
});
|
||||
|
||||
const syllableFromB64 = b64 => {
|
||||
const ini = (b64 >>> 2) & 15;
|
||||
const mid = (b64 >>> 0) & 3;
|
||||
return inis[ini] + mids[mid];
|
||||
}
|
||||
|
||||
const syllableToB64 = syllable => {
|
||||
const ini = back[0][syllable[0]];
|
||||
const mid = back[1][syllable[1]];
|
||||
return ini * 4 + mid;
|
||||
}
|
||||
|
||||
const b64sFromBytes = bytes => {
|
||||
//BoooooBoooooBoooooBooooo
|
||||
//BoooooooBoooooooBooooooo
|
||||
let b64s = [], b64;
|
||||
for (let i = 0, l = Math.ceil(bytes.length * 8 / 6); i < l; ++i) {
|
||||
let j = i / 8 * 6 | 0;
|
||||
b64s.push
|
||||
( i % 4 === 0 ? (bytes[j+0] / 4 | 0)
|
||||
: i % 4 === 1 ? bytes[j+0] % 4 * 16 + (bytes[j+1] / 16 | 0)
|
||||
: i % 4 === 2 ? bytes[j+0] % 16 * 4 + (bytes[j+1] / 64 | 0)
|
||||
: bytes[j+0] % 64 * 1);
|
||||
}
|
||||
return b64s;
|
||||
}
|
||||
|
||||
const b64sToBytes = b64s => {
|
||||
let bytes = [];
|
||||
for (let i = 0, l = Math.floor(b64s.length * 6 / 8); i < l; ++i) {
|
||||
let j = i / 6 * 8 | 0;
|
||||
bytes.push
|
||||
( i % 3 === 0 ? b64s[j+0] % 64 * 4 + (b64s[j+1] / 16 | 0)
|
||||
: i % 3 === 1 ? b64s[j+0] % 16 * 16 + (b64s[j+1] / 4 | 0)
|
||||
: b64s[j+0] % 4 * 64 + (b64s[j+1] / 1 | 0));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
const fromBytes = bytes =>
|
||||
b64sFromBytes(Bytes.toArray(bytes)).map(syllableFromB64).join("");
|
||||
|
||||
const toBytes = syllables =>
|
||||
Bytes.fromArray(b64sToBytes(syllables.match(/\w\w/g).map(syllableToB64)));
|
||||
|
||||
module.exports = {
|
||||
fromBytes,
|
||||
toBytes
|
||||
}
|
||||
+340
@@ -0,0 +1,340 @@
|
||||
// This was ported from https://github.com/emn178/js-sha3, with some minor
|
||||
// modifications and pruning. It is licensed under MIT:
|
||||
//
|
||||
// Copyright 2015-2016 Chen, Yi-Cyuan
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
const HEX_CHARS = '0123456789abcdef'.split('');
|
||||
const KECCAK_PADDING = [1, 256, 65536, 16777216];
|
||||
const SHIFT = [0, 8, 16, 24];
|
||||
const RC = [
|
||||
1, 0, 32898, 0, 32906, 2147483648, 2147516416, 2147483648, 32907, 0, 2147483649,
|
||||
0, 2147516545, 2147483648, 32777, 2147483648, 138, 0, 136, 0, 2147516425, 0,
|
||||
2147483658, 0, 2147516555, 0, 139, 2147483648, 32905, 2147483648, 32771,
|
||||
2147483648, 32770, 2147483648, 128, 2147483648, 32778, 0, 2147483658, 2147483648,
|
||||
2147516545, 2147483648, 32896, 2147483648, 2147483649, 0, 2147516424, 2147483648];
|
||||
|
||||
const Keccak = (bits) => ({
|
||||
blocks: [],
|
||||
reset: true,
|
||||
block: 0,
|
||||
start: 0,
|
||||
blockCount: (1600 - (bits << 1)) >> 5,
|
||||
outputBlocks: bits >> 5,
|
||||
s: (s=>[].concat(s,s,s,s,s))([0,0,0,0,0,0,0,0,0,0])
|
||||
});
|
||||
|
||||
const update = (state, message) => {
|
||||
var length = message.length,
|
||||
blocks = state.blocks,
|
||||
byteCount = state.blockCount << 2,
|
||||
blockCount = state.blockCount,
|
||||
outputBlocks = state.outputBlocks,
|
||||
s = state.s,
|
||||
index = 0,
|
||||
i,
|
||||
code;
|
||||
|
||||
// update
|
||||
while (index < length) {
|
||||
if (state.reset) {
|
||||
state.reset = false;
|
||||
blocks[0] = state.block;
|
||||
for (i = 1; i < blockCount + 1; ++i) {
|
||||
blocks[i] = 0;
|
||||
}
|
||||
}
|
||||
if (typeof message !== "string") {
|
||||
for (i = state.start; index < length && i < byteCount; ++index) {
|
||||
blocks[i >> 2] |= message[index] << SHIFT[i++ & 3];
|
||||
}
|
||||
} else {
|
||||
for (i = state.start; index < length && i < byteCount; ++index) {
|
||||
code = message.charCodeAt(index);
|
||||
if (code < 0x80) {
|
||||
blocks[i >> 2] |= code << SHIFT[i++ & 3];
|
||||
} else if (code < 0x800) {
|
||||
blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
||||
} else if (code < 0xd800 || code >= 0xe000) {
|
||||
blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
||||
} else {
|
||||
code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff));
|
||||
blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
||||
}
|
||||
}
|
||||
}
|
||||
state.lastByteIndex = i;
|
||||
if (i >= byteCount) {
|
||||
state.start = i - byteCount;
|
||||
state.block = blocks[blockCount];
|
||||
for (i = 0; i < blockCount; ++i) {
|
||||
s[i] ^= blocks[i];
|
||||
}
|
||||
f(s);
|
||||
state.reset = true;
|
||||
} else {
|
||||
state.start = i;
|
||||
}
|
||||
}
|
||||
|
||||
// finalize
|
||||
i = state.lastByteIndex;
|
||||
blocks[i >> 2] |= KECCAK_PADDING[i & 3];
|
||||
if (state.lastByteIndex === byteCount) {
|
||||
blocks[0] = blocks[blockCount];
|
||||
for (i = 1; i < blockCount + 1; ++i) {
|
||||
blocks[i] = 0;
|
||||
}
|
||||
}
|
||||
blocks[blockCount - 1] |= 0x80000000;
|
||||
for (i = 0; i < blockCount; ++i) {
|
||||
s[i] ^= blocks[i];
|
||||
}
|
||||
f(s);
|
||||
|
||||
// toString
|
||||
var hex = '', i = 0, j = 0, block;
|
||||
while (j < outputBlocks) {
|
||||
for (i = 0; i < blockCount && j < outputBlocks; ++i, ++j) {
|
||||
block = s[i];
|
||||
hex += HEX_CHARS[(block >> 4) & 0x0F] + HEX_CHARS[block & 0x0F] +
|
||||
HEX_CHARS[(block >> 12) & 0x0F] + HEX_CHARS[(block >> 8) & 0x0F] +
|
||||
HEX_CHARS[(block >> 20) & 0x0F] + HEX_CHARS[(block >> 16) & 0x0F] +
|
||||
HEX_CHARS[(block >> 28) & 0x0F] + HEX_CHARS[(block >> 24) & 0x0F];
|
||||
}
|
||||
if (j % blockCount === 0) {
|
||||
f(s);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
return "0x" + hex;
|
||||
}
|
||||
|
||||
const f = s => {
|
||||
var h, l, n, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9,
|
||||
b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17,
|
||||
b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30, b31, b32, b33,
|
||||
b34, b35, b36, b37, b38, b39, b40, b41, b42, b43, b44, b45, b46, b47, b48, b49;
|
||||
|
||||
for (n = 0; n < 48; n += 2) {
|
||||
c0 = s[0] ^ s[10] ^ s[20] ^ s[30] ^ s[40];
|
||||
c1 = s[1] ^ s[11] ^ s[21] ^ s[31] ^ s[41];
|
||||
c2 = s[2] ^ s[12] ^ s[22] ^ s[32] ^ s[42];
|
||||
c3 = s[3] ^ s[13] ^ s[23] ^ s[33] ^ s[43];
|
||||
c4 = s[4] ^ s[14] ^ s[24] ^ s[34] ^ s[44];
|
||||
c5 = s[5] ^ s[15] ^ s[25] ^ s[35] ^ s[45];
|
||||
c6 = s[6] ^ s[16] ^ s[26] ^ s[36] ^ s[46];
|
||||
c7 = s[7] ^ s[17] ^ s[27] ^ s[37] ^ s[47];
|
||||
c8 = s[8] ^ s[18] ^ s[28] ^ s[38] ^ s[48];
|
||||
c9 = s[9] ^ s[19] ^ s[29] ^ s[39] ^ s[49];
|
||||
|
||||
h = c8 ^ ((c2 << 1) | (c3 >>> 31));
|
||||
l = c9 ^ ((c3 << 1) | (c2 >>> 31));
|
||||
s[0] ^= h;
|
||||
s[1] ^= l;
|
||||
s[10] ^= h;
|
||||
s[11] ^= l;
|
||||
s[20] ^= h;
|
||||
s[21] ^= l;
|
||||
s[30] ^= h;
|
||||
s[31] ^= l;
|
||||
s[40] ^= h;
|
||||
s[41] ^= l;
|
||||
h = c0 ^ ((c4 << 1) | (c5 >>> 31));
|
||||
l = c1 ^ ((c5 << 1) | (c4 >>> 31));
|
||||
s[2] ^= h;
|
||||
s[3] ^= l;
|
||||
s[12] ^= h;
|
||||
s[13] ^= l;
|
||||
s[22] ^= h;
|
||||
s[23] ^= l;
|
||||
s[32] ^= h;
|
||||
s[33] ^= l;
|
||||
s[42] ^= h;
|
||||
s[43] ^= l;
|
||||
h = c2 ^ ((c6 << 1) | (c7 >>> 31));
|
||||
l = c3 ^ ((c7 << 1) | (c6 >>> 31));
|
||||
s[4] ^= h;
|
||||
s[5] ^= l;
|
||||
s[14] ^= h;
|
||||
s[15] ^= l;
|
||||
s[24] ^= h;
|
||||
s[25] ^= l;
|
||||
s[34] ^= h;
|
||||
s[35] ^= l;
|
||||
s[44] ^= h;
|
||||
s[45] ^= l;
|
||||
h = c4 ^ ((c8 << 1) | (c9 >>> 31));
|
||||
l = c5 ^ ((c9 << 1) | (c8 >>> 31));
|
||||
s[6] ^= h;
|
||||
s[7] ^= l;
|
||||
s[16] ^= h;
|
||||
s[17] ^= l;
|
||||
s[26] ^= h;
|
||||
s[27] ^= l;
|
||||
s[36] ^= h;
|
||||
s[37] ^= l;
|
||||
s[46] ^= h;
|
||||
s[47] ^= l;
|
||||
h = c6 ^ ((c0 << 1) | (c1 >>> 31));
|
||||
l = c7 ^ ((c1 << 1) | (c0 >>> 31));
|
||||
s[8] ^= h;
|
||||
s[9] ^= l;
|
||||
s[18] ^= h;
|
||||
s[19] ^= l;
|
||||
s[28] ^= h;
|
||||
s[29] ^= l;
|
||||
s[38] ^= h;
|
||||
s[39] ^= l;
|
||||
s[48] ^= h;
|
||||
s[49] ^= l;
|
||||
|
||||
b0 = s[0];
|
||||
b1 = s[1];
|
||||
b32 = (s[11] << 4) | (s[10] >>> 28);
|
||||
b33 = (s[10] << 4) | (s[11] >>> 28);
|
||||
b14 = (s[20] << 3) | (s[21] >>> 29);
|
||||
b15 = (s[21] << 3) | (s[20] >>> 29);
|
||||
b46 = (s[31] << 9) | (s[30] >>> 23);
|
||||
b47 = (s[30] << 9) | (s[31] >>> 23);
|
||||
b28 = (s[40] << 18) | (s[41] >>> 14);
|
||||
b29 = (s[41] << 18) | (s[40] >>> 14);
|
||||
b20 = (s[2] << 1) | (s[3] >>> 31);
|
||||
b21 = (s[3] << 1) | (s[2] >>> 31);
|
||||
b2 = (s[13] << 12) | (s[12] >>> 20);
|
||||
b3 = (s[12] << 12) | (s[13] >>> 20);
|
||||
b34 = (s[22] << 10) | (s[23] >>> 22);
|
||||
b35 = (s[23] << 10) | (s[22] >>> 22);
|
||||
b16 = (s[33] << 13) | (s[32] >>> 19);
|
||||
b17 = (s[32] << 13) | (s[33] >>> 19);
|
||||
b48 = (s[42] << 2) | (s[43] >>> 30);
|
||||
b49 = (s[43] << 2) | (s[42] >>> 30);
|
||||
b40 = (s[5] << 30) | (s[4] >>> 2);
|
||||
b41 = (s[4] << 30) | (s[5] >>> 2);
|
||||
b22 = (s[14] << 6) | (s[15] >>> 26);
|
||||
b23 = (s[15] << 6) | (s[14] >>> 26);
|
||||
b4 = (s[25] << 11) | (s[24] >>> 21);
|
||||
b5 = (s[24] << 11) | (s[25] >>> 21);
|
||||
b36 = (s[34] << 15) | (s[35] >>> 17);
|
||||
b37 = (s[35] << 15) | (s[34] >>> 17);
|
||||
b18 = (s[45] << 29) | (s[44] >>> 3);
|
||||
b19 = (s[44] << 29) | (s[45] >>> 3);
|
||||
b10 = (s[6] << 28) | (s[7] >>> 4);
|
||||
b11 = (s[7] << 28) | (s[6] >>> 4);
|
||||
b42 = (s[17] << 23) | (s[16] >>> 9);
|
||||
b43 = (s[16] << 23) | (s[17] >>> 9);
|
||||
b24 = (s[26] << 25) | (s[27] >>> 7);
|
||||
b25 = (s[27] << 25) | (s[26] >>> 7);
|
||||
b6 = (s[36] << 21) | (s[37] >>> 11);
|
||||
b7 = (s[37] << 21) | (s[36] >>> 11);
|
||||
b38 = (s[47] << 24) | (s[46] >>> 8);
|
||||
b39 = (s[46] << 24) | (s[47] >>> 8);
|
||||
b30 = (s[8] << 27) | (s[9] >>> 5);
|
||||
b31 = (s[9] << 27) | (s[8] >>> 5);
|
||||
b12 = (s[18] << 20) | (s[19] >>> 12);
|
||||
b13 = (s[19] << 20) | (s[18] >>> 12);
|
||||
b44 = (s[29] << 7) | (s[28] >>> 25);
|
||||
b45 = (s[28] << 7) | (s[29] >>> 25);
|
||||
b26 = (s[38] << 8) | (s[39] >>> 24);
|
||||
b27 = (s[39] << 8) | (s[38] >>> 24);
|
||||
b8 = (s[48] << 14) | (s[49] >>> 18);
|
||||
b9 = (s[49] << 14) | (s[48] >>> 18);
|
||||
|
||||
s[0] = b0 ^ (~b2 & b4);
|
||||
s[1] = b1 ^ (~b3 & b5);
|
||||
s[10] = b10 ^ (~b12 & b14);
|
||||
s[11] = b11 ^ (~b13 & b15);
|
||||
s[20] = b20 ^ (~b22 & b24);
|
||||
s[21] = b21 ^ (~b23 & b25);
|
||||
s[30] = b30 ^ (~b32 & b34);
|
||||
s[31] = b31 ^ (~b33 & b35);
|
||||
s[40] = b40 ^ (~b42 & b44);
|
||||
s[41] = b41 ^ (~b43 & b45);
|
||||
s[2] = b2 ^ (~b4 & b6);
|
||||
s[3] = b3 ^ (~b5 & b7);
|
||||
s[12] = b12 ^ (~b14 & b16);
|
||||
s[13] = b13 ^ (~b15 & b17);
|
||||
s[22] = b22 ^ (~b24 & b26);
|
||||
s[23] = b23 ^ (~b25 & b27);
|
||||
s[32] = b32 ^ (~b34 & b36);
|
||||
s[33] = b33 ^ (~b35 & b37);
|
||||
s[42] = b42 ^ (~b44 & b46);
|
||||
s[43] = b43 ^ (~b45 & b47);
|
||||
s[4] = b4 ^ (~b6 & b8);
|
||||
s[5] = b5 ^ (~b7 & b9);
|
||||
s[14] = b14 ^ (~b16 & b18);
|
||||
s[15] = b15 ^ (~b17 & b19);
|
||||
s[24] = b24 ^ (~b26 & b28);
|
||||
s[25] = b25 ^ (~b27 & b29);
|
||||
s[34] = b34 ^ (~b36 & b38);
|
||||
s[35] = b35 ^ (~b37 & b39);
|
||||
s[44] = b44 ^ (~b46 & b48);
|
||||
s[45] = b45 ^ (~b47 & b49);
|
||||
s[6] = b6 ^ (~b8 & b0);
|
||||
s[7] = b7 ^ (~b9 & b1);
|
||||
s[16] = b16 ^ (~b18 & b10);
|
||||
s[17] = b17 ^ (~b19 & b11);
|
||||
s[26] = b26 ^ (~b28 & b20);
|
||||
s[27] = b27 ^ (~b29 & b21);
|
||||
s[36] = b36 ^ (~b38 & b30);
|
||||
s[37] = b37 ^ (~b39 & b31);
|
||||
s[46] = b46 ^ (~b48 & b40);
|
||||
s[47] = b47 ^ (~b49 & b41);
|
||||
s[8] = b8 ^ (~b0 & b2);
|
||||
s[9] = b9 ^ (~b1 & b3);
|
||||
s[18] = b18 ^ (~b10 & b12);
|
||||
s[19] = b19 ^ (~b11 & b13);
|
||||
s[28] = b28 ^ (~b20 & b22);
|
||||
s[29] = b29 ^ (~b21 & b23);
|
||||
s[38] = b38 ^ (~b30 & b32);
|
||||
s[39] = b39 ^ (~b31 & b33);
|
||||
s[48] = b48 ^ (~b40 & b42);
|
||||
s[49] = b49 ^ (~b41 & b43);
|
||||
|
||||
s[0] ^= RC[n];
|
||||
s[1] ^= RC[n + 1];
|
||||
}
|
||||
}
|
||||
|
||||
const keccak = bits => str => {
|
||||
var msg;
|
||||
if (str.slice(0,2) === "0x") {
|
||||
msg = [];
|
||||
for (var i = 2, l = str.length; i < l; i += 2)
|
||||
msg.push(parseInt(str.slice(i,i+2), 16));
|
||||
} else {
|
||||
msg = str;
|
||||
}
|
||||
return update(Keccak(bits, bits), msg);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
keccak256: keccak(256),
|
||||
keccak512: keccak(512),
|
||||
keccak256s: keccak(256),
|
||||
keccak512s: keccak(512),
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
Account: require("./account.js"),
|
||||
Nat: require("./nat.js"),
|
||||
Bytes: require("./bytes.js"),
|
||||
Hash: require("./hash.js"),
|
||||
RLP: require("./rlp.js"),
|
||||
Api: require("./api.js"),
|
||||
Provider: require("./provider.js"),
|
||||
Desubits: require("./desubits.js"),
|
||||
Passphrase: require("./passphrase.js")
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
const merge = a => b => {
|
||||
let c = {};
|
||||
for (let key in a)
|
||||
c[key] = a[key];
|
||||
for (let key in b)
|
||||
c[key] = b[key];
|
||||
return c;
|
||||
}
|
||||
|
||||
const remove = removeKey => a => {
|
||||
let b = {};
|
||||
for (let key in a)
|
||||
if (key !== removeKey)
|
||||
b[key] = a[key];
|
||||
return b;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
merge,
|
||||
remove
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
const BN = require("bn.js");
|
||||
const Bytes = require("./bytes");
|
||||
|
||||
const fromBN = bn =>
|
||||
"0x" + bn.toString("hex");
|
||||
|
||||
const toBN = str =>
|
||||
new BN(str.slice(2), 16);
|
||||
|
||||
const fromString = str => {
|
||||
const bn = "0x" + (str.slice(0,2) === "0x"
|
||||
? new BN(str.slice(2), 16)
|
||||
: new BN(str, 10)).toString("hex");
|
||||
return bn === "0x0" ? "0x" : bn;
|
||||
}
|
||||
|
||||
const toEther = wei =>
|
||||
toNumber(div(wei, fromString("10000000000"))) / 100000000;
|
||||
|
||||
const fromEther = eth =>
|
||||
mul(fromNumber(Math.floor(eth * 100000000)), fromString("10000000000"));
|
||||
|
||||
const toString = a =>
|
||||
toBN(a).toString(10);
|
||||
|
||||
const fromNumber = a =>
|
||||
typeof a === "string"
|
||||
? (/^0x/.test(a) ? a : "0x" + a)
|
||||
: "0x" + new BN(a).toString("hex");
|
||||
|
||||
const toNumber = a =>
|
||||
toBN(a).toNumber();
|
||||
|
||||
const toUint256 = a =>
|
||||
Bytes.pad(32, a);
|
||||
|
||||
const bin = method => (a, b) =>
|
||||
fromBN(toBN(a)[method](toBN(b)));
|
||||
|
||||
const add = bin("add");
|
||||
const mul = bin("mul");
|
||||
const div = bin("div");
|
||||
const sub = bin("sub");
|
||||
|
||||
module.exports = {
|
||||
toString,
|
||||
fromString,
|
||||
toNumber,
|
||||
fromNumber,
|
||||
toEther,
|
||||
fromEther,
|
||||
toUint256,
|
||||
add,
|
||||
mul,
|
||||
div,
|
||||
sub
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
const Hash = require("./hash");
|
||||
const Bytes = require("./bytes");
|
||||
const Desubits = require("./desubits");
|
||||
|
||||
// Bytes -> Bytes
|
||||
const bytesAddChecksum = (bytes) => {
|
||||
const hash = Hash.keccak256(bytes);
|
||||
const sum = Bytes.slice(0, 1, hash);
|
||||
return Bytes.concat(bytes, sum);
|
||||
};
|
||||
|
||||
// Bytes -> Bool
|
||||
const bytesChecksum = (bytes) => {
|
||||
const length = Bytes.length(bytes);
|
||||
const prefix = Bytes.slice(0, length - 1, bytes);
|
||||
return bytesAddChecksum(prefix) === bytes;
|
||||
};
|
||||
|
||||
// () ~> Passphrase
|
||||
const create = () => {
|
||||
const bytes = bytesAddChecksum(Bytes.random(11));
|
||||
const seed = Desubits.fromBytes(bytes);
|
||||
const passphrase = seed.replace(/([a-z]{8})/g, "$1 ");
|
||||
return passphrase;
|
||||
};
|
||||
|
||||
// Passphrase -> Bytes
|
||||
const toBytes = (passphrase) => {
|
||||
const seed = passphrase.replace(/ /g, "");
|
||||
const bytes = Desubits.toBytes(passphrase);
|
||||
return bytes;
|
||||
};
|
||||
|
||||
// Passphrase -> Bool
|
||||
const checksum = (passphrase) =>
|
||||
bytesChecksum(toBytes(passphrase));
|
||||
|
||||
// Passphrase -> Bytes
|
||||
const toMasterKey = (passphrase) => {
|
||||
let hash = Hash.keccak256;
|
||||
let bytes = toBytes(passphrase);
|
||||
for (let i = 0, l = Math.pow(2,12); i < l; ++i)
|
||||
bytes = hash(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
create,
|
||||
checksum,
|
||||
toMasterKey
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
const njsp = require("nano-json-stream-parser");
|
||||
const request = require("xhr-request-promise");
|
||||
|
||||
const EthereumProvider = (url, intercept) => {
|
||||
intercept = intercept || (() => {});
|
||||
|
||||
let api = {};
|
||||
let onResponse = {};
|
||||
let callbacks = {};
|
||||
let nextId = 0;
|
||||
let send;
|
||||
|
||||
const makeSender = send => {
|
||||
const P = fn => (...args) => new Promise((resolve, reject) =>
|
||||
fn(...args.concat((err,res) => err ? reject(err) : resolve(res))));
|
||||
const sender = intercept => (method, params, callback) => {
|
||||
const intercepted = intercept(method, params, P(sender(() => {})));
|
||||
if (intercepted) {
|
||||
intercepted.then(response => callback(null, response));
|
||||
} else {
|
||||
send(method, params, callback);
|
||||
}
|
||||
}
|
||||
return sender(intercept);
|
||||
};
|
||||
|
||||
const parseResponse = njsp(json => {
|
||||
onResponse[json.id] && onResponse[json.id](null, json.result);
|
||||
});
|
||||
|
||||
const genPayload = (method, params) => ({
|
||||
jsonrpc: "2.0",
|
||||
id: ++nextId,
|
||||
method: method,
|
||||
params: params
|
||||
});
|
||||
|
||||
api.on = (name, callback) => {
|
||||
callbacks[name] = callback;
|
||||
}
|
||||
|
||||
if (/^ws/.test(url)) {
|
||||
const WebSocket = require("w"+"s");
|
||||
const ws = new WebSocket(url);
|
||||
api.send = makeSender((method, params, callback) => {
|
||||
const intercepted = intercept(method, params, P(send(() => {})));
|
||||
if (intercepted) {
|
||||
intercepted.then(response => callback(null, response));
|
||||
} else {
|
||||
const payload = genPayload(method, params);
|
||||
onResponse[payload.id] = callback;
|
||||
ws.send(JSON.stringify(payload));
|
||||
}
|
||||
});
|
||||
ws.on("message", parseResponse);
|
||||
ws.on("open", () => callbacks.connect && callbacks.connect(eth));
|
||||
ws.on("close", () => callbacks.disconnect && callbacks.disconnect());
|
||||
|
||||
} else if (/^http/.test(url)) {
|
||||
api.send = makeSender((method, params, callback) => {
|
||||
request(url, {
|
||||
method: "POST",
|
||||
contentType: "application/json-rpc",
|
||||
body: JSON.stringify(genPayload(method,params))})
|
||||
.then(answer => {
|
||||
var resp = JSON.parse(answer);
|
||||
if (resp.error) {
|
||||
callback(resp.error.message);
|
||||
} else {
|
||||
callback(null, resp.result)
|
||||
}
|
||||
})
|
||||
.catch(err => callback("Couldn't connect to Ethereum node."));
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
callbacks.connect && callbacks.connect();
|
||||
}, 1);
|
||||
|
||||
} else {
|
||||
throw "IPC not supported yet.";
|
||||
}
|
||||
|
||||
return api;
|
||||
};
|
||||
|
||||
module.exports = EthereumProvider;
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
// The RLP format
|
||||
// Serialization and deserialization for the BytesTree type, under the following grammar:
|
||||
// | First byte | Meaning |
|
||||
// | ---------- | -------------------------------------------------------------------------- |
|
||||
// | 0 to 127 | HEX(leaf) |
|
||||
// | 128 to 183 | HEX(length_of_leaf + 128) + HEX(leaf) |
|
||||
// | 184 to 191 | HEX(length_of_length_of_leaf + 128 + 55) + HEX(length_of_leaf) + HEX(leaf) |
|
||||
// | 192 to 247 | HEX(length_of_node + 192) + HEX(node) |
|
||||
// | 248 to 255 | HEX(length_of_length_of_node + 128 + 55) + HEX(length_of_node) + HEX(node) |
|
||||
|
||||
const encode = tree => {
|
||||
const padEven = str =>
|
||||
str.length % 2 === 0 ? str : "0" + str;
|
||||
|
||||
const uint = num =>
|
||||
padEven(num.toString(16))
|
||||
|
||||
const length = (len, add) =>
|
||||
len < 56 ? uint(add + len) : uint(add + uint(len).length / 2 + 55) + uint(len);
|
||||
|
||||
const dataTree = tree => {
|
||||
if (typeof tree === "string") {
|
||||
const hex = tree.slice(2);
|
||||
const pre = hex.length != 2 || hex >= "80" ? length(hex.length / 2, 128) : "";
|
||||
return pre + hex;
|
||||
} else {
|
||||
const hex = tree.map(dataTree).join("");
|
||||
const pre = length(hex.length / 2, 192)
|
||||
return pre + hex;
|
||||
}
|
||||
}
|
||||
|
||||
return "0x" + dataTree(tree);
|
||||
};
|
||||
|
||||
const decode = hex => {
|
||||
let i = 2;
|
||||
|
||||
const parseTree = () => {
|
||||
if (i >= hex.length) throw "";
|
||||
const head = hex.slice(i,i+2);
|
||||
return head < "80" ? (i+=2, "0x" + head)
|
||||
: head < "c0" ? parseHex()
|
||||
: parseList();
|
||||
}
|
||||
|
||||
const parseLength = () => {
|
||||
const len = parseInt(hex.slice(i,i+=2), 16) % 64;
|
||||
return len < 56 ? len : parseInt(hex.slice(i, i += (len - 55) * 2), 16);
|
||||
}
|
||||
|
||||
const parseHex = () => {
|
||||
const len = parseLength();
|
||||
return "0x" + hex.slice(i, i += len * 2);
|
||||
}
|
||||
|
||||
const parseList = () => {
|
||||
const lim = parseLength() * 2 + i;
|
||||
let list = [];
|
||||
while (i < lim)
|
||||
list.push(parseTree());
|
||||
return list;
|
||||
}
|
||||
|
||||
try {
|
||||
return parseTree();
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {encode, decode};
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
const F = require("forall");
|
||||
const A = require("./array");
|
||||
const B = require("./bytes");
|
||||
const Account = require("./account");
|
||||
|
||||
F.Bytes = F(F.Type, {
|
||||
form: "a JavaScript String starting with a `0x`, followed by an even number of low-case hex chars (i.e., `0123456789abcdef`)",
|
||||
rand: () => "0x" + A.generate((Math.random() * 16 | 0) * 2, () => (Math.random() * 16 | 0).toString(16)).join(""),
|
||||
test: value => typeof value === "string" && /^0x([0-9a-f][0-9a-f])*$/.test(value),
|
||||
}).__name("Bytes").__desc("any arbitrary data");
|
||||
|
||||
F.NBytes = bytes => F(F.Type, {
|
||||
form: "a JavaScript String starting with a `0x`, followed by " + (bytes * 2) + " low-case hex chars (i.e., `0123456789abcdef`)",
|
||||
test: value => F.Bytes.test(value) && value.length === (bytes * 2 + 2),
|
||||
rand: () => "0x" + A.generate(bytes * 2, () => (Math.random() * 16 | 0).toString(16)).join("")
|
||||
})
|
||||
.__name("NBytes(" + bytes + ")")
|
||||
.__desc("any arbitrary data of exactly " + bytes + "-byte" + (bytes > 1 ? "s" : ""));
|
||||
|
||||
F.Nat = F(F.Type, {
|
||||
form: "a JavaScript String starting with a `0x`, followed by at least one low-case hex char different from 0, followed by any number of low-case hex chars (i.e., `0123456789abcdef`)",
|
||||
test: value => typeof value === "string" && /^0x[1-9a-f]([0-9a-f])*$/.test(value),
|
||||
rand: () => "0x" + (Math.random() * Math.pow(2,50) | 0).toString(16)
|
||||
})
|
||||
.__name("Nat")
|
||||
.__desc("an arbitrarily long non-negative integer number");
|
||||
|
||||
F.Address = F(F.Type, {
|
||||
form: "a JavaScript String starting with a `0x`, followed by 40 hex chars (i.e., `0123456789abcdefABCDEF`), with the nth hex being uppercase iff the nth hex of the keccak256 of the lowercase address in ASCII is > 7",
|
||||
test: address => /^(0x)?[0-9a-f]{40}$/i.test(address) && Account.toChecksum(address.toLowerCase()) === address,
|
||||
rand: () => F.Account.rand().address
|
||||
})
|
||||
.__name("Address")
|
||||
.__desc("an Ethereum public address");
|
||||
|
||||
F.Hash = F(F.Type, {
|
||||
form: F.NBytes(32).form,
|
||||
test: F.NBytes(32).test,
|
||||
rand: F.NBytes(32).rand
|
||||
})
|
||||
.__name("Hash")
|
||||
.__desc("a Keccak-256 hash");
|
||||
|
||||
F.PrivateKey = F(F.Type, {
|
||||
form: F.NBytes(32).form,
|
||||
test: F.NBytes(32).test,
|
||||
rand: F.NBytes(32).rand
|
||||
})
|
||||
.__name("PrivateKey")
|
||||
.__desc("an Ethereum private key");
|
||||
|
||||
F.Account = (() => {
|
||||
const base = F.Struct({
|
||||
address: F.Address,
|
||||
privateKey: F.PrivateKey,
|
||||
});
|
||||
return F(F.Type, {
|
||||
form: base.form,
|
||||
test: base.test,
|
||||
rand: () => Account.create("")
|
||||
});
|
||||
})()
|
||||
.__name("Account")
|
||||
.__desc("an Ethereum account");
|
||||
|
||||
F.BytesTree = F(F.Type, {
|
||||
form: "either " + F.Bytes.form + ", or a tree of nested JavaScript Arrays of BytesTrees",
|
||||
test: value => F.Bytes.test(value) || value instanceof Array && value.reduce((r,v) => F.BytesTree.test(v) && r, true),
|
||||
rand: () => {
|
||||
let list = [];
|
||||
while (Math.random() < 0.8) {
|
||||
if (Math.random() < 0.8)
|
||||
list.push(F.Bytes.rand());
|
||||
else
|
||||
list.push(F.BytesTree.rand());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
})
|
||||
.__name("BytesTree")
|
||||
.__desc("a tree of arbitrary binary data");
|
||||
|
||||
module.exports = F;
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
const rnd = require("./randomData.js");
|
||||
const rlp = require("./../../src/rlp.js");
|
||||
const ref = {rlp: require("rlp")};
|
||||
const cps = f => {
|
||||
for (var t = Date.now(), i = 0; Date.now() - t < 1000; ++i)
|
||||
f();
|
||||
return i;
|
||||
};
|
||||
|
||||
let dataTrees = [];
|
||||
for (let i = 0; i < 64; ++i)
|
||||
dataTrees.push(rnd.dataTree());
|
||||
console.log("Benchmarking Eth-Lib's RLP implementation vs the one on NPM.");
|
||||
const liteCps = cps(() => dataTrees.map((dt) => rlp.decode(rlp.encode(dt))));
|
||||
const npmCps = cps(() => dataTrees.map((dt) => ref.rlp.decode(ref.rlp.encode(dt))));
|
||||
console.log("- Eth-Lib: " + liteCps + " calls per second.");
|
||||
console.log("- Reference: " + npmCps + " calls per second.");
|
||||
console.log("- Eth-Lib is: " + (liteCps / npmCps).toFixed(2) + " " + (liteCps > npmCps ? "faster" : "slower") + " than reference.");
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// () ~> HexString
|
||||
const hexString = () => {
|
||||
let str = "";
|
||||
while (Math.random() < 0.95)
|
||||
str = str + ("00" + (Math.random() * 256 | 0).toString(16)).slice(-2);
|
||||
return "0x" + str;
|
||||
}
|
||||
|
||||
// () ~> DataTree
|
||||
const dataTree = () => {
|
||||
let list = [];
|
||||
while (Math.random() < 0.8) {
|
||||
if (Math.random() < 0.8)
|
||||
list.push(hexString());
|
||||
else
|
||||
list.push(dataTree());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
hexString,
|
||||
dataTree
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
const assert = require("assert");
|
||||
const rnd = require("./lib/randomData.js");
|
||||
const rlp = require("./../src/rlp.js");
|
||||
const ref = {rlp: require("rlp")};
|
||||
const F = require("./../src/types.js");
|
||||
const Nat = require("./../src/Nat.js");
|
||||
const Account = require("./../src/account.js");
|
||||
const keccak256 = require("./../src/hash.js").keccak256;
|
||||
const ethjs = {
|
||||
signer: require("ethjs-signer"),
|
||||
account: require("ethjs-account")};
|
||||
|
||||
describe("RLP", () => {
|
||||
it("Must operate identically to reference implementation", () => {
|
||||
// Builds a test set of 256 random dataTrees + 2 default
|
||||
let dataTrees = [];
|
||||
dataTrees.push(["0x00112233", "0x00", "0x44", "0x55", "0xf0", "0xff", ["0x66"], ["0x77", "0x88"], "0x", "0x", "0x99aabb"]);
|
||||
dataTrees.push("0x00112233445566778899aabbccddeeff");
|
||||
for (let i = 0; i < 256; ++i)
|
||||
dataTrees.push(rnd.dataTree());
|
||||
|
||||
// Tests if they encode and decode identically to the reference implementation
|
||||
dataTrees.forEach(dataTree => {
|
||||
const refEnc = "0x" + ref.rlp.encode(dataTree).toString("hex");
|
||||
const impEnc = rlp.encode(dataTree);
|
||||
const impDec = rlp.decode(impEnc);
|
||||
try {
|
||||
assert(refEnc === impEnc);
|
||||
assert(JSON.stringify(impDec) === JSON.stringify(dataTree));
|
||||
} catch (e) {
|
||||
console.log(JSON.stringify(dataTree, null, 2));
|
||||
}
|
||||
});
|
||||
});
|
||||
it("Obeys law: `decode(encode(tree)) == tree`", () => {
|
||||
F.forall([F.BytesTree], tree => JSON.stringify(rlp.decode(rlp.encode(tree))) === JSON.stringify(tree));
|
||||
});
|
||||
});
|
||||
|
||||
describe("account", function () {
|
||||
it("must recover the same address that signed", () => {
|
||||
F.forall([F.Account, F.Bytes], (acc, msg) =>
|
||||
Account.recover(keccak256(msg), Account.sign(keccak256(msg), acc.privateKey, 0)) === acc.address,
|
||||
32);
|
||||
});
|
||||
|
||||
it("must match the generated address with EthJS-account", () => {
|
||||
F.forall([F.NBytes(32)], pvt => {
|
||||
const ethfpAcc = Account.fromPrivate(pvt);
|
||||
const ethjsAcc = ethjs.account.privateToAccount(pvt);
|
||||
return ethfpAcc.address === ethjsAcc.address;
|
||||
}, 128);
|
||||
});
|
||||
|
||||
it("must match expected values for complex pre-determined tests", () => {
|
||||
const accounts = [
|
||||
{
|
||||
"address": '0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F',
|
||||
"publicKey": '0x4bc2a31265153f07e70e0bab08724e6b85e217f8cd628ceb62974247bb493382ce28cab79ad7119ee1ad3ebcdb98a16805211530ecc6cfefa1b88e6dff99232a',
|
||||
"privateKey": '0x4646464646464646464646464646464646464646464646464646464646464646',
|
||||
"transactions": [
|
||||
{
|
||||
"object": {
|
||||
"nonce": Nat.fromString("9"),
|
||||
"gasPrice": Nat.fromString("20000000000"),
|
||||
"gas": Nat.fromString("21000"),
|
||||
"to": '0x3535353535353535353535353535353535353535',
|
||||
"value": Nat.fromString("1000000000000000000"),
|
||||
"data": "0x"
|
||||
},
|
||||
"signature": "0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83"
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"address": '0xFCAd0B19bB29D4674531d6f115237E16AfCE377c',
|
||||
"publicKey": '0x4646ae5047316b4230d0086c8acec687f00b1cd9d1dc634f6cb358ac0a9a8ffffe77b4dd0a4bfb95851f3b7355c781dd60f8418fc8a65d14907aff47c903a559',
|
||||
"privateKey": '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
|
||||
"transactions": [
|
||||
{
|
||||
"object": {
|
||||
"nonce": Nat.fromString("0"),
|
||||
"gasPrice": Nat.fromString("230000000000"),
|
||||
"gas": Nat.fromString("21000"),
|
||||
"to": '0xFCAd0B19bB29D4674531d6f115237E16AfCE377c',
|
||||
"value": Nat.fromString("1000000000000000000"),
|
||||
"data": "0x0123abcd"
|
||||
},
|
||||
"signature": "0xf8708085358d117c0082520894fcad0b19bb29d4674531d6f115237e16afce377c880de0b6b3a7640000840123abcd25a032dbcf46a64b9892df24d8b961d2a52fd66b1dabd3a0d96940fd6795c01d8711a01b86df9475de7451554557d87b69456e3fa95aa5375584bf63d1ffd647a225d9",
|
||||
"oldSignature": "0xf8708085358d117c0082520894fcad0b19bb29d4674531d6f115237e16afce377c880de0b6b3a7640000840123abcd1ba04e289b471dd4469d5080ce3726b8359d5b0c649e012bbbdde53f9b6580ad21a2a0333663ea96846c112f3878705c8c24a763d1fbf8f97c174d26e350c7ef0d7263"
|
||||
},
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// For each account
|
||||
accounts.forEach(function (correctAccount, i) {
|
||||
|
||||
// Generates an address from this account's private key
|
||||
const testAccount = Account.fromPrivate(correctAccount.privateKey);
|
||||
|
||||
// Generated address must match
|
||||
assert(testAccount.address === correctAccount.address);
|
||||
|
||||
// For each transaction on this account
|
||||
correctAccount.transactions.forEach(function (transaction) {
|
||||
|
||||
// Signs it, uing post-EIP 155 scheme
|
||||
const signature = Account.signTransaction(
|
||||
transaction.object,
|
||||
correctAccount.privateKey);
|
||||
|
||||
// Checks if the signature is as expected
|
||||
assert(transaction.signature === signature);
|
||||
|
||||
//// Checks if we can recover the right address
|
||||
const recoveredAddress = Account.recoverTransaction(signature);
|
||||
assert(recoveredAddress === testAccount.address);
|
||||
|
||||
//// If the test also provides a pre-EIP 155 signature
|
||||
if (transaction.oldSignature) {
|
||||
|
||||
let txObj = {};
|
||||
for (let key in transaction.object)
|
||||
txObj[key] = transaction.object[key].replace(/^0x$/,"0x0");
|
||||
|
||||
//Signs it, using pre-EIP 155 scheme (using the ethjs-signer lib)
|
||||
const oldSignature = ethjs.signer.sign(txObj, correctAccount.privateKey);
|
||||
|
||||
//// Checks if the signature is as expected
|
||||
assert(transaction.oldSignature === oldSignature);
|
||||
|
||||
//// Checks if we can recover the right address from old sigs (using web3)
|
||||
const recoveredAddress = Account.recoverTransaction(oldSignature);
|
||||
assert(recoveredAddress === testAccount.address);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user