| // Domain Public by Eric Wendelin http://www.eriwen.com/ (2008) |
| // Luke Smith http://lucassmith.name/ (2008) |
| // Loic Dachary <loic@dachary.org> (2008) |
| // Johan Euphrosine <proppy@aminche.com> (2008) |
| // Oyvind Sean Kinsey http://kinsey.no/blog (2010) |
| // Victor Homyakov <victor-homyakov@users.sourceforge.net> (2010) |
| /*global module, exports, define, ActiveXObject*/ |
| (function(global, factory) { |
| if (typeof exports === 'object') { |
| // Node |
| module.exports.printStackTrace = factory(); |
| } else if (typeof define === 'function' && define.amd) { |
| // AMD |
| define(factory); |
| } else { |
| // Browser globals |
| global.printStackTrace = factory(); |
| } |
| }(this, function() { |
| /** |
| * Main function giving a function stack trace with a forced or passed in Error |
| * |
| * @cfg {Error} e The error to create a stacktrace from (optional) |
| * @cfg {Boolean} guess If we should try to resolve the names of anonymous functions |
| * @return {Array} of Strings with functions, lines, files, and arguments where possible |
| */ |
| function printStackTrace(options) { |
| options = options || {guess: true}; |
| var ex = options.e || null, guess = !!options.guess, mode = options.mode || null; |
| var p = new printStackTrace.implementation(), result = p.run(ex, mode); |
| return (guess) ? p.guessAnonymousFunctions(result) : result; |
| } |
| |
| printStackTrace.implementation = function() { |
| }; |
| |
| printStackTrace.implementation.prototype = { |
| /** |
| * @param {Error} [ex] The error to create a stacktrace from (optional) |
| * @param {String} [mode] Forced mode (optional, mostly for unit tests) |
| */ |
| run: function(ex, mode) { |
| ex = ex || this.createException(); |
| mode = mode || this.mode(ex); |
| if (mode === 'other') { |
| return this.other(arguments.callee); |
| } else { |
| return this[mode](ex); |
| } |
| }, |
| |
| createException: function() { |
| try { |
| this.undef(); |
| } catch (e) { |
| return e; |
| } |
| }, |
| |
| /** |
| * Mode could differ for different exception, e.g. |
| * exceptions in Chrome may or may not have arguments or stack. |
| * |
| * @return {String} mode of operation for the exception |
| */ |
| mode: function(e) { |
| if (typeof window !== 'undefined' && window.navigator.userAgent.indexOf('PhantomJS') > -1) { |
| return 'phantomjs'; |
| } |
| |
| if (e['arguments'] && e.stack) { |
| return 'chrome'; |
| } |
| |
| if (e.stack && e.sourceURL) { |
| return 'safari'; |
| } |
| |
| if (e.stack && e.number) { |
| return 'ie'; |
| } |
| |
| if (e.stack && e.fileName) { |
| return 'firefox'; |
| } |
| |
| if (e.message && e['opera#sourceloc']) { |
| // e.message.indexOf("Backtrace:") > -1 -> opera9 |
| // 'opera#sourceloc' in e -> opera9, opera10a |
| // !e.stacktrace -> opera9 |
| if (!e.stacktrace) { |
| return 'opera9'; // use e.message |
| } |
| if (e.message.indexOf('\n') > -1 && e.message.split('\n').length > e.stacktrace.split('\n').length) { |
| // e.message may have more stack entries than e.stacktrace |
| return 'opera9'; // use e.message |
| } |
| return 'opera10a'; // use e.stacktrace |
| } |
| |
| if (e.message && e.stack && e.stacktrace) { |
| // e.stacktrace && e.stack -> opera10b |
| if (e.stacktrace.indexOf("called from line") < 0) { |
| return 'opera10b'; // use e.stacktrace, format differs from 'opera10a' |
| } |
| // e.stacktrace && e.stack -> opera11 |
| return 'opera11'; // use e.stacktrace, format differs from 'opera10a', 'opera10b' |
| } |
| |
| if (e.stack && !e.fileName) { |
| // Chrome 27 does not have e.arguments as earlier versions, |
| // but still does not have e.fileName as Firefox |
| return 'chrome'; |
| } |
| |
| return 'other'; |
| }, |
| |
| /** |
| * Given a context, function name, and callback function, overwrite it so that it calls |
| * printStackTrace() first with a callback and then runs the rest of the body. |
| * |
| * @param {Object} context of execution (e.g. window) |
| * @param {String} functionName to instrument |
| * @param {Function} callback function to call with a stack trace on invocation |
| */ |
| instrumentFunction: function(context, functionName, callback) { |
| context = context || window; |
| var original = context[functionName]; |
| context[functionName] = function instrumented() { |
| callback.call(this, printStackTrace().slice(4)); |
| return context[functionName]._instrumented.apply(this, arguments); |
| }; |
| context[functionName]._instrumented = original; |
| }, |
| |
| /** |
| * Given a context and function name of a function that has been |
| * instrumented, revert the function to it's original (non-instrumented) |
| * state. |
| * |
| * @param {Object} context of execution (e.g. window) |
| * @param {String} functionName to de-instrument |
| */ |
| deinstrumentFunction: function(context, functionName) { |
| if (context[functionName].constructor === Function && |
| context[functionName]._instrumented && |
| context[functionName]._instrumented.constructor === Function) { |
| context[functionName] = context[functionName]._instrumented; |
| } |
| }, |
| |
| /** |
| * Given an Error object, return a formatted Array based on Chrome's stack string. |
| * |
| * @param e - Error object to inspect |
| * @return Array<String> of function calls, files and line numbers |
| */ |
| chrome: function(e) { |
| return (e.stack + '\n') |
| .replace(/^[\s\S]+?\s+at\s+/, ' at ') // remove message |
| .replace(/^\s+(at eval )?at\s+/gm, '') // remove 'at' and indentation |
| .replace(/^([^\(]+?)([\n$])/gm, '{anonymous}() ($1)$2') |
| .replace(/^Object.<anonymous>\s*\(([^\)]+)\)/gm, '{anonymous}() ($1)') |
| .replace(/^(.+) \((.+)\)$/gm, '$1@$2') |
| .split('\n') |
| .slice(0, -1); |
| }, |
| |
| /** |
| * Given an Error object, return a formatted Array based on Safari's stack string. |
| * |
| * @param e - Error object to inspect |
| * @return Array<String> of function calls, files and line numbers |
| */ |
| safari: function(e) { |
| return e.stack.replace(/\[native code\]\n/m, '') |
| .replace(/^(?=\w+Error\:).*$\n/m, '') |
| .replace(/^@/gm, '{anonymous}()@') |
| .split('\n'); |
| }, |
| |
| /** |
| * Given an Error object, return a formatted Array based on IE's stack string. |
| * |
| * @param e - Error object to inspect |
| * @return Array<String> of function calls, files and line numbers |
| */ |
| ie: function(e) { |
| return e.stack |
| .replace(/^\s*at\s+(.*)$/gm, '$1') |
| .replace(/^Anonymous function\s+/gm, '{anonymous}() ') |
| .replace(/^(.+)\s+\((.+)\)$/gm, '$1@$2') |
| .split('\n') |
| .slice(1); |
| }, |
| |
| /** |
| * Given an Error object, return a formatted Array based on Firefox's stack string. |
| * |
| * @param e - Error object to inspect |
| * @return Array<String> of function calls, files and line numbers |
| */ |
| firefox: function(e) { |
| return e.stack.replace(/(?:\n@:0)?\s+$/m, '') |
| .replace(/^(?:\((\S*)\))?@/gm, '{anonymous}($1)@') |
| .split('\n'); |
| }, |
| |
| opera11: function(e) { |
| var ANON = '{anonymous}', lineRE = /^.*line (\d+), column (\d+)(?: in (.+))? in (\S+):$/; |
| var lines = e.stacktrace.split('\n'), result = []; |
| |
| for (var i = 0, len = lines.length; i < len; i += 2) { |
| var match = lineRE.exec(lines[i]); |
| if (match) { |
| var location = match[4] + ':' + match[1] + ':' + match[2]; |
| var fnName = match[3] || "global code"; |
| fnName = fnName.replace(/<anonymous function: (\S+)>/, "$1").replace(/<anonymous function>/, ANON); |
| result.push(fnName + '@' + location + ' -- ' + lines[i + 1].replace(/^\s+/, '')); |
| } |
| } |
| |
| return result; |
| }, |
| |
| opera10b: function(e) { |
| // "<anonymous function: run>([arguments not available])@file://localhost/G:/js/stacktrace.js:27\n" + |
| // "printStackTrace([arguments not available])@file://localhost/G:/js/stacktrace.js:18\n" + |
| // "@file://localhost/G:/js/test/functional/testcase1.html:15" |
| var lineRE = /^(.*)@(.+):(\d+)$/; |
| var lines = e.stacktrace.split('\n'), result = []; |
| |
| for (var i = 0, len = lines.length; i < len; i++) { |
| var match = lineRE.exec(lines[i]); |
| if (match) { |
| var fnName = match[1] ? (match[1] + '()') : "global code"; |
| result.push(fnName + '@' + match[2] + ':' + match[3]); |
| } |
| } |
| |
| return result; |
| }, |
| |
| /** |
| * Given an Error object, return a formatted Array based on Opera 10's stacktrace string. |
| * |
| * @param e - Error object to inspect |
| * @return Array<String> of function calls, files and line numbers |
| */ |
| opera10a: function(e) { |
| // " Line 27 of linked script file://localhost/G:/js/stacktrace.js\n" |
| // " Line 11 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function foo\n" |
| var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i; |
| var lines = e.stacktrace.split('\n'), result = []; |
| |
| for (var i = 0, len = lines.length; i < len; i += 2) { |
| var match = lineRE.exec(lines[i]); |
| if (match) { |
| var fnName = match[3] || ANON; |
| result.push(fnName + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, '')); |
| } |
| } |
| |
| return result; |
| }, |
| |
| // Opera 7.x-9.2x only! |
| opera9: function(e) { |
| // " Line 43 of linked script file://localhost/G:/js/stacktrace.js\n" |
| // " Line 7 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n" |
| var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)/i; |
| var lines = e.message.split('\n'), result = []; |
| |
| for (var i = 2, len = lines.length; i < len; i += 2) { |
| var match = lineRE.exec(lines[i]); |
| if (match) { |
| result.push(ANON + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, '')); |
| } |
| } |
| |
| return result; |
| }, |
| |
| phantomjs: function(e) { |
| var ANON = '{anonymous}', lineRE = /(\S+) \((\S+)\)/i; |
| var lines = e.stack.split('\n'), result = []; |
| |
| for (var i = 1, len = lines.length; i < len; i++) { |
| lines[i] = lines[i].replace(/^\s+at\s+/gm, ''); |
| var match = lineRE.exec(lines[i]); |
| if (match) { |
| result.push(match[1] + '()@' + match[2]); |
| } |
| else { |
| result.push(ANON + '()@' + lines[i]); |
| } |
| } |
| |
| return result; |
| }, |
| |
| // Safari 5-, IE 9-, and others |
| other: function(curr) { |
| var ANON = '{anonymous}', fnRE = /function(?:\s+([\w$]+))?\s*\(/, stack = [], fn, args, maxStackSize = 10; |
| var slice = Array.prototype.slice; |
| while (curr && stack.length < maxStackSize) { |
| fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON; |
| try { |
| args = slice.call(curr['arguments'] || []); |
| } catch (e) { |
| args = ['Cannot access arguments: ' + e]; |
| } |
| stack[stack.length] = fn + '(' + this.stringifyArguments(args) + ')'; |
| try { |
| curr = curr.caller; |
| } catch (e) { |
| stack[stack.length] = 'Cannot access caller: ' + e; |
| break; |
| } |
| } |
| return stack; |
| }, |
| |
| /** |
| * Given arguments array as a String, substituting type names for non-string types. |
| * |
| * @param {Arguments,Array} args |
| * @return {String} stringified arguments |
| */ |
| stringifyArguments: function(args) { |
| var result = []; |
| var slice = Array.prototype.slice; |
| for (var i = 0; i < args.length; ++i) { |
| var arg = args[i]; |
| if (arg === undefined) { |
| result[i] = 'undefined'; |
| } else if (arg === null) { |
| result[i] = 'null'; |
| } else if (arg.constructor) { |
| // TODO constructor comparison does not work for iframes |
| if (arg.constructor === Array) { |
| if (arg.length < 3) { |
| result[i] = '[' + this.stringifyArguments(arg) + ']'; |
| } else { |
| result[i] = '[' + this.stringifyArguments(slice.call(arg, 0, 1)) + '...' + this.stringifyArguments(slice.call(arg, -1)) + ']'; |
| } |
| } else if (arg.constructor === Object) { |
| result[i] = '#object'; |
| } else if (arg.constructor === Function) { |
| result[i] = '#function'; |
| } else if (arg.constructor === String) { |
| result[i] = '"' + arg + '"'; |
| } else if (arg.constructor === Number) { |
| result[i] = arg; |
| } else { |
| result[i] = '?'; |
| } |
| } |
| } |
| return result.join(','); |
| }, |
| |
| sourceCache: {}, |
| |
| /** |
| * @return {String} the text from a given URL |
| */ |
| ajax: function(url) { |
| var req = this.createXMLHTTPObject(); |
| if (req) { |
| try { |
| req.open('GET', url, false); |
| //req.overrideMimeType('text/plain'); |
| //req.overrideMimeType('text/javascript'); |
| req.send(null); |
| //return req.status == 200 ? req.responseText : ''; |
| return req.responseText; |
| } catch (e) { |
| } |
| } |
| return ''; |
| }, |
| |
| /** |
| * Try XHR methods in order and store XHR factory. |
| * |
| * @return {XMLHttpRequest} XHR function or equivalent |
| */ |
| createXMLHTTPObject: function() { |
| var xmlhttp, XMLHttpFactories = [ |
| function() { |
| return new XMLHttpRequest(); |
| }, function() { |
| return new ActiveXObject('Msxml2.XMLHTTP'); |
| }, function() { |
| return new ActiveXObject('Msxml3.XMLHTTP'); |
| }, function() { |
| return new ActiveXObject('Microsoft.XMLHTTP'); |
| } |
| ]; |
| for (var i = 0; i < XMLHttpFactories.length; i++) { |
| try { |
| xmlhttp = XMLHttpFactories[i](); |
| // Use memoization to cache the factory |
| this.createXMLHTTPObject = XMLHttpFactories[i]; |
| return xmlhttp; |
| } catch (e) { |
| } |
| } |
| }, |
| |
| /** |
| * Given a URL, check if it is in the same domain (so we can get the source |
| * via Ajax). |
| * |
| * @param url {String} source url |
| * @return {Boolean} False if we need a cross-domain request |
| */ |
| isSameDomain: function(url) { |
| return typeof location !== "undefined" && url.indexOf(location.hostname) !== -1; // location may not be defined, e.g. when running from nodejs. |
| }, |
| |
| /** |
| * Get source code from given URL if in the same domain. |
| * |
| * @param url {String} JS source URL |
| * @return {Array} Array of source code lines |
| */ |
| getSource: function(url) { |
| // TODO reuse source from script tags? |
| if (!(url in this.sourceCache)) { |
| this.sourceCache[url] = this.ajax(url).split('\n'); |
| } |
| return this.sourceCache[url]; |
| }, |
| |
| guessAnonymousFunctions: function(stack) { |
| for (var i = 0; i < stack.length; ++i) { |
| var reStack = /\{anonymous\}\(.*\)@(.*)/, |
| reRef = /^(.*?)(?::(\d+))(?::(\d+))?(?: -- .+)?$/, |
| frame = stack[i], ref = reStack.exec(frame); |
| |
| if (ref) { |
| var m = reRef.exec(ref[1]); |
| if (m) { // If falsey, we did not get any file/line information |
| var file = m[1], lineno = m[2], charno = m[3] || 0; |
| if (file && this.isSameDomain(file) && lineno) { |
| var functionName = this.guessAnonymousFunction(file, lineno, charno); |
| stack[i] = frame.replace('{anonymous}', functionName); |
| } |
| } |
| } |
| } |
| return stack; |
| }, |
| |
| guessAnonymousFunction: function(url, lineNo, charNo) { |
| var ret; |
| try { |
| ret = this.findFunctionName(this.getSource(url), lineNo); |
| } catch (e) { |
| ret = 'getSource failed with url: ' + url + ', exception: ' + e.toString(); |
| } |
| return ret; |
| }, |
| |
| findFunctionName: function(source, lineNo) { |
| // FIXME findFunctionName fails for compressed source |
| // (more than one function on the same line) |
| // function {name}({args}) m[1]=name m[2]=args |
| var reFunctionDeclaration = /function\s+([^(]*?)\s*\(([^)]*)\)/; |
| // {name} = function ({args}) TODO args capture |
| // /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*function(?:[^(]*)/ |
| var reFunctionExpression = /['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*function\b/; |
| // {name} = eval() |
| var reFunctionEvaluation = /['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*(?:eval|new Function)\b/; |
| // Walk backwards in the source lines until we find |
| // the line which matches one of the patterns above |
| var code = "", line, maxLines = Math.min(lineNo, 20), m, commentPos; |
| for (var i = 0; i < maxLines; ++i) { |
| // lineNo is 1-based, source[] is 0-based |
| line = source[lineNo - i - 1]; |
| commentPos = line.indexOf('//'); |
| if (commentPos >= 0) { |
| line = line.substr(0, commentPos); |
| } |
| // TODO check other types of comments? Commented code may lead to false positive |
| if (line) { |
| code = line + code; |
| m = reFunctionExpression.exec(code); |
| if (m && m[1]) { |
| return m[1]; |
| } |
| m = reFunctionDeclaration.exec(code); |
| if (m && m[1]) { |
| //return m[1] + "(" + (m[2] || "") + ")"; |
| return m[1]; |
| } |
| m = reFunctionEvaluation.exec(code); |
| if (m && m[1]) { |
| return m[1]; |
| } |
| } |
| } |
| return '(?)'; |
| } |
| }; |
| |
| return printStackTrace; |
| })); |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Ryan Bennett |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| // define a shim require function so that a node/browserify require calls dont cause errors when ndn-js is used via <script> tag |
| |
| /** @ignore */ |
| var ndn = ndn || {} |
| /** @ignore */ |
| var exports = ndn; |
| |
| /** @ignore */ |
| var module = {} |
| /** @ignore */ |
| function require(){return ndn;} |
| /** |
| * Copyright (C) 2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| // This is included after stacktrace.js and browserify-require.js so that |
| // require().printStackTrace works. |
| |
| exports.printStackTrace = printStackTrace; |
| /** |
| * This module checks for the availability of various crypto.subtle api's at runtime, |
| * exporting a function that returns the known availability of necessary NDN crypto apis |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Ryan Bennett <nomad.ry@gmail.com> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| function DetectSubtleCrypto(){ |
| var use = false; |
| var baselineSupport = ( |
| (typeof crypto !== 'undefined' && crypto && crypto.subtle) |
| && ( |
| (location.protocol === "https:" || "chrome-extension:" || "chrome:") |
| || (location.hostname === "localhost" || location.hostname === "127.0.0.1") |
| ) |
| ) ? true : false ; |
| if (baselineSupport) { |
| var algo = { name: "RSASSA-PKCS1-v1_5", modulusLength: 2048, hash:{name:"SHA-256"}, publicExponent: new Uint8Array([0x01, 0x00, 0x01])}; |
| var keypair; |
| //try to perform every RSA crypto operation we need, if everything works, set use = true |
| crypto.subtle.generateKey( |
| algo, |
| true, //exportable; |
| ["sign", "verify"]).then(function(key){ |
| keypair = key; |
| return crypto.subtle.sign(algo, key.privateKey, new Uint8Array([1,2,3,4,5])); |
| }).then(function(signature){ |
| return crypto.subtle.verify(algo, keypair.publicKey, signature, new Uint8Array([1,2,3,4,5])); |
| }).then(function(verified){ |
| return crypto.subtle.exportKey("pkcs8",keypair.privateKey); |
| }).then(function(pkcs8){ |
| return crypto.subtle.importKey("pkcs8", pkcs8, algo, true, ["sign"]); |
| }).then(function(importedKey){ |
| return crypto.subtle.exportKey("spki", keypair.publicKey); |
| }).then(function(spki){ |
| return crypto.subtle.importKey("spki", spki, algo, true, ["verify"]); |
| }).then(function(importedKey){ |
| var testDigest = new Uint8Array([1,2,3,4,5]); |
| return crypto.subtle.digest({name:"SHA-256"}, testDigest.buffer); |
| }).then(function(result){ |
| use = true; |
| }, function(err){ |
| console.log("DetectSubtleCrypto encountered error, not using crypto.subtle: ", err) |
| }); |
| } |
| return function useSubtleCrypto(){ |
| return use; |
| } |
| } |
| |
| var UseSubtleCrypto = DetectSubtleCrypto(); |
| |
| exports.UseSubtleCrypto = UseSubtleCrypto; |
| /*! CryptoJS v3.1.2 core-fix.js
|
| * code.google.com/p/crypto-js
|
| * (c) 2009-2013 by Jeff Mott. All rights reserved.
|
| * code.google.com/p/crypto-js/wiki/License
|
| * THIS IS FIX of 'core.js' to fix Hmac issue.
|
| * https://code.google.com/p/crypto-js/issues/detail?id=84
|
| * https://crypto-js.googlecode.com/svn-history/r667/branches/3.x/src/core.js
|
| */
|
| /**
|
| * CryptoJS core components.
|
| */
|
| var CryptoJS = CryptoJS || (function (Math, undefined) {
|
| /**
|
| * CryptoJS namespace.
|
| */
|
| var C = {};
|
|
|
| /**
|
| * Library namespace.
|
| */
|
| var C_lib = C.lib = {};
|
|
|
| /**
|
| * Base object for prototypal inheritance.
|
| */
|
| var Base = C_lib.Base = (function () {
|
| function F() {}
|
|
|
| return {
|
| /**
|
| * Creates a new object that inherits from this object.
|
| *
|
| * @param {Object} overrides Properties to copy into the new object.
|
| *
|
| * @return {Object} The new object.
|
| *
|
| * @static
|
| *
|
| * @example
|
| *
|
| * var MyType = CryptoJS.lib.Base.extend({
|
| * field: 'value',
|
| *
|
| * method: function () {
|
| * }
|
| * });
|
| */
|
| extend: function (overrides) {
|
| // Spawn
|
| F.prototype = this;
|
| var subtype = new F();
|
|
|
| // Augment
|
| if (overrides) {
|
| subtype.mixIn(overrides);
|
| }
|
|
|
| // Create default initializer
|
| if (!subtype.hasOwnProperty('init')) {
|
| subtype.init = function () {
|
| subtype.$super.init.apply(this, arguments);
|
| };
|
| }
|
|
|
| // Initializer's prototype is the subtype object
|
| subtype.init.prototype = subtype;
|
|
|
| // Reference supertype
|
| subtype.$super = this;
|
|
|
| return subtype;
|
| },
|
|
|
| /**
|
| * Extends this object and runs the init method.
|
| * Arguments to create() will be passed to init().
|
| *
|
| * @return {Object} The new object.
|
| *
|
| * @static
|
| *
|
| * @example
|
| *
|
| * var instance = MyType.create();
|
| */
|
| create: function () {
|
| var instance = this.extend();
|
| instance.init.apply(instance, arguments);
|
|
|
| return instance;
|
| },
|
|
|
| /**
|
| * Initializes a newly created object.
|
| * Override this method to add some logic when your objects are created.
|
| *
|
| * @example
|
| *
|
| * var MyType = CryptoJS.lib.Base.extend({
|
| * init: function () {
|
| * // ...
|
| * }
|
| * });
|
| */
|
| init: function () {
|
| },
|
|
|
| /**
|
| * Copies properties into this object.
|
| *
|
| * @param {Object} properties The properties to mix in.
|
| *
|
| * @example
|
| *
|
| * MyType.mixIn({
|
| * field: 'value'
|
| * });
|
| */
|
| mixIn: function (properties) {
|
| for (var propertyName in properties) {
|
| if (properties.hasOwnProperty(propertyName)) {
|
| this[propertyName] = properties[propertyName];
|
| }
|
| }
|
|
|
| // IE won't copy toString using the loop above
|
| if (properties.hasOwnProperty('toString')) {
|
| this.toString = properties.toString;
|
| }
|
| },
|
|
|
| /**
|
| * Creates a copy of this object.
|
| *
|
| * @return {Object} The clone.
|
| *
|
| * @example
|
| *
|
| * var clone = instance.clone();
|
| */
|
| clone: function () {
|
| return this.init.prototype.extend(this);
|
| }
|
| };
|
| }());
|
|
|
| /**
|
| * An array of 32-bit words.
|
| *
|
| * @property {Array} words The array of 32-bit words.
|
| * @property {number} sigBytes The number of significant bytes in this word array.
|
| */
|
| var WordArray = C_lib.WordArray = Base.extend({
|
| /**
|
| * Initializes a newly created word array.
|
| *
|
| * @param {Array} words (Optional) An array of 32-bit words.
|
| * @param {number} sigBytes (Optional) The number of significant bytes in the words.
|
| *
|
| * @example
|
| *
|
| * var wordArray = CryptoJS.lib.WordArray.create();
|
| * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607]);
|
| * var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607], 6);
|
| */
|
| init: function (words, sigBytes) {
|
| words = this.words = words || [];
|
|
|
| if (sigBytes != undefined) {
|
| this.sigBytes = sigBytes;
|
| } else {
|
| this.sigBytes = words.length * 4;
|
| }
|
| },
|
|
|
| /**
|
| * Converts this word array to a string.
|
| *
|
| * @param {Encoder} encoder (Optional) The encoding strategy to use. Default: CryptoJS.enc.Hex
|
| *
|
| * @return {string} The stringified word array.
|
| *
|
| * @example
|
| *
|
| * var string = wordArray + '';
|
| * var string = wordArray.toString();
|
| * var string = wordArray.toString(CryptoJS.enc.Utf8);
|
| */
|
| toString: function (encoder) {
|
| return (encoder || Hex).stringify(this);
|
| },
|
|
|
| /**
|
| * Concatenates a word array to this word array.
|
| *
|
| * @param {WordArray} wordArray The word array to append.
|
| *
|
| * @return {WordArray} This word array.
|
| *
|
| * @example
|
| *
|
| * wordArray1.concat(wordArray2);
|
| */
|
| concat: function (wordArray) {
|
| // Shortcuts
|
| var thisWords = this.words;
|
| var thatWords = wordArray.words;
|
| var thisSigBytes = this.sigBytes;
|
| var thatSigBytes = wordArray.sigBytes;
|
|
|
| // Clamp excess bits
|
| this.clamp();
|
|
|
| // Concat
|
| if (thisSigBytes % 4) {
|
| // Copy one byte at a time
|
| for (var i = 0; i < thatSigBytes; i++) {
|
| var thatByte = (thatWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
|
| thisWords[(thisSigBytes + i) >>> 2] |= thatByte << (24 - ((thisSigBytes + i) % 4) * 8);
|
| }
|
| } else {
|
| // Copy one word at a time
|
| for (var i = 0; i < thatSigBytes; i += 4) {
|
| thisWords[(thisSigBytes + i) >>> 2] = thatWords[i >>> 2];
|
| }
|
| }
|
| this.sigBytes += thatSigBytes;
|
|
|
| // Chainable
|
| return this;
|
| },
|
|
|
| /**
|
| * Removes insignificant bits.
|
| *
|
| * @example
|
| *
|
| * wordArray.clamp();
|
| */
|
| clamp: function () {
|
| // Shortcuts
|
| var words = this.words;
|
| var sigBytes = this.sigBytes;
|
|
|
| // Clamp
|
| words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8);
|
| words.length = Math.ceil(sigBytes / 4);
|
| },
|
|
|
| /**
|
| * Creates a copy of this word array.
|
| *
|
| * @return {WordArray} The clone.
|
| *
|
| * @example
|
| *
|
| * var clone = wordArray.clone();
|
| */
|
| clone: function () {
|
| var clone = Base.clone.call(this);
|
| clone.words = this.words.slice(0);
|
|
|
| return clone;
|
| },
|
|
|
| /**
|
| * Creates a word array filled with random bytes.
|
| *
|
| * @param {number} nBytes The number of random bytes to generate.
|
| *
|
| * @return {WordArray} The random word array.
|
| *
|
| * @static
|
| *
|
| * @example
|
| *
|
| * var wordArray = CryptoJS.lib.WordArray.random(16);
|
| */
|
| random: function (nBytes) {
|
| var words = [];
|
| for (var i = 0; i < nBytes; i += 4) {
|
| words.push((Math.random() * 0x100000000) | 0);
|
| }
|
|
|
| return new WordArray.init(words, nBytes);
|
| }
|
| });
|
|
|
| /**
|
| * Encoder namespace.
|
| */
|
| var C_enc = C.enc = {};
|
|
|
| /**
|
| * Hex encoding strategy.
|
| */
|
| var Hex = C_enc.Hex = {
|
| /**
|
| * Converts a word array to a hex string.
|
| *
|
| * @param {WordArray} wordArray The word array.
|
| *
|
| * @return {string} The hex string.
|
| *
|
| * @static
|
| *
|
| * @example
|
| *
|
| * var hexString = CryptoJS.enc.Hex.stringify(wordArray);
|
| */
|
| stringify: function (wordArray) {
|
| // Shortcuts
|
| var words = wordArray.words;
|
| var sigBytes = wordArray.sigBytes;
|
|
|
| // Convert
|
| var hexChars = [];
|
| for (var i = 0; i < sigBytes; i++) {
|
| var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
|
| hexChars.push((bite >>> 4).toString(16));
|
| hexChars.push((bite & 0x0f).toString(16));
|
| }
|
|
|
| return hexChars.join('');
|
| },
|
|
|
| /**
|
| * Converts a hex string to a word array.
|
| *
|
| * @param {string} hexStr The hex string.
|
| *
|
| * @return {WordArray} The word array.
|
| *
|
| * @static
|
| *
|
| * @example
|
| *
|
| * var wordArray = CryptoJS.enc.Hex.parse(hexString);
|
| */
|
| parse: function (hexStr) {
|
| // Shortcut
|
| var hexStrLength = hexStr.length;
|
|
|
| // Convert
|
| var words = [];
|
| for (var i = 0; i < hexStrLength; i += 2) {
|
| words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4);
|
| }
|
|
|
| return new WordArray.init(words, hexStrLength / 2);
|
| }
|
| };
|
|
|
| /**
|
| * Latin1 encoding strategy.
|
| */
|
| var Latin1 = C_enc.Latin1 = {
|
| /**
|
| * Converts a word array to a Latin1 string.
|
| *
|
| * @param {WordArray} wordArray The word array.
|
| *
|
| * @return {string} The Latin1 string.
|
| *
|
| * @static
|
| *
|
| * @example
|
| *
|
| * var latin1String = CryptoJS.enc.Latin1.stringify(wordArray);
|
| */
|
| stringify: function (wordArray) {
|
| // Shortcuts
|
| var words = wordArray.words;
|
| var sigBytes = wordArray.sigBytes;
|
|
|
| // Convert
|
| var latin1Chars = [];
|
| for (var i = 0; i < sigBytes; i++) {
|
| var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
|
| latin1Chars.push(String.fromCharCode(bite));
|
| }
|
|
|
| return latin1Chars.join('');
|
| },
|
|
|
| /**
|
| * Converts a Latin1 string to a word array.
|
| *
|
| * @param {string} latin1Str The Latin1 string.
|
| *
|
| * @return {WordArray} The word array.
|
| *
|
| * @static
|
| *
|
| * @example
|
| *
|
| * var wordArray = CryptoJS.enc.Latin1.parse(latin1String);
|
| */
|
| parse: function (latin1Str) {
|
| // Shortcut
|
| var latin1StrLength = latin1Str.length;
|
|
|
| // Convert
|
| var words = [];
|
| for (var i = 0; i < latin1StrLength; i++) {
|
| words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8);
|
| }
|
|
|
| return new WordArray.init(words, latin1StrLength);
|
| }
|
| };
|
|
|
| /**
|
| * UTF-8 encoding strategy.
|
| */
|
| var Utf8 = C_enc.Utf8 = {
|
| /**
|
| * Converts a word array to a UTF-8 string.
|
| *
|
| * @param {WordArray} wordArray The word array.
|
| *
|
| * @return {string} The UTF-8 string.
|
| *
|
| * @static
|
| *
|
| * @example
|
| *
|
| * var utf8String = CryptoJS.enc.Utf8.stringify(wordArray);
|
| */
|
| stringify: function (wordArray) {
|
| try {
|
| return decodeURIComponent(escape(Latin1.stringify(wordArray)));
|
| } catch (e) {
|
| throw new Error('Malformed UTF-8 data');
|
| }
|
| },
|
|
|
| /**
|
| * Converts a UTF-8 string to a word array.
|
| *
|
| * @param {string} utf8Str The UTF-8 string.
|
| *
|
| * @return {WordArray} The word array.
|
| *
|
| * @static
|
| *
|
| * @example
|
| *
|
| * var wordArray = CryptoJS.enc.Utf8.parse(utf8String);
|
| */
|
| parse: function (utf8Str) {
|
| return Latin1.parse(unescape(encodeURIComponent(utf8Str)));
|
| }
|
| };
|
|
|
| /**
|
| * Abstract buffered block algorithm template.
|
| *
|
| * The property blockSize must be implemented in a concrete subtype.
|
| *
|
| * @property {number} _minBufferSize The number of blocks that should be kept unprocessed in the buffer. Default: 0
|
| */
|
| var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm = Base.extend({
|
| /**
|
| * Resets this block algorithm's data buffer to its initial state.
|
| *
|
| * @example
|
| *
|
| * bufferedBlockAlgorithm.reset();
|
| */
|
| reset: function () {
|
| // Initial values
|
| this._data = new WordArray.init();
|
| this._nDataBytes = 0;
|
| },
|
|
|
| /**
|
| * Adds new data to this block algorithm's buffer.
|
| *
|
| * @param {WordArray|string} data The data to append. Strings are converted to a WordArray using UTF-8.
|
| *
|
| * @example
|
| *
|
| * bufferedBlockAlgorithm._append('data');
|
| * bufferedBlockAlgorithm._append(wordArray);
|
| */
|
| _append: function (data) {
|
| // Convert string to WordArray, else assume WordArray already
|
| if (typeof data == 'string') {
|
| data = Utf8.parse(data);
|
| }
|
|
|
| // Append
|
| this._data.concat(data);
|
| this._nDataBytes += data.sigBytes;
|
| },
|
|
|
| /**
|
| * Processes available data blocks.
|
| *
|
| * This method invokes _doProcessBlock(offset), which must be implemented by a concrete subtype.
|
| *
|
| * @param {boolean} doFlush Whether all blocks and partial blocks should be processed.
|
| *
|
| * @return {WordArray} The processed data.
|
| *
|
| * @example
|
| *
|
| * var processedData = bufferedBlockAlgorithm._process();
|
| * var processedData = bufferedBlockAlgorithm._process(!!'flush');
|
| */
|
| _process: function (doFlush) {
|
| // Shortcuts
|
| var data = this._data;
|
| var dataWords = data.words;
|
| var dataSigBytes = data.sigBytes;
|
| var blockSize = this.blockSize;
|
| var blockSizeBytes = blockSize * 4;
|
|
|
| // Count blocks ready
|
| var nBlocksReady = dataSigBytes / blockSizeBytes;
|
| if (doFlush) {
|
| // Round up to include partial blocks
|
| nBlocksReady = Math.ceil(nBlocksReady);
|
| } else {
|
| // Round down to include only full blocks,
|
| // less the number of blocks that must remain in the buffer
|
| nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0);
|
| }
|
|
|
| // Count words ready
|
| var nWordsReady = nBlocksReady * blockSize;
|
|
|
| // Count bytes ready
|
| var nBytesReady = Math.min(nWordsReady * 4, dataSigBytes);
|
|
|
| // Process blocks
|
| if (nWordsReady) {
|
| for (var offset = 0; offset < nWordsReady; offset += blockSize) {
|
| // Perform concrete-algorithm logic
|
| this._doProcessBlock(dataWords, offset);
|
| }
|
|
|
| // Remove processed words
|
| var processedWords = dataWords.splice(0, nWordsReady);
|
| data.sigBytes -= nBytesReady;
|
| }
|
|
|
| // Return processed words
|
| return new WordArray.init(processedWords, nBytesReady);
|
| },
|
|
|
| /**
|
| * Creates a copy of this object.
|
| *
|
| * @return {Object} The clone.
|
| *
|
| * @example
|
| *
|
| * var clone = bufferedBlockAlgorithm.clone();
|
| */
|
| clone: function () {
|
| var clone = Base.clone.call(this);
|
| clone._data = this._data.clone();
|
|
|
| return clone;
|
| },
|
|
|
| _minBufferSize: 0
|
| });
|
|
|
| /**
|
| * Abstract hasher template.
|
| *
|
| * @property {number} blockSize The number of 32-bit words this hasher operates on. Default: 16 (512 bits)
|
| */
|
| var Hasher = C_lib.Hasher = BufferedBlockAlgorithm.extend({
|
| /**
|
| * Configuration options.
|
| */
|
| cfg: Base.extend(),
|
|
|
| /**
|
| * Initializes a newly created hasher.
|
| *
|
| * @param {Object} cfg (Optional) The configuration options to use for this hash computation.
|
| *
|
| * @example
|
| *
|
| * var hasher = CryptoJS.algo.SHA256.create();
|
| */
|
| init: function (cfg) {
|
| // Apply config defaults
|
| this.cfg = this.cfg.extend(cfg);
|
|
|
| // Set initial values
|
| this.reset();
|
| },
|
|
|
| /**
|
| * Resets this hasher to its initial state.
|
| *
|
| * @example
|
| *
|
| * hasher.reset();
|
| */
|
| reset: function () {
|
| // Reset data buffer
|
| BufferedBlockAlgorithm.reset.call(this);
|
|
|
| // Perform concrete-hasher logic
|
| this._doReset();
|
| },
|
|
|
| /**
|
| * Updates this hasher with a message.
|
| *
|
| * @param {WordArray|string} messageUpdate The message to append.
|
| *
|
| * @return {Hasher} This hasher.
|
| *
|
| * @example
|
| *
|
| * hasher.update('message');
|
| * hasher.update(wordArray);
|
| */
|
| update: function (messageUpdate) {
|
| // Append
|
| this._append(messageUpdate);
|
|
|
| // Update the hash
|
| this._process();
|
|
|
| // Chainable
|
| return this;
|
| },
|
|
|
| /**
|
| * Finalizes the hash computation.
|
| * Note that the finalize operation is effectively a destructive, read-once operation.
|
| *
|
| * @param {WordArray|string} messageUpdate (Optional) A final message update.
|
| *
|
| * @return {WordArray} The hash.
|
| *
|
| * @example
|
| *
|
| * var hash = hasher.finalize();
|
| * var hash = hasher.finalize('message');
|
| * var hash = hasher.finalize(wordArray);
|
| */
|
| finalize: function (messageUpdate) {
|
| // Final message update
|
| if (messageUpdate) {
|
| this._append(messageUpdate);
|
| }
|
|
|
| // Perform concrete-hasher logic
|
| var hash = this._doFinalize();
|
|
|
| return hash;
|
| },
|
|
|
| blockSize: 512/32,
|
|
|
| /**
|
| * Creates a shortcut function to a hasher's object interface.
|
| *
|
| * @param {Hasher} hasher The hasher to create a helper for.
|
| *
|
| * @return {Function} The shortcut function.
|
| *
|
| * @static
|
| *
|
| * @example
|
| *
|
| * var SHA256 = CryptoJS.lib.Hasher._createHelper(CryptoJS.algo.SHA256);
|
| */
|
| _createHelper: function (hasher) {
|
| return function (message, cfg) {
|
| return new hasher.init(cfg).finalize(message);
|
| };
|
| },
|
|
|
| /**
|
| * Creates a shortcut function to the HMAC's object interface.
|
| *
|
| * @param {Hasher} hasher The hasher to use in this HMAC helper.
|
| *
|
| * @return {Function} The shortcut function.
|
| *
|
| * @static
|
| *
|
| * @example
|
| *
|
| * var HmacSHA256 = CryptoJS.lib.Hasher._createHmacHelper(CryptoJS.algo.SHA256);
|
| */
|
| _createHmacHelper: function (hasher) {
|
| return function (message, key) {
|
| return new C_algo.HMAC.init(hasher, key).finalize(message);
|
| };
|
| }
|
| });
|
|
|
| /**
|
| * Algorithm namespace.
|
| */
|
| var C_algo = C.algo = {};
|
|
|
| return C;
|
| }(Math));
|
|
|
| exports.CryptoJS = CryptoJS;
|
| module.exports = exports;
|
| /*
|
| CryptoJS v3.1.2
|
| code.google.com/p/crypto-js
|
| (c) 2009-2013 by Jeff Mott. All rights reserved.
|
|
|
| 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.
|
|
|
| code.google.com/p/crypto-js/wiki/License
|
| */
|
|
|
| var C = require('./core.js').CryptoJS;
|
| (function (Math) {
|
| // Shortcuts
|
| var C_lib = C.lib;
|
| var WordArray = C_lib.WordArray;
|
| var Hasher = C_lib.Hasher;
|
| var C_algo = C.algo;
|
|
|
| // Initialization and round constants tables
|
| var H = [];
|
| var K = [];
|
|
|
| // Compute constants
|
| (function () {
|
| function isPrime(n) {
|
| var sqrtN = Math.sqrt(n);
|
| for (var factor = 2; factor <= sqrtN; factor++) {
|
| if (!(n % factor)) {
|
| return false;
|
| }
|
| }
|
|
|
| return true;
|
| }
|
|
|
| function getFractionalBits(n) {
|
| return ((n - (n | 0)) * 0x100000000) | 0;
|
| }
|
|
|
| var n = 2;
|
| var nPrime = 0;
|
| while (nPrime < 64) {
|
| if (isPrime(n)) {
|
| if (nPrime < 8) {
|
| H[nPrime] = getFractionalBits(Math.pow(n, 1 / 2));
|
| }
|
| K[nPrime] = getFractionalBits(Math.pow(n, 1 / 3));
|
|
|
| nPrime++;
|
| }
|
|
|
| n++;
|
| }
|
| }());
|
|
|
| // Reusable object
|
| var W = [];
|
|
|
| /**
|
| * SHA-256 hash algorithm.
|
| */
|
| var SHA256 = C_algo.SHA256 = Hasher.extend({
|
| _doReset: function () {
|
| this._hash = new WordArray.init(H.slice(0));
|
| },
|
|
|
| _doProcessBlock: function (M, offset) {
|
| // Shortcut
|
| var H = this._hash.words;
|
|
|
| // Working variables
|
| var a = H[0];
|
| var b = H[1];
|
| var c = H[2];
|
| var d = H[3];
|
| var e = H[4];
|
| var f = H[5];
|
| var g = H[6];
|
| var h = H[7];
|
|
|
| // Computation
|
| for (var i = 0; i < 64; i++) {
|
| if (i < 16) {
|
| W[i] = M[offset + i] | 0;
|
| } else {
|
| var gamma0x = W[i - 15];
|
| var gamma0 = ((gamma0x << 25) | (gamma0x >>> 7)) ^
|
| ((gamma0x << 14) | (gamma0x >>> 18)) ^
|
| (gamma0x >>> 3);
|
|
|
| var gamma1x = W[i - 2];
|
| var gamma1 = ((gamma1x << 15) | (gamma1x >>> 17)) ^
|
| ((gamma1x << 13) | (gamma1x >>> 19)) ^
|
| (gamma1x >>> 10);
|
|
|
| W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16];
|
| }
|
|
|
| var ch = (e & f) ^ (~e & g);
|
| var maj = (a & b) ^ (a & c) ^ (b & c);
|
|
|
| var sigma0 = ((a << 30) | (a >>> 2)) ^ ((a << 19) | (a >>> 13)) ^ ((a << 10) | (a >>> 22));
|
| var sigma1 = ((e << 26) | (e >>> 6)) ^ ((e << 21) | (e >>> 11)) ^ ((e << 7) | (e >>> 25));
|
|
|
| var t1 = h + sigma1 + ch + K[i] + W[i];
|
| var t2 = sigma0 + maj;
|
|
|
| h = g;
|
| g = f;
|
| f = e;
|
| e = (d + t1) | 0;
|
| d = c;
|
| c = b;
|
| b = a;
|
| a = (t1 + t2) | 0;
|
| }
|
|
|
| // Intermediate hash value
|
| H[0] = (H[0] + a) | 0;
|
| H[1] = (H[1] + b) | 0;
|
| H[2] = (H[2] + c) | 0;
|
| H[3] = (H[3] + d) | 0;
|
| H[4] = (H[4] + e) | 0;
|
| H[5] = (H[5] + f) | 0;
|
| H[6] = (H[6] + g) | 0;
|
| H[7] = (H[7] + h) | 0;
|
| },
|
|
|
| _doFinalize: function () {
|
| // Shortcuts
|
| var data = this._data;
|
| var dataWords = data.words;
|
|
|
| var nBitsTotal = this._nDataBytes * 8;
|
| var nBitsLeft = data.sigBytes * 8;
|
|
|
| // Add padding
|
| dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32);
|
| dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000);
|
| dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal;
|
| data.sigBytes = dataWords.length * 4;
|
|
|
| // Hash final blocks
|
| this._process();
|
|
|
| // Return final computed hash
|
| return this._hash;
|
| },
|
|
|
| clone: function () {
|
| var clone = Hasher.clone.call(this);
|
| clone._hash = this._hash.clone();
|
|
|
| return clone;
|
| }
|
| });
|
|
|
| /**
|
| * Shortcut function to the hasher's object interface.
|
| *
|
| * @param {WordArray|string} message The message to hash.
|
| *
|
| * @return {WordArray} The hash.
|
| *
|
| * @static
|
| *
|
| * @example
|
| *
|
| * var hash = CryptoJS.SHA256('message');
|
| * var hash = CryptoJS.SHA256(wordArray);
|
| */
|
| C.SHA256 = Hasher._createHelper(SHA256);
|
|
|
| /**
|
| * Shortcut function to the HMAC's object interface.
|
| *
|
| * @param {WordArray|string} message The message to hash.
|
| * @param {WordArray|string} key The secret key.
|
| *
|
| * @return {WordArray} The HMAC.
|
| *
|
| * @static
|
| *
|
| * @example
|
| *
|
| * var hmac = CryptoJS.HmacSHA256(message, key);
|
| */
|
| C.HmacSHA256 = Hasher._createHmacHelper(SHA256);
|
| }(Math));
|
|
|
| exports.CryptoJS = C;
|
| module.exports = exports;
|
| /* |
| CryptoJS v3.1.2 |
| code.google.com/p/crypto-js |
| (c) 2009-2013 by Jeff Mott. All rights reserved. |
| code.google.com/p/crypto-js/wiki/License |
| */ |
| (function () {
|
| // Shortcuts
|
| var C = CryptoJS;
|
| var C_lib = C.lib;
|
| var Base = C_lib.Base;
|
| var C_enc = C.enc;
|
| var Utf8 = C_enc.Utf8;
|
| var C_algo = C.algo;
|
|
|
| /**
|
| * HMAC algorithm.
|
| */
|
| var HMAC = C_algo.HMAC = Base.extend({
|
| /**
|
| * Initializes a newly created HMAC.
|
| *
|
| * @param {Hasher} hasher The hash algorithm to use.
|
| * @param {WordArray|string} key The secret key.
|
| *
|
| * @example
|
| *
|
| * var hmacHasher = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, key);
|
| */
|
| init: function (hasher, key) {
|
| // Init hasher
|
| hasher = this._hasher = new hasher.init();
|
|
|
| // Convert string to WordArray, else assume WordArray already
|
| if (typeof key == 'string') {
|
| key = Utf8.parse(key);
|
| }
|
|
|
| // Shortcuts
|
| var hasherBlockSize = hasher.blockSize;
|
| var hasherBlockSizeBytes = hasherBlockSize * 4;
|
|
|
| // Allow arbitrary length keys
|
| if (key.sigBytes > hasherBlockSizeBytes) {
|
| key = hasher.finalize(key);
|
| }
|
|
|
| // Clamp excess bits
|
| key.clamp();
|
|
|
| // Clone key for inner and outer pads
|
| var oKey = this._oKey = key.clone();
|
| var iKey = this._iKey = key.clone();
|
|
|
| // Shortcuts
|
| var oKeyWords = oKey.words;
|
| var iKeyWords = iKey.words;
|
|
|
| // XOR keys with pad constants
|
| for (var i = 0; i < hasherBlockSize; i++) {
|
| oKeyWords[i] ^= 0x5c5c5c5c;
|
| iKeyWords[i] ^= 0x36363636;
|
| }
|
| oKey.sigBytes = iKey.sigBytes = hasherBlockSizeBytes;
|
|
|
| // Set initial values
|
| this.reset();
|
| },
|
|
|
| /**
|
| * Resets this HMAC to its initial state.
|
| *
|
| * @example
|
| *
|
| * hmacHasher.reset();
|
| */
|
| reset: function () {
|
| // Shortcut
|
| var hasher = this._hasher;
|
|
|
| // Reset
|
| hasher.reset();
|
| hasher.update(this._iKey);
|
| },
|
|
|
| /**
|
| * Updates this HMAC with a message.
|
| *
|
| * @param {WordArray|string} messageUpdate The message to append.
|
| *
|
| * @return {HMAC} This HMAC instance.
|
| *
|
| * @example
|
| *
|
| * hmacHasher.update('message');
|
| * hmacHasher.update(wordArray);
|
| */
|
| update: function (messageUpdate) {
|
| this._hasher.update(messageUpdate);
|
|
|
| // Chainable
|
| return this;
|
| },
|
|
|
| /**
|
| * Finalizes the HMAC computation.
|
| * Note that the finalize operation is effectively a destructive, read-once operation.
|
| *
|
| * @param {WordArray|string} messageUpdate (Optional) A final message update.
|
| *
|
| * @return {WordArray} The HMAC.
|
| *
|
| * @example
|
| *
|
| * var hmac = hmacHasher.finalize();
|
| * var hmac = hmacHasher.finalize('message');
|
| * var hmac = hmacHasher.finalize(wordArray);
|
| */
|
| finalize: function (messageUpdate) {
|
| // Shortcut
|
| var hasher = this._hasher;
|
|
|
| // Compute HMAC
|
| var innerHash = hasher.finalize(messageUpdate);
|
| hasher.reset();
|
| var hmac = hasher.finalize(this._oKey.clone().concat(innerHash));
|
|
|
| return hmac;
|
| }
|
| });
|
| }());
|
| /*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ |
| */ |
| var b64map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
| var b64pad="="; |
| |
| function hex2b64(h) { |
| var i; |
| var c; |
| var ret = ""; |
| for(i = 0; i+3 <= h.length; i+=3) { |
| c = parseInt(h.substring(i,i+3),16); |
| ret += b64map.charAt(c >> 6) + b64map.charAt(c & 63); |
| } |
| if(i+1 == h.length) { |
| c = parseInt(h.substring(i,i+1),16); |
| ret += b64map.charAt(c << 2); |
| } |
| else if(i+2 == h.length) { |
| c = parseInt(h.substring(i,i+2),16); |
| ret += b64map.charAt(c >> 2) + b64map.charAt((c & 3) << 4); |
| } |
| if (b64pad) while((ret.length & 3) > 0) ret += b64pad; |
| return ret; |
| } |
| |
| // convert a base64 string to hex |
| function b64tohex(s) { |
| var ret = "" |
| var i; |
| var k = 0; // b64 state, 0-3 |
| var slop; |
| var v; |
| for(i = 0; i < s.length; ++i) { |
| if(s.charAt(i) == b64pad) break; |
| v = b64map.indexOf(s.charAt(i)); |
| if(v < 0) continue; |
| if(k == 0) { |
| ret += int2char(v >> 2); |
| slop = v & 3; |
| k = 1; |
| } |
| else if(k == 1) { |
| ret += int2char((slop << 2) | (v >> 4)); |
| slop = v & 0xf; |
| k = 2; |
| } |
| else if(k == 2) { |
| ret += int2char(slop); |
| ret += int2char(v >> 2); |
| slop = v & 3; |
| k = 3; |
| } |
| else { |
| ret += int2char((slop << 2) | (v >> 4)); |
| ret += int2char(v & 0xf); |
| k = 0; |
| } |
| } |
| if(k == 1) |
| ret += int2char(slop << 2); |
| return ret; |
| } |
| |
| // convert a base64 string to a byte/number array |
| function b64toBA(s) { |
| //piggyback on b64tohex for now, optimize later |
| var h = b64tohex(s); |
| var i; |
| var a = new Array(); |
| for(i = 0; 2*i < h.length; ++i) { |
| a[i] = parseInt(h.substring(2*i,2*i+2),16); |
| } |
| return a; |
| } |
| |
| exports.b64tohex = b64tohex; |
| exports.b64toBA = b64toBA; |
| exports.hex2b64 = hex2b64; |
| |
| module.exports = exports; |
| /*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ |
| */ |
| // prng4.js - uses Arcfour as a PRNG |
| |
| function Arcfour() { |
| this.i = 0; |
| this.j = 0; |
| this.S = new Array(); |
| } |
| |
| // Initialize arcfour context from key, an array of ints, each from [0..255] |
| function ARC4init(key) { |
| var i, j, t; |
| for(i = 0; i < 256; ++i) |
| this.S[i] = i; |
| j = 0; |
| for(i = 0; i < 256; ++i) { |
| j = (j + this.S[i] + key[i % key.length]) & 255; |
| t = this.S[i]; |
| this.S[i] = this.S[j]; |
| this.S[j] = t; |
| } |
| this.i = 0; |
| this.j = 0; |
| } |
| |
| function ARC4next() { |
| var t; |
| this.i = (this.i + 1) & 255; |
| this.j = (this.j + this.S[this.i]) & 255; |
| t = this.S[this.i]; |
| this.S[this.i] = this.S[this.j]; |
| this.S[this.j] = t; |
| return this.S[(t + this.S[this.i]) & 255]; |
| } |
| |
| Arcfour.prototype.init = ARC4init; |
| Arcfour.prototype.next = ARC4next; |
| |
| // Plug in your RNG constructor here |
| function prng_newstate() { |
| return new Arcfour(); |
| } |
| |
| // Pool size must be a multiple of 4 and greater than 32. |
| // An array of bytes the size of the pool will be passed to init() |
| var rng_psize = 256; |
| /*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ |
| */ |
| // Random number generator - requires a PRNG backend, e.g. prng4.js |
| |
| // For best results, put code like |
| // <body onClick='rng_seed_time();' onKeyPress='rng_seed_time();'> |
| // in your main HTML document. |
| |
| var rng_state; |
| var rng_pool; |
| var rng_pptr; |
| |
| // Mix in a 32-bit integer into the pool |
| function rng_seed_int(x) { |
| rng_pool[rng_pptr++] ^= x & 255; |
| rng_pool[rng_pptr++] ^= (x >> 8) & 255; |
| rng_pool[rng_pptr++] ^= (x >> 16) & 255; |
| rng_pool[rng_pptr++] ^= (x >> 24) & 255; |
| if(rng_pptr >= rng_psize) rng_pptr -= rng_psize; |
| } |
| |
| // Mix in the current time (w/milliseconds) into the pool |
| function rng_seed_time() { |
| rng_seed_int(new Date().getTime()); |
| } |
| |
| // Initialize the pool with junk if needed. |
| if(rng_pool == null) { |
| rng_pool = new Array(); |
| rng_pptr = 0; |
| var t; |
| if(navigator.appName == "Netscape" && navigator.appVersion < "5" && window.crypto) { |
| // Extract entropy (256 bits) from NS4 RNG if available |
| var z = window.crypto.random(32); |
| for(t = 0; t < z.length; ++t) |
| rng_pool[rng_pptr++] = z.charCodeAt(t) & 255; |
| } |
| while(rng_pptr < rng_psize) { // extract some randomness from Math.random() |
| t = Math.floor(65536 * Math.random()); |
| rng_pool[rng_pptr++] = t >>> 8; |
| rng_pool[rng_pptr++] = t & 255; |
| } |
| rng_pptr = 0; |
| rng_seed_time(); |
| //rng_seed_int(window.screenX); |
| //rng_seed_int(window.screenY); |
| } |
| |
| function rng_get_byte() { |
| if(rng_state == null) { |
| rng_seed_time(); |
| rng_state = prng_newstate(); |
| rng_state.init(rng_pool); |
| for(rng_pptr = 0; rng_pptr < rng_pool.length; ++rng_pptr) |
| rng_pool[rng_pptr] = 0; |
| rng_pptr = 0; |
| //rng_pool = null; |
| } |
| // TODO: allow reseeding after first request |
| return rng_state.next(); |
| } |
| |
| function rng_get_bytes(ba) { |
| var i; |
| for(i = 0; i < ba.length; ++i) ba[i] = rng_get_byte(); |
| } |
| |
| function SecureRandom() {} |
| |
| SecureRandom.prototype.nextBytes = rng_get_bytes; |
| /*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ |
| */ |
| // Depends on jsbn.js and rng.js |
| |
| // Version 1.1: support utf-8 encoding in pkcs1pad2 |
| |
| var intShim = require("jsbn"); |
| |
| // convert a (hex) string to a bignum object |
| function parseBigInt(str,r) { |
| return new BigInteger(str,r); |
| } |
| |
| function linebrk(s,n) { |
| var ret = ""; |
| var i = 0; |
| while(i + n < s.length) { |
| ret += s.substring(i,i+n) + "\n"; |
| i += n; |
| } |
| return ret + s.substring(i,s.length); |
| } |
| |
| function byte2Hex(b) { |
| if(b < 0x10) |
| return "0" + b.toString(16); |
| else |
| return b.toString(16); |
| } |
| |
| // PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint |
| function pkcs1pad2(s,n) { |
| if(n < s.length + 11) { // TODO: fix for utf-8 |
| alert("Message too long for RSA"); |
| return null; |
| } |
| var ba = new Array(); |
| var i = s.length - 1; |
| while(i >= 0 && n > 0) { |
| var c = s.charCodeAt(i--); |
| if(c < 128) { // encode using utf-8 |
| ba[--n] = c; |
| } |
| else if((c > 127) && (c < 2048)) { |
| ba[--n] = (c & 63) | 128; |
| ba[--n] = (c >> 6) | 192; |
| } |
| else { |
| ba[--n] = (c & 63) | 128; |
| ba[--n] = ((c >> 6) & 63) | 128; |
| ba[--n] = (c >> 12) | 224; |
| } |
| } |
| ba[--n] = 0; |
| var rng = new SecureRandom(); |
| var x = new Array(); |
| while(n > 2) { // random non-zero pad |
| x[0] = 0; |
| while(x[0] == 0) rng.nextBytes(x); |
| ba[--n] = x[0]; |
| } |
| ba[--n] = 2; |
| ba[--n] = 0; |
| return new BigInteger(ba); |
| } |
| |
| // PKCS#1 (OAEP) mask generation function |
| function oaep_mgf1_arr(seed, len, hash) |
| { |
| var mask = '', i = 0; |
| |
| while (mask.length < len) |
| { |
| mask += hash(String.fromCharCode.apply(String, seed.concat([ |
| (i & 0xff000000) >> 24, |
| (i & 0x00ff0000) >> 16, |
| (i & 0x0000ff00) >> 8, |
| i & 0x000000ff]))); |
| i += 1; |
| } |
| |
| return mask; |
| } |
| |
| var SHA1_SIZE = 20; |
| |
| // PKCS#1 (OAEP) pad input string s to n bytes, and return a bigint |
| function oaep_pad(s, n, hash) |
| { |
| if (s.length + 2 * SHA1_SIZE + 2 > n) |
| { |
| throw "Message too long for RSA"; |
| } |
| |
| var PS = '', i; |
| |
| for (i = 0; i < n - s.length - 2 * SHA1_SIZE - 2; i += 1) |
| { |
| PS += '\x00'; |
| } |
| |
| var DB = rstr_sha1('') + PS + '\x01' + s; |
| var seed = new Array(SHA1_SIZE); |
| new SecureRandom().nextBytes(seed); |
| |
| var dbMask = oaep_mgf1_arr(seed, DB.length, hash || rstr_sha1); |
| var maskedDB = []; |
| |
| for (i = 0; i < DB.length; i += 1) |
| { |
| maskedDB[i] = DB.charCodeAt(i) ^ dbMask.charCodeAt(i); |
| } |
| |
| var seedMask = oaep_mgf1_arr(maskedDB, seed.length, rstr_sha1); |
| var maskedSeed = [0]; |
| |
| for (i = 0; i < seed.length; i += 1) |
| { |
| maskedSeed[i + 1] = seed[i] ^ seedMask.charCodeAt(i); |
| } |
| |
| return new BigInteger(maskedSeed.concat(maskedDB)); |
| } |
| |
| // "empty" RSA key constructor |
| function RSAKey() { |
| this.n = null; |
| this.e = 0; |
| this.d = null; |
| this.p = null; |
| this.q = null; |
| this.dmp1 = null; |
| this.dmq1 = null; |
| this.coeff = null; |
| } |
| |
| // Set the public key fields N and e from hex strings |
| function RSASetPublic(N,E) { |
| this.isPublic = true; |
| if (typeof N !== "string") |
| { |
| this.n = N; |
| this.e = E; |
| } |
| else if(N != null && E != null && N.length > 0 && E.length > 0) { |
| this.n = parseBigInt(N,16); |
| this.e = parseInt(E,16); |
| } |
| else |
| alert("Invalid RSA public key"); |
| } |
| |
| // Perform raw public operation on "x": return x^e (mod n) |
| function RSADoPublic(x) { |
| return x.modPowInt(this.e, this.n); |
| } |
| |
| // Return the PKCS#1 RSA encryption of "text" as an even-length hex string |
| function RSAEncrypt(text) { |
| var m = pkcs1pad2(text,(this.n.bitLength()+7)>>3); |
| if(m == null) return null; |
| var c = this.doPublic(m); |
| if(c == null) return null; |
| var h = c.toString(16); |
| if((h.length & 1) == 0) return h; else return "0" + h; |
| } |
| |
| // Return the PKCS#1 OAEP RSA encryption of "text" as an even-length hex string |
| function RSAEncryptOAEP(text, hash) { |
| var m = oaep_pad(text, (this.n.bitLength()+7)>>3, hash); |
| if(m == null) return null; |
| var c = this.doPublic(m); |
| if(c == null) return null; |
| var h = c.toString(16); |
| if((h.length & 1) == 0) return h; else return "0" + h; |
| } |
| |
| // Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string |
| //function RSAEncryptB64(text) { |
| // var h = this.encrypt(text); |
| // if(h) return hex2b64(h); else return null; |
| //} |
| |
| // protected |
| RSAKey.prototype.doPublic = RSADoPublic; |
| |
| // public |
| RSAKey.prototype.setPublic = RSASetPublic; |
| RSAKey.prototype.encrypt = RSAEncrypt; |
| RSAKey.prototype.encryptOAEP = RSAEncryptOAEP; |
| //RSAKey.prototype.encrypt_b64 = RSAEncryptB64; |
| |
| RSAKey.prototype.type = "RSA"; |
| |
| exports.RSAKey = RSAKey; |
| module.exports = exports; |
| /*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ |
| */ |
| // Depends on rsa.js and jsbn2.js |
| |
| // Version 1.1: support utf-8 decoding in pkcs1unpad2 |
| |
| var intShim = require("jsbn"); |
| var BigInteger = intShim.BigInteger ? intShim.BigInteger : intShim ; |
| var RSAKey = require('./rsa.js').RSAKey; |
| |
| // Undo PKCS#1 (type 2, random) padding and, if valid, return the plaintext |
| function pkcs1unpad2(d,n) { |
| var b = d.toByteArray(); |
| var i = 0; |
| while(i < b.length && b[i] == 0) ++i; |
| if(b.length-i != n-1 || b[i] != 2) |
| return null; |
| ++i; |
| while(b[i] != 0) |
| if(++i >= b.length) return null; |
| var ret = ""; |
| while(++i < b.length) { |
| var c = b[i] & 255; |
| if(c < 128) { // utf-8 decode |
| ret += String.fromCharCode(c); |
| } |
| else if((c > 191) && (c < 224)) { |
| ret += String.fromCharCode(((c & 31) << 6) | (b[i+1] & 63)); |
| ++i; |
| } |
| else { |
| ret += String.fromCharCode(((c & 15) << 12) | ((b[i+1] & 63) << 6) | (b[i+2] & 63)); |
| i += 2; |
| } |
| } |
| return ret; |
| } |
| |
| // PKCS#1 (OAEP) mask generation function |
| function oaep_mgf1_str(seed, len, hash) |
| { |
| var mask = '', i = 0; |
| |
| while (mask.length < len) |
| { |
| mask += hash(seed + String.fromCharCode.apply(String, [ |
| (i & 0xff000000) >> 24, |
| (i & 0x00ff0000) >> 16, |
| (i & 0x0000ff00) >> 8, |
| i & 0x000000ff])); |
| i += 1; |
| } |
| |
| return mask; |
| } |
| |
| var SHA1_SIZE = 20; |
| |
| // Undo PKCS#1 (OAEP) padding and, if valid, return the plaintext |
| function oaep_unpad(d, n, hash) |
| { |
| d = d.toByteArray(); |
| |
| var i; |
| |
| for (i = 0; i < d.length; i += 1) |
| { |
| d[i] &= 0xff; |
| } |
| |
| while (d.length < n) |
| { |
| d.unshift(0); |
| } |
| |
| d = String.fromCharCode.apply(String, d); |
| |
| if (d.length < 2 * SHA1_SIZE + 2) |
| { |
| throw "Cipher too short"; |
| } |
| |
| var maskedSeed = d.substr(1, SHA1_SIZE) |
| var maskedDB = d.substr(SHA1_SIZE + 1); |
| |
| var seedMask = oaep_mgf1_str(maskedDB, SHA1_SIZE, hash || rstr_sha1); |
| var seed = [], i; |
| |
| for (i = 0; i < maskedSeed.length; i += 1) |
| { |
| seed[i] = maskedSeed.charCodeAt(i) ^ seedMask.charCodeAt(i); |
| } |
| |
| var dbMask = oaep_mgf1_str(String.fromCharCode.apply(String, seed), |
| d.length - SHA1_SIZE, rstr_sha1); |
| |
| var DB = []; |
| |
| for (i = 0; i < maskedDB.length; i += 1) |
| { |
| DB[i] = maskedDB.charCodeAt(i) ^ dbMask.charCodeAt(i); |
| } |
| |
| DB = String.fromCharCode.apply(String, DB); |
| |
| if (DB.substr(0, SHA1_SIZE) !== rstr_sha1('')) |
| { |
| throw "Hash mismatch"; |
| } |
| |
| DB = DB.substr(SHA1_SIZE); |
| |
| var first_one = DB.indexOf('\x01'); |
| var last_zero = (first_one != -1) ? DB.substr(0, first_one).lastIndexOf('\x00') : -1; |
| |
| if (last_zero + 1 != first_one) |
| { |
| throw "Malformed data"; |
| } |
| |
| return DB.substr(first_one + 1); |
| } |
| |
| // Set the private key fields N, e, and d from hex strings |
| function RSASetPrivate(N,E,D) { |
| this.isPrivate = true; |
| if (typeof N !== "string") |
| { |
| this.n = N; |
| this.e = E; |
| this.d = D; |
| } |
| else if(N != null && E != null && N.length > 0 && E.length > 0) { |
| this.n = parseBigInt(N,16); |
| this.e = parseInt(E,16); |
| this.d = parseBigInt(D,16); |
| } |
| else |
| alert("Invalid RSA private key"); |
| } |
| |
| // Set the private key fields N, e, d and CRT params from hex strings |
| function RSASetPrivateEx(N,E,D,P,Q,DP,DQ,C) { |
| this.isPrivate = true; |
| if (N == null) throw "RSASetPrivateEx N == null"; |
| if (E == null) throw "RSASetPrivateEx E == null"; |
| if (N.length == 0) throw "RSASetPrivateEx N.length == 0"; |
| if (E.length == 0) throw "RSASetPrivateEx E.length == 0"; |
| |
| if (N != null && E != null && N.length > 0 && E.length > 0) { |
| this.n = parseBigInt(N,16); |
| this.e = parseInt(E,16); |
| this.d = parseBigInt(D,16); |
| this.p = parseBigInt(P,16); |
| this.q = parseBigInt(Q,16); |
| this.dmp1 = parseBigInt(DP,16); |
| this.dmq1 = parseBigInt(DQ,16); |
| this.coeff = parseBigInt(C,16); |
| } else { |
| alert("Invalid RSA private key in RSASetPrivateEx"); |
| } |
| } |
| |
| // Generate a new random private key B bits long, using public expt E |
| function RSAGenerate(B,E) { |
| var rng = new SecureRandom(); |
| var qs = B>>1; |
| this.e = parseInt(E,16); |
| var ee = new BigInteger(E,16); |
| for(;;) { |
| for(;;) { |
| this.p = new BigInteger(B-qs,1,rng); |
| if(this.p.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) == 0 && this.p.isProbablePrime(10)) break; |
| } |
| for(;;) { |
| this.q = new BigInteger(qs,1,rng); |
| if(this.q.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) == 0 && this.q.isProbablePrime(10)) break; |
| } |
| if(this.p.compareTo(this.q) <= 0) { |
| var t = this.p; |
| this.p = this.q; |
| this.q = t; |
| } |
| var p1 = this.p.subtract(BigInteger.ONE); // p1 = p - 1 |
| var q1 = this.q.subtract(BigInteger.ONE); // q1 = q - 1 |
| var phi = p1.multiply(q1); |
| if(phi.gcd(ee).compareTo(BigInteger.ONE) == 0) { |
| this.n = this.p.multiply(this.q); // this.n = p * q |
| this.d = ee.modInverse(phi); // this.d = |
| this.dmp1 = this.d.mod(p1); // this.dmp1 = d mod (p - 1) |
| this.dmq1 = this.d.mod(q1); // this.dmq1 = d mod (q - 1) |
| this.coeff = this.q.modInverse(this.p); // this.coeff = (q ^ -1) mod p |
| break; |
| } |
| } |
| this.isPrivate = true; |
| } |
| |
| // Perform raw private operation on "x": return x^d (mod n) |
| function RSADoPrivate(x) { |
| if(this.p == null || this.q == null) |
| return x.modPow(this.d, this.n); |
| |
| // TODO: re-calculate any missing CRT params |
| var xp = x.mod(this.p).modPow(this.dmp1, this.p); // xp=cp? |
| var xq = x.mod(this.q).modPow(this.dmq1, this.q); // xq=cq? |
| |
| while(xp.compareTo(xq) < 0) |
| xp = xp.add(this.p); |
| // NOTE: |
| // xp.subtract(xq) => cp -cq |
| // xp.subtract(xq).multiply(this.coeff).mod(this.p) => (cp - cq) * u mod p = h |
| // xp.subtract(xq).multiply(this.coeff).mod(this.p).multiply(this.q).add(xq) => cq + (h * q) = M |
| return xp.subtract(xq).multiply(this.coeff).mod(this.p).multiply(this.q).add(xq); |
| } |
| |
| // Return the PKCS#1 RSA decryption of "ctext". |
| // "ctext" is an even-length hex string and the output is a plain string. |
| function RSADecrypt(ctext) { |
| var c = parseBigInt(ctext, 16); |
| var m = this.doPrivate(c); |
| if(m == null) return null; |
| return pkcs1unpad2(m, (this.n.bitLength()+7)>>3); |
| } |
| |
| // Return the PKCS#1 OAEP RSA decryption of "ctext". |
| // "ctext" is an even-length hex string and the output is a plain string. |
| function RSADecryptOAEP(ctext, hash) { |
| var c = parseBigInt(ctext, 16); |
| var m = this.doPrivate(c); |
| if(m == null) return null; |
| return oaep_unpad(m, (this.n.bitLength()+7)>>3, hash); |
| } |
| |
| // Return the PKCS#1 RSA decryption of "ctext". |
| // "ctext" is a Base64-encoded string and the output is a plain string. |
| //function RSAB64Decrypt(ctext) { |
| // var h = b64tohex(ctext); |
| // if(h) return this.decrypt(h); else return null; |
| //} |
| |
| // protected |
| RSAKey.prototype.doPrivate = RSADoPrivate; |
| |
| // public |
| RSAKey.prototype.setPrivate = RSASetPrivate; |
| RSAKey.prototype.setPrivateEx = RSASetPrivateEx; |
| RSAKey.prototype.generate = RSAGenerate; |
| RSAKey.prototype.decrypt = RSADecrypt; |
| RSAKey.prototype.decryptOAEP = RSADecryptOAEP; |
| //RSAKey.prototype.b64_decrypt = RSAB64Decrypt; |
| |
| exports.RSAKey = RSAKey; |
| module.exports = exports; |
| /*! crypto-1.1.7.js (c) 2013-2015 Kenji Urushima | kjur.github.com/jsrsasign/license |
| */ |
| /* |
| * crypto.js - Cryptographic Algorithm Provider class |
| * |
| * Copyright (c) 2013-2015 Kenji Urushima (kenji.urushima@gmail.com) |
| * |
| * This software is licensed under the terms of the MIT License. |
| * http://kjur.github.com/jsrsasign/license |
| * |
| * The above copyright and license notice shall be |
| * included in all copies or substantial portions of the Software. |
| */ |
| |
| /** |
| * @fileOverview |
| * @name crypto-1.1.js |
| * @author Kenji Urushima kenji.urushima@gmail.com |
| * @version 1.1.7 (2015-Oct-11) |
| * @since jsrsasign 2.2 |
| * @license <a href="http://kjur.github.io/jsrsasign/license/">MIT License</a> |
| */ |
| |
| var CryptoJS = require('./sha256.js').CryptoJS |
| , intShim = require('jsbn'); |
| |
| |
| /** |
| * kjur's class library name space |
| * @name KJUR |
| * @namespace kjur's class library name space |
| */ |
| if (typeof KJUR == "undefined" || !KJUR) KJUR = {}; |
| /** |
| * kjur's cryptographic algorithm provider library name space |
| * <p> |
| * This namespace privides following crytpgrahic classes. |
| * <ul> |
| * <li>{@link KJUR.crypto.MessageDigest} - Java JCE(cryptograhic extension) style MessageDigest class</li> |
| * <li>{@link KJUR.crypto.Signature} - Java JCE(cryptograhic extension) style Signature class</li> |
| * <li>{@link KJUR.crypto.Util} - cryptographic utility functions and properties</li> |
| * </ul> |
| * NOTE: Please ignore method summary and document of this namespace. This caused by a bug of jsdoc2. |
| * </p> |
| * @name KJUR.crypto |
| * @namespace |
| */ |
| if (typeof KJUR.crypto == "undefined" || !KJUR.crypto) KJUR.crypto = {}; |
| |
| /** |
| * static object for cryptographic function utilities |
| * @name KJUR.crypto.Util |
| * @class static object for cryptographic function utilities |
| * @property {Array} DIGESTINFOHEAD PKCS#1 DigestInfo heading hexadecimal bytes for each hash algorithms |
| * @property {Array} DEFAULTPROVIDER associative array of default provider name for each hash and signature algorithms |
| * @description |
| */ |
| KJUR.crypto.Util = new function() { |
| this.DIGESTINFOHEAD = { |
| 'sha1': "3021300906052b0e03021a05000414", |
| 'sha224': "302d300d06096086480165030402040500041c", |
| 'sha256': "3031300d060960864801650304020105000420", |
| 'sha384': "3041300d060960864801650304020205000430", |
| 'sha512': "3051300d060960864801650304020305000440", |
| 'md2': "3020300c06082a864886f70d020205000410", |
| 'md5': "3020300c06082a864886f70d020505000410", |
| 'ripemd160': "3021300906052b2403020105000414" |
| }; |
| |
| /* |
| * @since crypto 1.1.1 |
| */ |
| this.DEFAULTPROVIDER = { |
| 'md5': 'cryptojs', |
| 'sha1': 'cryptojs', |
| 'sha224': 'cryptojs', |
| 'sha256': 'cryptojs', |
| 'sha384': 'cryptojs', |
| 'sha512': 'cryptojs', |
| 'ripemd160': 'cryptojs', |
| 'hmacmd5': 'cryptojs', |
| 'hmacsha1': 'cryptojs', |
| 'hmacsha224': 'cryptojs', |
| 'hmacsha256': 'cryptojs', |
| 'hmacsha384': 'cryptojs', |
| 'hmacsha512': 'cryptojs', |
| 'hmacripemd160': 'cryptojs', |
| |
| 'MD5withRSA': 'cryptojs/jsrsa', |
| 'SHA1withRSA': 'cryptojs/jsrsa', |
| 'SHA224withRSA': 'cryptojs/jsrsa', |
| 'SHA256withRSA': 'cryptojs/jsrsa', |
| 'SHA384withRSA': 'cryptojs/jsrsa', |
| 'SHA512withRSA': 'cryptojs/jsrsa', |
| 'RIPEMD160withRSA': 'cryptojs/jsrsa', |
| |
| 'MD5withECDSA': 'cryptojs/jsrsa', |
| 'SHA1withECDSA': 'cryptojs/jsrsa', |
| 'SHA224withECDSA': 'cryptojs/jsrsa', |
| 'SHA256withECDSA': 'cryptojs/jsrsa', |
| 'SHA384withECDSA': 'cryptojs/jsrsa', |
| 'SHA512withECDSA': 'cryptojs/jsrsa', |
| 'RIPEMD160withECDSA': 'cryptojs/jsrsa', |
| |
| 'SHA1withDSA': 'cryptojs/jsrsa', |
| 'SHA224withDSA': 'cryptojs/jsrsa', |
| 'SHA256withDSA': 'cryptojs/jsrsa', |
| |
| 'MD5withRSAandMGF1': 'cryptojs/jsrsa', |
| 'SHA1withRSAandMGF1': 'cryptojs/jsrsa', |
| 'SHA224withRSAandMGF1': 'cryptojs/jsrsa', |
| 'SHA256withRSAandMGF1': 'cryptojs/jsrsa', |
| 'SHA384withRSAandMGF1': 'cryptojs/jsrsa', |
| 'SHA512withRSAandMGF1': 'cryptojs/jsrsa', |
| 'RIPEMD160withRSAandMGF1': 'cryptojs/jsrsa' |
| }; |
| |
| /* |
| * @since crypto 1.1.2 |
| */ |
| this.CRYPTOJSMESSAGEDIGESTNAME = { |
| 'md5': 'CryptoJS.algo.MD5', |
| 'sha1': 'CryptoJS.algo.SHA1', |
| 'sha224': 'CryptoJS.algo.SHA224', |
| 'sha256': 'CryptoJS.algo.SHA256', |
| 'sha384': 'CryptoJS.algo.SHA384', |
| 'sha512': 'CryptoJS.algo.SHA512', |
| 'ripemd160': 'CryptoJS.algo.RIPEMD160' |
| }; |
| |
| /** |
| * get hexadecimal DigestInfo |
| * @name getDigestInfoHex |
| * @memberOf KJUR.crypto.Util |
| * @function |
| * @param {String} hHash hexadecimal hash value |
| * @param {String} alg hash algorithm name (ex. 'sha1') |
| * @return {String} hexadecimal string DigestInfo ASN.1 structure |
| */ |
| this.getDigestInfoHex = function(hHash, alg) { |
| if (typeof this.DIGESTINFOHEAD[alg] == "undefined") |
| throw "alg not supported in Util.DIGESTINFOHEAD: " + alg; |
| return this.DIGESTINFOHEAD[alg] + hHash; |
| }; |
| |
| /** |
| * get PKCS#1 padded hexadecimal DigestInfo |
| * @name getPaddedDigestInfoHex |
| * @memberOf KJUR.crypto.Util |
| * @function |
| * @param {String} hHash hexadecimal hash value of message to be signed |
| * @param {String} alg hash algorithm name (ex. 'sha1') |
| * @param {Integer} keySize key bit length (ex. 1024) |
| * @return {String} hexadecimal string of PKCS#1 padded DigestInfo |
| */ |
| this.getPaddedDigestInfoHex = function(hHash, alg, keySize) { |
| var hDigestInfo = this.getDigestInfoHex(hHash, alg); |
| var pmStrLen = keySize / 4; // minimum PM length |
| |
| if (hDigestInfo.length + 22 > pmStrLen) // len(0001+ff(*8)+00+hDigestInfo)=22 |
| throw "key is too short for SigAlg: keylen=" + keySize + "," + alg; |
| |
| var hHead = "0001"; |
| var hTail = "00" + hDigestInfo; |
| var hMid = ""; |
| var fLen = pmStrLen - hHead.length - hTail.length; |
| for (var i = 0; i < fLen; i += 2) { |
| hMid += "ff"; |
| } |
| var hPaddedMessage = hHead + hMid + hTail; |
| return hPaddedMessage; |
| }; |
| |
| /** |
| * get hexadecimal hash of string with specified algorithm |
| * @name hashString |
| * @memberOf KJUR.crypto.Util |
| * @function |
| * @param {String} s input string to be hashed |
| * @param {String} alg hash algorithm name |
| * @return {String} hexadecimal string of hash value |
| * @since 1.1.1 |
| */ |
| this.hashString = function(s, alg) { |
| var md = new KJUR.crypto.MessageDigest({'alg': alg}); |
| return md.digestString(s); |
| }; |
| |
| /** |
| * get hexadecimal hash of hexadecimal string with specified algorithm |
| * @name hashHex |
| * @memberOf KJUR.crypto.Util |
| * @function |
| * @param {String} sHex input hexadecimal string to be hashed |
| * @param {String} alg hash algorithm name |
| * @return {String} hexadecimal string of hash value |
| * @since 1.1.1 |
| */ |
| this.hashHex = function(sHex, alg) { |
| var md = new KJUR.crypto.MessageDigest({'alg': alg}); |
| return md.digestHex(sHex); |
| }; |
| |
| /** |
| * get hexadecimal SHA1 hash of string |
| * @name sha1 |
| * @memberOf KJUR.crypto.Util |
| * @function |
| * @param {String} s input string to be hashed |
| * @return {String} hexadecimal string of hash value |
| * @since 1.0.3 |
| */ |
| this.sha1 = function(s) { |
| var md = new KJUR.crypto.MessageDigest({'alg':'sha1', 'prov':'cryptojs'}); |
| return md.digestString(s); |
| }; |
| |
| /** |
| * get hexadecimal SHA256 hash of string |
| * @name sha256 |
| * @memberOf KJUR.crypto.Util |
| * @function |
| * @param {String} s input string to be hashed |
| * @return {String} hexadecimal string of hash value |
| * @since 1.0.3 |
| */ |
| this.sha256 = function(s) { |
| var md = new KJUR.crypto.MessageDigest({'alg':'sha256', 'prov':'cryptojs'}); |
| return md.digestString(s); |
| }; |
| |
| this.sha256Hex = function(s) { |
| var md = new KJUR.crypto.MessageDigest({'alg':'sha256', 'prov':'cryptojs'}); |
| return md.digestHex(s); |
| }; |
| |
| /** |
| * get hexadecimal SHA512 hash of string |
| * @name sha512 |
| * @memberOf KJUR.crypto.Util |
| * @function |
| * @param {String} s input string to be hashed |
| * @return {String} hexadecimal string of hash value |
| * @since 1.0.3 |
| */ |
| this.sha512 = function(s) { |
| var md = new KJUR.crypto.MessageDigest({'alg':'sha512', 'prov':'cryptojs'}); |
| return md.digestString(s); |
| }; |
| |
| this.sha512Hex = function(s) { |
| var md = new KJUR.crypto.MessageDigest({'alg':'sha512', 'prov':'cryptojs'}); |
| return md.digestHex(s); |
| }; |
| |
| /** |
| * get hexadecimal MD5 hash of string |
| * @name md5 |
| * @memberOf KJUR.crypto.Util |
| * @function |
| * @param {String} s input string to be hashed |
| * @return {String} hexadecimal string of hash value |
| * @since 1.0.3 |
| */ |
| this.md5 = function(s) { |
| var md = new KJUR.crypto.MessageDigest({'alg':'md5', 'prov':'cryptojs'}); |
| return md.digestString(s); |
| }; |
| |
| /** |
| * get hexadecimal RIPEMD160 hash of string |
| * @name ripemd160 |
| * @memberOf KJUR.crypto.Util |
| * @function |
| * @param {String} s input string to be hashed |
| * @return {String} hexadecimal string of hash value |
| * @since 1.0.3 |
| */ |
| this.ripemd160 = function(s) { |
| var md = new KJUR.crypto.MessageDigest({'alg':'ripemd160', 'prov':'cryptojs'}); |
| return md.digestString(s); |
| }; |
| |
| /* |
| * @since 1.1.2 |
| */ |
| this.getCryptoJSMDByName = function(s) { |
| |
| }; |
| }; |
| |
| /** |
| * MessageDigest class which is very similar to java.security.MessageDigest class |
| * @name KJUR.crypto.MessageDigest |
| * @class MessageDigest class which is very similar to java.security.MessageDigest class |
| * @param {Array} params parameters for constructor |
| * @description |
| * <br/> |
| * Currently this supports following algorithm and providers combination: |
| * <ul> |
| * <li>md5 - cryptojs</li> |
| * <li>sha1 - cryptojs</li> |
| * <li>sha224 - cryptojs</li> |
| * <li>sha256 - cryptojs</li> |
| * <li>sha384 - cryptojs</li> |
| * <li>sha512 - cryptojs</li> |
| * <li>ripemd160 - cryptojs</li> |
| * <li>sha256 - sjcl (NEW from crypto.js 1.0.4)</li> |
| * </ul> |
| * @example |
| * // CryptoJS provider sample |
| * var md = new KJUR.crypto.MessageDigest({alg: "sha1", prov: "cryptojs"}); |
| * md.updateString('aaa') |
| * var mdHex = md.digest() |
| * |
| * // SJCL(Stanford JavaScript Crypto Library) provider sample |
| * var md = new KJUR.crypto.MessageDigest({alg: "sha256", prov: "sjcl"}); // sjcl supports sha256 only |
| * md.updateString('aaa') |
| * var mdHex = md.digest() |
| */ |
| KJUR.crypto.MessageDigest = function(params) { |
| var md = null; |
| var algName = null; |
| var provName = null; |
| |
| /** |
| * set hash algorithm and provider |
| * @name setAlgAndProvider |
| * @memberOf KJUR.crypto.MessageDigest |
| * @function |
| * @param {String} alg hash algorithm name |
| * @param {String} prov provider name |
| * @description |
| * @example |
| * // for SHA1 |
| * md.setAlgAndProvider('sha1', 'cryptojs'); |
| * // for RIPEMD160 |
| * md.setAlgAndProvider('ripemd160', 'cryptojs'); |
| */ |
| this.setAlgAndProvider = function(alg, prov) { |
| if (alg != null && prov === undefined) prov = KJUR.crypto.Util.DEFAULTPROVIDER[alg]; |
| |
| // for cryptojs |
| if (':md5:sha1:sha224:sha256:sha384:sha512:ripemd160:'.indexOf(alg) != -1 && |
| prov == 'cryptojs') { |
| try { |
| this.md = eval(KJUR.crypto.Util.CRYPTOJSMESSAGEDIGESTNAME[alg]).create(); |
| } catch (ex) { |
| throw "setAlgAndProvider hash alg set fail alg=" + alg + "/" + ex; |
| } |
| this.updateString = function(str) { |
| this.md.update(str); |
| }; |
| this.updateHex = function(hex) { |
| var wHex = CryptoJS.enc.Hex.parse(hex); |
| this.md.update(wHex); |
| }; |
| this.digest = function() { |
| var hash = this.md.finalize(); |
| return hash.toString(CryptoJS.enc.Hex); |
| }; |
| this.digestString = function(str) { |
| this.updateString(str); |
| return this.digest(); |
| }; |
| this.digestHex = function(hex) { |
| this.updateHex(hex); |
| return this.digest(); |
| }; |
| } |
| if (':sha256:'.indexOf(alg) != -1 && |
| prov == 'sjcl') { |
| try { |
| this.md = new sjcl.hash.sha256(); |
| } catch (ex) { |
| throw "setAlgAndProvider hash alg set fail alg=" + alg + "/" + ex; |
| } |
| this.updateString = function(str) { |
| this.md.update(str); |
| }; |
| this.updateHex = function(hex) { |
| var baHex = sjcl.codec.hex.toBits(hex); |
| this.md.update(baHex); |
| }; |
| this.digest = function() { |
| var hash = this.md.finalize(); |
| return sjcl.codec.hex.fromBits(hash); |
| }; |
| this.digestString = function(str) { |
| this.updateString(str); |
| return this.digest(); |
| }; |
| this.digestHex = function(hex) { |
| this.updateHex(hex); |
| return this.digest(); |
| }; |
| } |
| }; |
| |
| /** |
| * update digest by specified string |
| * @name updateString |
| * @memberOf KJUR.crypto.MessageDigest |
| * @function |
| * @param {String} str string to update |
| * @description |
| * @example |
| * md.updateString('New York'); |
| */ |
| this.updateString = function(str) { |
| throw "updateString(str) not supported for this alg/prov: " + this.algName + "/" + this.provName; |
| }; |
| |
| /** |
| * update digest by specified hexadecimal string |
| * @name updateHex |
| * @memberOf KJUR.crypto.MessageDigest |
| * @function |
| * @param {String} hex hexadecimal string to update |
| * @description |
| * @example |
| * md.updateHex('0afe36'); |
| */ |
| this.updateHex = function(hex) { |
| throw "updateHex(hex) not supported for this alg/prov: " + this.algName + "/" + this.provName; |
| }; |
| |
| /** |
| * completes hash calculation and returns hash result |
| * @name digest |
| * @memberOf KJUR.crypto.MessageDigest |
| * @function |
| * @description |
| * @example |
| * md.digest() |
| */ |
| this.digest = function() { |
| throw "digest() not supported for this alg/prov: " + this.algName + "/" + this.provName; |
| }; |
| |
| /** |
| * performs final update on the digest using string, then completes the digest computation |
| * @name digestString |
| * @memberOf KJUR.crypto.MessageDigest |
| * @function |
| * @param {String} str string to final update |
| * @description |
| * @example |
| * md.digestString('aaa') |
| */ |
| this.digestString = function(str) { |
| throw "digestString(str) not supported for this alg/prov: " + this.algName + "/" + this.provName; |
| }; |
| |
| /** |
| * performs final update on the digest using hexadecimal string, then completes the digest computation |
| * @name digestHex |
| * @memberOf KJUR.crypto.MessageDigest |
| * @function |
| * @param {String} hex hexadecimal string to final update |
| * @description |
| * @example |
| * md.digestHex('0f2abd') |
| */ |
| this.digestHex = function(hex) { |
| throw "digestHex(hex) not supported for this alg/prov: " + this.algName + "/" + this.provName; |
| }; |
| |
| if (params !== undefined) { |
| if (params['alg'] !== undefined) { |
| this.algName = params['alg']; |
| if (params['prov'] === undefined) |
| this.provName = KJUR.crypto.Util.DEFAULTPROVIDER[this.algName]; |
| this.setAlgAndProvider(this.algName, this.provName); |
| } |
| } |
| }; |
| |
| /** |
| * Mac(Message Authentication Code) class which is very similar to java.security.Mac class |
| * @name KJUR.crypto.Mac |
| * @class Mac class which is very similar to java.security.Mac class |
| * @param {Array} params parameters for constructor |
| * @description |
| * <br/> |
| * Currently this supports following algorithm and providers combination: |
| * <ul> |
| * <li>hmacmd5 - cryptojs</li> |
| * <li>hmacsha1 - cryptojs</li> |
| * <li>hmacsha224 - cryptojs</li> |
| * <li>hmacsha256 - cryptojs</li> |
| * <li>hmacsha384 - cryptojs</li> |
| * <li>hmacsha512 - cryptojs</li> |
| * </ul> |
| * NOTE: HmacSHA224 and HmacSHA384 issue was fixed since jsrsasign 4.1.4. |
| * Please use 'ext/cryptojs-312-core-fix*.js' instead of 'core.js' of original CryptoJS |
| * to avoid those issue. |
| * <br/> |
| * NOTE2: Hmac signature bug was fixed in jsrsasign 4.9.0 by providing CryptoJS |
| * bug workaround. |
| * <br/> |
| * Please see {@link KJUR.crypto.Mac.setPassword}, how to provide password |
| * in various ways in detail. |
| * @example |
| * var mac = new KJUR.crypto.Mac({alg: "HmacSHA1", "pass": "pass"}); |
| * mac.updateString('aaa') |
| * var macHex = md.doFinal() |
| * |
| * // other password representation |
| * var mac = new KJUR.crypto.Mac({alg: "HmacSHA256", "pass": {"hex": "6161"}}); |
| * var mac = new KJUR.crypto.Mac({alg: "HmacSHA256", "pass": {"utf8": "aa"}}); |
| * var mac = new KJUR.crypto.Mac({alg: "HmacSHA256", "pass": {"rstr": "\x61\x61"}}); |
| * var mac = new KJUR.crypto.Mac({alg: "HmacSHA256", "pass": {"b64": "Mi02/+...a=="}}); |
| * var mac = new KJUR.crypto.Mac({alg: "HmacSHA256", "pass": {"b64u": "Mi02_-...a"}}); |
| */ |
| KJUR.crypto.Mac = function(params) { |
| var mac = null; |
| var pass = null; |
| var algName = null; |
| var provName = null; |
| var algProv = null; |
| |
| this.setAlgAndProvider = function(alg, prov) { |
| alg = alg.toLowerCase(); |
| |
| if (alg == null) alg = "hmacsha1"; |
| |
| alg = alg.toLowerCase(); |
| if (alg.substr(0, 4) != "hmac") { |
| throw "setAlgAndProvider unsupported HMAC alg: " + alg; |
| } |
| |
| if (prov === undefined) prov = KJUR.crypto.Util.DEFAULTPROVIDER[alg]; |
| this.algProv = alg + "/" + prov; |
| |
| var hashAlg = alg.substr(4); |
| |
| // for cryptojs |
| if (':md5:sha1:sha224:sha256:sha384:sha512:ripemd160:'.indexOf(hashAlg) != -1 && |
| prov == 'cryptojs') { |
| try { |
| var mdObj = eval(KJUR.crypto.Util.CRYPTOJSMESSAGEDIGESTNAME[hashAlg]); |
| this.mac = CryptoJS.algo.HMAC.create(mdObj, this.pass); |
| } catch (ex) { |
| throw "setAlgAndProvider hash alg set fail hashAlg=" + hashAlg + "/" + ex; |
| } |
| this.updateString = function(str) { |
| this.mac.update(str); |
| }; |
| this.updateHex = function(hex) { |
| var wHex = CryptoJS.enc.Hex.parse(hex); |
| this.mac.update(wHex); |
| }; |
| this.doFinal = function() { |
| var hash = this.mac.finalize(); |
| return hash.toString(CryptoJS.enc.Hex); |
| }; |
| this.doFinalString = function(str) { |
| this.updateString(str); |
| return this.doFinal(); |
| }; |
| this.doFinalHex = function(hex) { |
| this.updateHex(hex); |
| return this.doFinal(); |
| }; |
| } |
| }; |
| |
| /** |
| * update digest by specified string |
| * @name updateString |
| * @memberOf KJUR.crypto.Mac |
| * @function |
| * @param {String} str string to update |
| * @description |
| * @example |
| * md.updateString('New York'); |
| */ |
| this.updateString = function(str) { |
| throw "updateString(str) not supported for this alg/prov: " + this.algProv; |
| }; |
| |
| /** |
| * update digest by specified hexadecimal string |
| * @name updateHex |
| * @memberOf KJUR.crypto.Mac |
| * @function |
| * @param {String} hex hexadecimal string to update |
| * @description |
| * @example |
| * md.updateHex('0afe36'); |
| */ |
| this.updateHex = function(hex) { |
| throw "updateHex(hex) not supported for this alg/prov: " + this.algProv; |
| }; |
| |
| /** |
| * completes hash calculation and returns hash result |
| * @name doFinal |
| * @memberOf KJUR.crypto.Mac |
| * @function |
| * @description |
| * @example |
| * md.digest() |
| */ |
| this.doFinal = function() { |
| throw "digest() not supported for this alg/prov: " + this.algProv; |
| }; |
| |
| /** |
| * performs final update on the digest using string, then completes the digest computation |
| * @name doFinalString |
| * @memberOf KJUR.crypto.Mac |
| * @function |
| * @param {String} str string to final update |
| * @description |
| * @example |
| * md.digestString('aaa') |
| */ |
| this.doFinalString = function(str) { |
| throw "digestString(str) not supported for this alg/prov: " + this.algProv; |
| }; |
| |
| /** |
| * performs final update on the digest using hexadecimal string, |
| * then completes the digest computation |
| * @name doFinalHex |
| * @memberOf KJUR.crypto.Mac |
| * @function |
| * @param {String} hex hexadecimal string to final update |
| * @description |
| * @example |
| * md.digestHex('0f2abd') |
| */ |
| this.doFinalHex = function(hex) { |
| throw "digestHex(hex) not supported for this alg/prov: " + this.algProv; |
| }; |
| |
| /** |
| * set password for Mac |
| * @name setPassword |
| * @memberOf KJUR.crypto.Mac |
| * @function |
| * @param {Object} pass password for Mac |
| * @since crypto 1.1.7 jsrsasign 4.9.0 |
| * @description |
| * This method will set password for (H)Mac internally. |
| * Argument 'pass' can be specified as following: |
| * <ul> |
| * <li>even length string of 0..9, a..f or A-F: implicitly specified as hexadecimal string</li> |
| * <li>not above string: implicitly specified as raw string</li> |
| * <li>{rstr: "\x65\x70"}: explicitly specified as raw string</li> |
| * <li>{hex: "6570"}: explicitly specified as hexacedimal string</li> |
| * <li>{utf8: "秘密"}: explicitly specified as UTF8 string</li> |
| * <li>{b64: "Mi78..=="}: explicitly specified as Base64 string</li> |
| * <li>{b64u: "Mi7-_"}: explicitly specified as Base64URL string</li> |
| * </ul> |
| * It is *STRONGLY RECOMMENDED* that explicit representation of password argument |
| * to avoid ambiguity. For example string "6161" can mean a string "6161" or |
| * a hexadecimal string of "aa" (i.e. \x61\x61). |
| * @example |
| * mac = KJUR.crypto.Mac({'alg': 'hmacsha256'}); |
| * // set password by implicit raw string |
| * mac.setPassword("\x65\x70\xb9\x0b"); |
| * mac.setPassword("password"); |
| * // set password by implicit hexadecimal string |
| * mac.setPassword("6570b90b"); |
| * mac.setPassword("6570B90B"); |
| * // set password by explicit raw string |
| * mac.setPassword({"rstr": "\x65\x70\xb9\x0b"}); |
| * // set password by explicit hexadecimal string |
| * mac.setPassword({"hex": "6570b90b"}); |
| * // set password by explicit utf8 string |
| * mac.setPassword({"utf8": "passwordパスワード"); |
| * // set password by explicit Base64 string |
| * mac.setPassword({"b64": "Mb+c3f/=="}); |
| * // set password by explicit Base64URL string |
| * mac.setPassword({"b64u": "Mb-c3f_"}); |
| */ |
| this.setPassword = function(pass) { |
| // internal this.pass shall be CryptoJS DWord Object for CryptoJS bug |
| // work around. CrytoJS HMac password can be passed by |
| // raw string as described in the manual however it doesn't |
| // work properly in some case. If password was passed |
| // by CryptoJS DWord which is not described in the manual |
| // it seems to work. (fixed since crypto 1.1.7) |
| |
| if (typeof pass == 'string') { |
| var hPass = pass; |
| if (pass.length % 2 == 1 || ! pass.match(/^[0-9A-Fa-f]+$/)) { // raw str |
| hPass = rstrtohex(pass); |
| } |
| this.pass = CryptoJS.enc.Hex.parse(hPass); |
| return; |
| } |
| |
| if (typeof pass != 'object') |
| throw "KJUR.crypto.Mac unsupported password type: " + pass; |
| |
| var hPass = null; |
| if (pass.hex !== undefined) { |
| if (pass.hex.length % 2 != 0 || ! pass.hex.match(/^[0-9A-Fa-f]+$/)) |
| throw "Mac: wrong hex password: " + pass.hex; |
| hPass = pass.hex; |
| } |
| if (pass.utf8 !== undefined) hPass = utf8tohex(pass.utf8); |
| if (pass.rstr !== undefined) hPass = rstrtohex(pass.rstr); |
| if (pass.b64 !== undefined) hPass = b64tohex(pass.b64); |
| if (pass.b64u !== undefined) hPass = b64utohex(pass.b64u); |
| |
| if (hPass == null) |
| throw "KJUR.crypto.Mac unsupported password type: " + pass; |
| |
| this.pass = CryptoJS.enc.Hex.parse(hPass); |
| }; |
| |
| if (params !== undefined) { |
| if (params.pass !== undefined) { |
| this.setPassword(params.pass); |
| } |
| if (params.alg !== undefined) { |
| this.algName = params.alg; |
| if (params['prov'] === undefined) |
| this.provName = KJUR.crypto.Util.DEFAULTPROVIDER[this.algName]; |
| this.setAlgAndProvider(this.algName, this.provName); |
| } |
| } |
| }; |
| |
| /** |
| * Signature class which is very similar to java.security.Signature class |
| * @name KJUR.crypto.Signature |
| * @class Signature class which is very similar to java.security.Signature class |
| * @param {Array} params parameters for constructor |
| * @property {String} state Current state of this signature object whether 'SIGN', 'VERIFY' or null |
| * @description |
| * <br/> |
| * As for params of constructor's argument, it can be specify following attributes: |
| * <ul> |
| * <li>alg - signature algorithm name (ex. {MD5,SHA1,SHA224,SHA256,SHA384,SHA512,RIPEMD160}with{RSA,ECDSA,DSA})</li> |
| * <li>provider - currently 'cryptojs/jsrsa' only</li> |
| * </ul> |
| * <h4>SUPPORTED ALGORITHMS AND PROVIDERS</h4> |
| * This Signature class supports following signature algorithm and provider names: |
| * <ul> |
| * <li>MD5withRSA - cryptojs/jsrsa</li> |
| * <li>SHA1withRSA - cryptojs/jsrsa</li> |
| * <li>SHA224withRSA - cryptojs/jsrsa</li> |
| * <li>SHA256withRSA - cryptojs/jsrsa</li> |
| * <li>SHA384withRSA - cryptojs/jsrsa</li> |
| * <li>SHA512withRSA - cryptojs/jsrsa</li> |
| * <li>RIPEMD160withRSA - cryptojs/jsrsa</li> |
| * <li>MD5withECDSA - cryptojs/jsrsa</li> |
| * <li>SHA1withECDSA - cryptojs/jsrsa</li> |
| * <li>SHA224withECDSA - cryptojs/jsrsa</li> |
| * <li>SHA256withECDSA - cryptojs/jsrsa</li> |
| * <li>SHA384withECDSA - cryptojs/jsrsa</li> |
| * <li>SHA512withECDSA - cryptojs/jsrsa</li> |
| * <li>RIPEMD160withECDSA - cryptojs/jsrsa</li> |
| * <li>MD5withRSAandMGF1 - cryptojs/jsrsa</li> |
| * <li>SHA1withRSAandMGF1 - cryptojs/jsrsa</li> |
| * <li>SHA224withRSAandMGF1 - cryptojs/jsrsa</li> |
| * <li>SHA256withRSAandMGF1 - cryptojs/jsrsa</li> |
| * <li>SHA384withRSAandMGF1 - cryptojs/jsrsa</li> |
| * <li>SHA512withRSAandMGF1 - cryptojs/jsrsa</li> |
| * <li>RIPEMD160withRSAandMGF1 - cryptojs/jsrsa</li> |
| * <li>SHA1withDSA - cryptojs/jsrsa</li> |
| * <li>SHA224withDSA - cryptojs/jsrsa</li> |
| * <li>SHA256withDSA - cryptojs/jsrsa</li> |
| * </ul> |
| * Here are supported elliptic cryptographic curve names and their aliases for ECDSA: |
| * <ul> |
| * <li>secp256k1</li> |
| * <li>secp256r1, NIST P-256, P-256, prime256v1</li> |
| * <li>secp384r1, NIST P-384, P-384</li> |
| * </ul> |
| * NOTE1: DSA signing algorithm is also supported since crypto 1.1.5. |
| * <h4>EXAMPLES</h4> |
| * @example |
| * // RSA signature generation |
| * var sig = new KJUR.crypto.Signature({"alg": "SHA1withRSA"}); |
| * sig.init(prvKeyPEM); |
| * sig.updateString('aaa'); |
| * var hSigVal = sig.sign(); |
| * |
| * // DSA signature validation |
| * var sig2 = new KJUR.crypto.Signature({"alg": "SHA1withDSA"}); |
| * sig2.init(certPEM); |
| * sig.updateString('aaa'); |
| * var isValid = sig2.verify(hSigVal); |
| * |
| * // ECDSA signing |
| * var sig = new KJUR.crypto.Signature({'alg':'SHA1withECDSA'}); |
| * sig.init(prvKeyPEM); |
| * sig.updateString('aaa'); |
| * var sigValueHex = sig.sign(); |
| * |
| * // ECDSA verifying |
| * var sig2 = new KJUR.crypto.Signature({'alg':'SHA1withECDSA'}); |
| * sig.init(certPEM); |
| * sig.updateString('aaa'); |
| * var isValid = sig.verify(sigValueHex); |
| */ |
| KJUR.crypto.Signature = function(params) { |
| var prvKey = null; // RSAKey/KJUR.crypto.{ECDSA,DSA} object for signing |
| var pubKey = null; // RSAKey/KJUR.crypto.{ECDSA,DSA} object for verifying |
| |
| var md = null; // KJUR.crypto.MessageDigest object |
| var sig = null; |
| var algName = null; |
| var provName = null; |
| var algProvName = null; |
| var mdAlgName = null; |
| var pubkeyAlgName = null; // rsa,ecdsa,rsaandmgf1(=rsapss) |
| var state = null; |
| var pssSaltLen = -1; |
| var initParams = null; |
| |
| var sHashHex = null; // hex hash value for hex |
| var hDigestInfo = null; |
| var hPaddedDigestInfo = null; |
| var hSign = null; |
| |
| this._setAlgNames = function() { |
| if (this.algName.match(/^(.+)with(.+)$/)) { |
| this.mdAlgName = RegExp.$1.toLowerCase(); |
| this.pubkeyAlgName = RegExp.$2.toLowerCase(); |
| } |
| }; |
| |
| this._zeroPaddingOfSignature = function(hex, bitLength) { |
| var s = ""; |
| var nZero = bitLength / 4 - hex.length; |
| for (var i = 0; i < nZero; i++) { |
| s = s + "0"; |
| } |
| return s + hex; |
| }; |
| |
| /** |
| * set signature algorithm and provider |
| * @name setAlgAndProvider |
| * @memberOf KJUR.crypto.Signature |
| * @function |
| * @param {String} alg signature algorithm name |
| * @param {String} prov provider name |
| * @description |
| * @example |
| * md.setAlgAndProvider('SHA1withRSA', 'cryptojs/jsrsa'); |
| */ |
| this.setAlgAndProvider = function(alg, prov) { |
| this._setAlgNames(); |
| if (prov != 'cryptojs/jsrsa') |
| throw "provider not supported: " + prov; |
| |
| if (':md5:sha1:sha224:sha256:sha384:sha512:ripemd160:'.indexOf(this.mdAlgName) != -1) { |
| try { |
| this.md = new KJUR.crypto.MessageDigest({'alg':this.mdAlgName}); |
| } catch (ex) { |
| throw "setAlgAndProvider hash alg set fail alg=" + |
| this.mdAlgName + "/" + ex; |
| } |
| |
| this.init = function(keyparam, pass) { |
| var keyObj = null; |
| try { |
| if (pass === undefined) { |
| keyObj = KEYUTIL.getKey(keyparam); |
| } else { |
| keyObj = KEYUTIL.getKey(keyparam, pass); |
| } |
| } catch (ex) { |
| throw "init failed:" + ex; |
| } |
| |
| if (keyObj.isPrivate === true) { |
| this.prvKey = keyObj; |
| this.state = "SIGN"; |
| } else if (keyObj.isPublic === true) { |
| this.pubKey = keyObj; |
| this.state = "VERIFY"; |
| } else { |
| throw "init failed.:" + keyObj; |
| } |
| }; |
| |
| this.initSign = function(params) { |
| if (typeof params['ecprvhex'] == 'string' && |
| typeof params['eccurvename'] == 'string') { |
| this.ecprvhex = params['ecprvhex']; |
| this.eccurvename = params['eccurvename']; |
| } else { |
| this.prvKey = params; |
| } |
| this.state = "SIGN"; |
| }; |
| |
| this.initVerifyByPublicKey = function(params) { |
| if (typeof params['ecpubhex'] == 'string' && |
| typeof params['eccurvename'] == 'string') { |
| this.ecpubhex = params['ecpubhex']; |
| this.eccurvename = params['eccurvename']; |
| } else if (params instanceof KJUR.crypto.ECDSA) { |
| this.pubKey = params; |
| } else if (params instanceof RSAKey) { |
| this.pubKey = params; |
| } |
| this.state = "VERIFY"; |
| }; |
| |
| this.initVerifyByCertificatePEM = function(certPEM) { |
| var x509 = new X509(); |
| x509.readCertPEM(certPEM); |
| this.pubKey = x509.subjectPublicKeyRSA; |
| this.state = "VERIFY"; |
| }; |
| |
| this.updateString = function(str) { |
| this.md.updateString(str); |
| }; |
| |
| this.updateHex = function(hex) { |
| this.md.updateHex(hex); |
| }; |
| |
| this.sign = function() { |
| this.sHashHex = this.md.digest(); |
| if (typeof this.ecprvhex != "undefined" && |
| typeof this.eccurvename != "undefined") { |
| var ec = new KJUR.crypto.ECDSA({'curve': this.eccurvename}); |
| this.hSign = ec.signHex(this.sHashHex, this.ecprvhex); |
| } else if (this.prvKey instanceof RSAKey && |
| this.pubkeyAlgName == "rsaandmgf1") { |
| this.hSign = this.prvKey.signWithMessageHashPSS(this.sHashHex, |
| this.mdAlgName, |
| this.pssSaltLen); |
| } else if (this.prvKey instanceof RSAKey && |
| this.pubkeyAlgName == "rsa") { |
| this.hSign = this.prvKey.signWithMessageHash(this.sHashHex, |
| this.mdAlgName); |
| } else if (this.prvKey instanceof KJUR.crypto.ECDSA) { |
| this.hSign = this.prvKey.signWithMessageHash(this.sHashHex); |
| } else if (this.prvKey instanceof KJUR.crypto.DSA) { |
| this.hSign = this.prvKey.signWithMessageHash(this.sHashHex); |
| } else { |
| throw "Signature: unsupported public key alg: " + this.pubkeyAlgName; |
| } |
| return this.hSign; |
| }; |
| this.signString = function(str) { |
| this.updateString(str); |
| return this.sign(); |
| }; |
| this.signHex = function(hex) { |
| this.updateHex(hex); |
| return this.sign(); |
| }; |
| this.verify = function(hSigVal) { |
| this.sHashHex = this.md.digest(); |
| if (typeof this.ecpubhex != "undefined" && |
| typeof this.eccurvename != "undefined") { |
| var ec = new KJUR.crypto.ECDSA({curve: this.eccurvename}); |
| return ec.verifyHex(this.sHashHex, hSigVal, this.ecpubhex); |
| } else if (this.pubKey instanceof RSAKey && |
| this.pubkeyAlgName == "rsaandmgf1") { |
| return this.pubKey.verifyWithMessageHashPSS(this.sHashHex, hSigVal, |
| this.mdAlgName, |
| this.pssSaltLen); |
| } else if (this.pubKey instanceof RSAKey && |
| this.pubkeyAlgName == "rsa") { |
| return this.pubKey.verifyWithMessageHash(this.sHashHex, hSigVal); |
| } else if (this.pubKey instanceof KJUR.crypto.ECDSA) { |
| return this.pubKey.verifyWithMessageHash(this.sHashHex, hSigVal); |
| } else if (this.pubKey instanceof KJUR.crypto.DSA) { |
| return this.pubKey.verifyWithMessageHash(this.sHashHex, hSigVal); |
| } else { |
| throw "Signature: unsupported public key alg: " + this.pubkeyAlgName; |
| } |
| }; |
| } |
| }; |
| |
| /** |
| * Initialize this object for signing or verifying depends on key |
| * @name init |
| * @memberOf KJUR.crypto.Signature |
| * @function |
| * @param {Object} key specifying public or private key as plain/encrypted PKCS#5/8 PEM file, certificate PEM or {@link RSAKey}, {@link KJUR.crypto.DSA} or {@link KJUR.crypto.ECDSA} object |
| * @param {String} pass (OPTION) passcode for encrypted private key |
| * @since crypto 1.1.3 |
| * @description |
| * This method is very useful initialize method for Signature class since |
| * you just specify key then this method will automatically initialize it |
| * using {@link KEYUTIL.getKey} method. |
| * As for 'key', following argument type are supported: |
| * <h5>signing</h5> |
| * <ul> |
| * <li>PEM formatted PKCS#8 encrypted RSA/ECDSA private key concluding "BEGIN ENCRYPTED PRIVATE KEY"</li> |
| * <li>PEM formatted PKCS#5 encrypted RSA/DSA private key concluding "BEGIN RSA/DSA PRIVATE KEY" and ",ENCRYPTED"</li> |
| * <li>PEM formatted PKCS#8 plain RSA/ECDSA private key concluding "BEGIN PRIVATE KEY"</li> |
| * <li>PEM formatted PKCS#5 plain RSA/DSA private key concluding "BEGIN RSA/DSA PRIVATE KEY" without ",ENCRYPTED"</li> |
| * <li>RSAKey object of private key</li> |
| * <li>KJUR.crypto.ECDSA object of private key</li> |
| * <li>KJUR.crypto.DSA object of private key</li> |
| * </ul> |
| * <h5>verification</h5> |
| * <ul> |
| * <li>PEM formatted PKCS#8 RSA/EC/DSA public key concluding "BEGIN PUBLIC KEY"</li> |
| * <li>PEM formatted X.509 certificate with RSA/EC/DSA public key concluding |
| * "BEGIN CERTIFICATE", "BEGIN X509 CERTIFICATE" or "BEGIN TRUSTED CERTIFICATE".</li> |
| * <li>RSAKey object of public key</li> |
| * <li>KJUR.crypto.ECDSA object of public key</li> |
| * <li>KJUR.crypto.DSA object of public key</li> |
| * </ul> |
| * @example |
| * sig.init(sCertPEM) |
| */ |
| this.init = function(key, pass) { |
| throw "init(key, pass) not supported for this alg:prov=" + |
| this.algProvName; |
| }; |
| |
| /** |
| * Initialize this object for verifying with a public key |
| * @name initVerifyByPublicKey |
| * @memberOf KJUR.crypto.Signature |
| * @function |
| * @param {Object} param RSAKey object of public key or associative array for ECDSA |
| * @since 1.0.2 |
| * @deprecated from crypto 1.1.5. please use init() method instead. |
| * @description |
| * Public key information will be provided as 'param' parameter and the value will be |
| * following: |
| * <ul> |
| * <li>{@link RSAKey} object for RSA verification</li> |
| * <li>associative array for ECDSA verification |
| * (ex. <code>{'ecpubhex': '041f..', 'eccurvename': 'secp256r1'}</code>) |
| * </li> |
| * </ul> |
| * @example |
| * sig.initVerifyByPublicKey(rsaPrvKey) |
| */ |
| this.initVerifyByPublicKey = function(rsaPubKey) { |
| throw "initVerifyByPublicKey(rsaPubKeyy) not supported for this alg:prov=" + |
| this.algProvName; |
| }; |
| |
| /** |
| * Initialize this object for verifying with a certficate |
| * @name initVerifyByCertificatePEM |
| * @memberOf KJUR.crypto.Signature |
| * @function |
| * @param {String} certPEM PEM formatted string of certificate |
| * @since 1.0.2 |
| * @deprecated from crypto 1.1.5. please use init() method instead. |
| * @description |
| * @example |
| * sig.initVerifyByCertificatePEM(certPEM) |
| */ |
| this.initVerifyByCertificatePEM = function(certPEM) { |
| throw "initVerifyByCertificatePEM(certPEM) not supported for this alg:prov=" + |
| this.algProvName; |
| }; |
| |
| /** |
| * Initialize this object for signing |
| * @name initSign |
| * @memberOf KJUR.crypto.Signature |
| * @function |
| * @param {Object} param RSAKey object of public key or associative array for ECDSA |
| * @deprecated from crypto 1.1.5. please use init() method instead. |
| * @description |
| * Private key information will be provided as 'param' parameter and the value will be |
| * following: |
| * <ul> |
| * <li>{@link RSAKey} object for RSA signing</li> |
| * <li>associative array for ECDSA signing |
| * (ex. <code>{'ecprvhex': '1d3f..', 'eccurvename': 'secp256r1'}</code>)</li> |
| * </ul> |
| * @example |
| * sig.initSign(prvKey) |
| */ |
| this.initSign = function(prvKey) { |
| throw "initSign(prvKey) not supported for this alg:prov=" + this.algProvName; |
| }; |
| |
| /** |
| * Updates the data to be signed or verified by a string |
| * @name updateString |
| * @memberOf KJUR.crypto.Signature |
| * @function |
| * @param {String} str string to use for the update |
| * @description |
| * @example |
| * sig.updateString('aaa') |
| */ |
| this.updateString = function(str) { |
| throw "updateString(str) not supported for this alg:prov=" + this.algProvName; |
| }; |
| |
| /** |
| * Updates the data to be signed or verified by a hexadecimal string |
| * @name updateHex |
| * @memberOf KJUR.crypto.Signature |
| * @function |
| * @param {String} hex hexadecimal string to use for the update |
| * @description |
| * @example |
| * sig.updateHex('1f2f3f') |
| */ |
| this.updateHex = function(hex) { |
| throw "updateHex(hex) not supported for this alg:prov=" + this.algProvName; |
| }; |
| |
| /** |
| * Returns the signature bytes of all data updates as a hexadecimal string |
| * @name sign |
| * @memberOf KJUR.crypto.Signature |
| * @function |
| * @return the signature bytes as a hexadecimal string |
| * @description |
| * @example |
| * var hSigValue = sig.sign() |
| */ |
| this.sign = function() { |
| throw "sign() not supported for this alg:prov=" + this.algProvName; |
| }; |
| |
| /** |
| * performs final update on the sign using string, then returns the signature bytes of all data updates as a hexadecimal string |
| * @name signString |
| * @memberOf KJUR.crypto.Signature |
| * @function |
| * @param {String} str string to final update |
| * @return the signature bytes of a hexadecimal string |
| * @description |
| * @example |
| * var hSigValue = sig.signString('aaa') |
| */ |
| this.signString = function(str) { |
| throw "digestString(str) not supported for this alg:prov=" + this.algProvName; |
| }; |
| |
| /** |
| * performs final update on the sign using hexadecimal string, then returns the signature bytes of all data updates as a hexadecimal string |
| * @name signHex |
| * @memberOf KJUR.crypto.Signature |
| * @function |
| * @param {String} hex hexadecimal string to final update |
| * @return the signature bytes of a hexadecimal string |
| * @description |
| * @example |
| * var hSigValue = sig.signHex('1fdc33') |
| */ |
| this.signHex = function(hex) { |
| throw "digestHex(hex) not supported for this alg:prov=" + this.algProvName; |
| }; |
| |
| /** |
| * verifies the passed-in signature. |
| * @name verify |
| * @memberOf KJUR.crypto.Signature |
| * @function |
| * @param {String} str string to final update |
| * @return {Boolean} true if the signature was verified, otherwise false |
| * @description |
| * @example |
| * var isValid = sig.verify('1fbcefdca4823a7(snip)') |
| */ |
| this.verify = function(hSigVal) { |
| throw "verify(hSigVal) not supported for this alg:prov=" + this.algProvName; |
| }; |
| |
| this.initParams = params; |
| |
| if (params !== undefined) { |
| if (params['alg'] !== undefined) { |
| this.algName = params['alg']; |
| if (params['prov'] === undefined) { |
| this.provName = KJUR.crypto.Util.DEFAULTPROVIDER[this.algName]; |
| } else { |
| this.provName = params['prov']; |
| } |
| this.algProvName = this.algName + ":" + this.provName; |
| this.setAlgAndProvider(this.algName, this.provName); |
| this._setAlgNames(); |
| } |
| |
| if (params['psssaltlen'] !== undefined) this.pssSaltLen = params['psssaltlen']; |
| |
| if (params['prvkeypem'] !== undefined) { |
| if (params['prvkeypas'] !== undefined) { |
| throw "both prvkeypem and prvkeypas parameters not supported"; |
| } else { |
| try { |
| var prvKey = new RSAKey(); |
| prvKey.readPrivateKeyFromPEMString(params['prvkeypem']); |
| this.initSign(prvKey); |
| } catch (ex) { |
| throw "fatal error to load pem private key: " + ex; |
| } |
| } |
| } |
| } |
| }; |
| |
| /** |
| * static object for cryptographic function utilities |
| * @name KJUR.crypto.OID |
| * @class static object for cryptography related OIDs |
| * @property {Array} oidhex2name key value of hexadecimal OID and its name |
| * (ex. '2a8648ce3d030107' and 'secp256r1') |
| * @since crypto 1.1.3 |
| * @description |
| */ |
| |
| |
| KJUR.crypto.OID = new function() { |
| this.oidhex2name = { |
| '2a864886f70d010101': 'rsaEncryption', |
| '2a8648ce3d0201': 'ecPublicKey', |
| '2a8648ce380401': 'dsa', |
| '2a8648ce3d030107': 'secp256r1', |
| '2b8104001f': 'secp192k1', |
| '2b81040021': 'secp224r1', |
| '2b8104000a': 'secp256k1', |
| '2b81040023': 'secp521r1', |
| '2b81040022': 'secp384r1', |
| '2a8648ce380403': 'SHA1withDSA', // 1.2.840.10040.4.3 |
| '608648016503040301': 'SHA224withDSA', // 2.16.840.1.101.3.4.3.1 |
| '608648016503040302': 'SHA256withDSA' // 2.16.840.1.101.3.4.3.2 |
| }; |
| }; |
| |
| exports.KJUR = KJUR; |
| module.exports = exports; |
| /*! rsapem-1.1.js (c) 2012 Kenji Urushima | kjur.github.com/jsrsasign/license |
| */ |
| // |
| // rsa-pem.js - adding function for reading/writing PKCS#1 PEM private key |
| // to RSAKey class. |
| // |
| // version: 1.1.1 (2013-Apr-12) |
| // |
| // Copyright (c) 2010-2013 Kenji Urushima (kenji.urushima@gmail.com) |
| // |
| // This software is licensed under the terms of the MIT License. |
| // http://kjur.github.com/jsrsasign/license/ |
| // |
| // The above copyright and license notice shall be |
| // included in all copies or substantial portions of the Software. |
| // |
| // |
| // Depends on: |
| // |
| // |
| // |
| // _RSApem_pemToBase64(sPEM) |
| // |
| // removing PEM header, PEM footer and space characters including |
| // new lines from PEM formatted RSA private key string. |
| // |
| |
| /** |
| * @fileOverview |
| * @name rsapem-1.1.js |
| * @author Kenji Urushima kenji.urushima@gmail.com |
| * @version 1.1 |
| * @license <a href="http://kjur.github.io/jsrsasign/license/">MIT License</a> |
| */ |
| |
| var ASN1HEX = require('./asn1hex-1.1.js').ASN1HEX; |
| var b64tohex = require('./base64.js').b64tohex; |
| var RSAKey = require('./rsa2.js').RSAKey; |
| |
| function _rsapem_pemToBase64(sPEMPrivateKey) { |
| var s = sPEMPrivateKey; |
| s = s.replace("-----BEGIN RSA PRIVATE KEY-----", ""); |
| s = s.replace("-----END RSA PRIVATE KEY-----", ""); |
| s = s.replace(/[ \n]+/g, ""); |
| return s; |
| } |
| |
| function _rsapem_getPosArrayOfChildrenFromHex(hPrivateKey) { |
| var a = new Array(); |
| var v1 = ASN1HEX.getStartPosOfV_AtObj(hPrivateKey, 0); |
| var n1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, v1); |
| var e1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, n1); |
| var d1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, e1); |
| var p1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, d1); |
| var q1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, p1); |
| var dp1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, q1); |
| var dq1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, dp1); |
| var co1 = ASN1HEX.getPosOfNextSibling_AtObj(hPrivateKey, dq1); |
| a.push(v1, n1, e1, d1, p1, q1, dp1, dq1, co1); |
| return a; |
| } |
| |
| function _rsapem_getHexValueArrayOfChildrenFromHex(hPrivateKey) { |
| var posArray = _rsapem_getPosArrayOfChildrenFromHex(hPrivateKey); |
| var v = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[0]); |
| var n = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[1]); |
| var e = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[2]); |
| var d = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[3]); |
| var p = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[4]); |
| var q = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[5]); |
| var dp = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[6]); |
| var dq = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[7]); |
| var co = ASN1HEX.getHexOfV_AtObj(hPrivateKey, posArray[8]); |
| var a = new Array(); |
| a.push(v, n, e, d, p, q, dp, dq, co); |
| return a; |
| } |
| |
| /** |
| * read RSA private key from a ASN.1 hexadecimal string |
| * @name readPrivateKeyFromASN1HexString |
| * @memberOf RSAKey# |
| * @function |
| * @param {String} keyHex ASN.1 hexadecimal string of PKCS#1 private key. |
| * @since 1.1.1 |
| */ |
| function _rsapem_readPrivateKeyFromASN1HexString(keyHex) { |
| var a = _rsapem_getHexValueArrayOfChildrenFromHex(keyHex); |
| this.setPrivateEx(a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8]); |
| } |
| |
| /** |
| * read PKCS#1 private key from a string |
| * @name readPrivateKeyFromPEMString |
| * @memberOf RSAKey# |
| * @function |
| * @param {String} keyPEM string of PKCS#1 private key. |
| */ |
| function _rsapem_readPrivateKeyFromPEMString(keyPEM) { |
| var keyB64 = _rsapem_pemToBase64(keyPEM); |
| var keyHex = b64tohex(keyB64) // depends base64.js |
| var a = _rsapem_getHexValueArrayOfChildrenFromHex(keyHex); |
| this.setPrivateEx(a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8]); |
| } |
| |
| RSAKey.prototype.readPrivateKeyFromPEMString = _rsapem_readPrivateKeyFromPEMString; |
| RSAKey.prototype.readPrivateKeyFromASN1HexString = _rsapem_readPrivateKeyFromASN1HexString; |
| |
| exports.RSAKey = RSAKey; |
| module.exports = exports; |
| /*! rsasign-1.2.7.js (c) 2012 Kenji Urushima | kjur.github.com/jsrsasign/license |
| */ |
| /* |
| * rsa-sign.js - adding signing functions to RSAKey class. |
| * |
| * version: 1.2.7 (2013 Aug 25) |
| * |
| * Copyright (c) 2010-2013 Kenji Urushima (kenji.urushima@gmail.com) |
| * |
| * This software is licensed under the terms of the MIT License. |
| * http://kjur.github.com/jsrsasign/license/ |
| * |
| * The above copyright and license notice shall be |
| * included in all copies or substantial portions of the Software. |
| */ |
| |
| /** |
| * @fileOverview |
| * @name rsasign-1.2.js |
| * @author Kenji Urushima kenji.urushima@gmail.com |
| * @version rsasign 1.2.7 |
| * @license <a href="http://kjur.github.io/jsrsasign/license/">MIT License</a> |
| */ |
| |
| var intShim = require('jsbn'); |
| var BigInteger = intShim.BigInteger ? intShim.BigInteger : intShim; |
| var RSAKey = require('./rsapem-1.1.js').RSAKey |
| |
| var _RE_HEXDECONLY = new RegExp(""); |
| _RE_HEXDECONLY.compile("[^0-9a-f]", "gi"); |
| |
| // ======================================================================== |
| // Signature Generation |
| // ======================================================================== |
| |
| function _rsasign_getHexPaddedDigestInfoForString(s, keySize, hashAlg) { |
| var hashFunc = function(s) { return KJUR.crypto.Util.hashString(s, hashAlg); }; |
| var sHashHex = hashFunc(s); |
| |
| return KJUR.crypto.Util.getPaddedDigestInfoHex(sHashHex, hashAlg, keySize); |
| } |
| |
| function _zeroPaddingOfSignature(hex, bitLength) { |
| var s = ""; |
| var nZero = bitLength / 4 - hex.length; |
| for (var i = 0; i < nZero; i++) { |
| s = s + "0"; |
| } |
| return s + hex; |
| } |
| |
| /** |
| * sign for a message string with RSA private key.<br/> |
| * @name signString |
| * @memberOf RSAKey |
| * @function |
| * @param {String} s message string to be signed. |
| * @param {String} hashAlg hash algorithm name for signing.<br/> |
| * @return returns hexadecimal string of signature value. |
| */ |
| function _rsasign_signString(s, hashAlg) { |
| var hashFunc = function(s) { return KJUR.crypto.Util.hashString(s, hashAlg); }; |
| var sHashHex = hashFunc(s); |
| |
| return this.signWithMessageHash(sHashHex, hashAlg); |
| } |
| |
| /** |
| * sign hash value of message to be signed with RSA private key.<br/> |
| * @name signWithMessageHash |
| * @memberOf RSAKey |
| * @function |
| * @param {String} sHashHex hexadecimal string of hash value of message to be signed. |
| * @param {String} hashAlg hash algorithm name for signing.<br/> |
| * @return returns hexadecimal string of signature value. |
| * @since rsasign 1.2.6 |
| */ |
| function _rsasign_signWithMessageHash(sHashHex, hashAlg) { |
| var hPM = KJUR.crypto.Util.getPaddedDigestInfoHex(sHashHex, hashAlg, this.n.bitLength()); |
| var biPaddedMessage = parseBigInt(hPM, 16); |
| var biSign = this.doPrivate(biPaddedMessage); |
| var hexSign = biSign.toString(16); |
| return _zeroPaddingOfSignature(hexSign, this.n.bitLength()); |
| } |
| |
| function _rsasign_signStringWithSHA1(s) { |
| return _rsasign_signString.call(this, s, 'sha1'); |
| } |
| |
| function _rsasign_signStringWithSHA256(s) { |
| return _rsasign_signString.call(this, s, 'sha256'); |
| } |
| |
| // PKCS#1 (PSS) mask generation function |
| function pss_mgf1_str(seed, len, hash) { |
| var mask = '', i = 0; |
| |
| while (mask.length < len) { |
| mask += hextorstr(hash(rstrtohex(seed + String.fromCharCode.apply(String, [ |
| (i & 0xff000000) >> 24, |
| (i & 0x00ff0000) >> 16, |
| (i & 0x0000ff00) >> 8, |
| i & 0x000000ff])))); |
| i += 1; |
| } |
| |
| return mask; |
| } |
| |
| /** |
| * sign for a message string with RSA private key by PKCS#1 PSS signing.<br/> |
| * @name signStringPSS |
| * @memberOf RSAKey |
| * @function |
| * @param {String} s message string to be signed. |
| * @param {String} hashAlg hash algorithm name for signing. |
| * @param {Integer} sLen salt byte length from 0 to (keybytelen - hashbytelen - 2). |
| * There are two special values: |
| * <ul> |
| * <li>-1: sets the salt length to the digest length</li> |
| * <li>-2: sets the salt length to maximum permissible value |
| * (i.e. keybytelen - hashbytelen - 2)</li> |
| * </ul> |
| * DEFAULT is -1. (NOTE: OpenSSL's default is -2.) |
| * @return returns hexadecimal string of signature value. |
| */ |
| function _rsasign_signStringPSS(s, hashAlg, sLen) { |
| var hashFunc = function(sHex) { return KJUR.crypto.Util.hashHex(sHex, hashAlg); } |
| var hHash = hashFunc(rstrtohex(s)); |
| |
| if (sLen === undefined) sLen = -1; |
| return this.signWithMessageHashPSS(hHash, hashAlg, sLen); |
| } |
| |
| /** |
| * sign hash value of message with RSA private key by PKCS#1 PSS signing.<br/> |
| * @name signWithMessageHashPSS |
| * @memberOf RSAKey |
| * @function |
| * @param {String} hHash hexadecimal hash value of message to be signed. |
| * @param {String} hashAlg hash algorithm name for signing. |
| * @param {Integer} sLen salt byte length from 0 to (keybytelen - hashbytelen - 2). |
| * There are two special values: |
| * <ul> |
| * <li>-1: sets the salt length to the digest length</li> |
| * <li>-2: sets the salt length to maximum permissible value |
| * (i.e. keybytelen - hashbytelen - 2)</li> |
| * </ul> |
| * DEFAULT is -1. (NOTE: OpenSSL's default is -2.) |
| * @return returns hexadecimal string of signature value. |
| * @since rsasign 1.2.6 |
| */ |
| function _rsasign_signWithMessageHashPSS(hHash, hashAlg, sLen) { |
| var mHash = hextorstr(hHash); |
| var hLen = mHash.length; |
| var emBits = this.n.bitLength() - 1; |
| var emLen = Math.ceil(emBits / 8); |
| var i; |
| var hashFunc = function(sHex) { return KJUR.crypto.Util.hashHex(sHex, hashAlg); } |
| |
| if (sLen === -1 || sLen === undefined) { |
| sLen = hLen; // same as hash length |
| } else if (sLen === -2) { |
| sLen = emLen - hLen - 2; // maximum |
| } else if (sLen < -2) { |
| throw "invalid salt length"; |
| } |
| |
| if (emLen < (hLen + sLen + 2)) { |
| throw "data too long"; |
| } |
| |
| var salt = ''; |
| |
| if (sLen > 0) { |
| salt = new Array(sLen); |
| new SecureRandom().nextBytes(salt); |
| salt = String.fromCharCode.apply(String, salt); |
| } |
| |
| var H = hextorstr(hashFunc(rstrtohex('\x00\x00\x00\x00\x00\x00\x00\x00' + mHash + salt))); |
| var PS = []; |
| |
| for (i = 0; i < emLen - sLen - hLen - 2; i += 1) { |
| PS[i] = 0x00; |
| } |
| |
| var DB = String.fromCharCode.apply(String, PS) + '\x01' + salt; |
| var dbMask = pss_mgf1_str(H, DB.length, hashFunc); |
| var maskedDB = []; |
| |
| for (i = 0; i < DB.length; i += 1) { |
| maskedDB[i] = DB.charCodeAt(i) ^ dbMask.charCodeAt(i); |
| } |
| |
| var mask = (0xff00 >> (8 * emLen - emBits)) & 0xff; |
| maskedDB[0] &= ~mask; |
| |
| for (i = 0; i < hLen; i++) { |
| maskedDB.push(H.charCodeAt(i)); |
| } |
| |
| maskedDB.push(0xbc); |
| |
| return _zeroPaddingOfSignature(this.doPrivate(new BigInteger(maskedDB)).toString(16), |
| this.n.bitLength()); |
| } |
| |
| // ======================================================================== |
| // Signature Verification |
| // ======================================================================== |
| |
| function _rsasign_getDecryptSignatureBI(biSig, hN, hE) { |
| var rsa = new RSAKey(); |
| rsa.setPublic(hN, hE); |
| var biDecryptedSig = rsa.doPublic(biSig); |
| return biDecryptedSig; |
| } |
| |
| function _rsasign_getHexDigestInfoFromSig(biSig, hN, hE) { |
| var biDecryptedSig = _rsasign_getDecryptSignatureBI(biSig, hN, hE); |
| var hDigestInfo = biDecryptedSig.toString(16).replace(/^1f+00/, ''); |
| return hDigestInfo; |
| } |
| |
| function _rsasign_getAlgNameAndHashFromHexDisgestInfo(hDigestInfo) { |
| for (var algName in KJUR.crypto.Util.DIGESTINFOHEAD) { |
| var head = KJUR.crypto.Util.DIGESTINFOHEAD[algName]; |
| var len = head.length; |
| if (hDigestInfo.substring(0, len) == head) { |
| var a = [algName, hDigestInfo.substring(len)]; |
| return a; |
| } |
| } |
| return []; |
| } |
| |
| function _rsasign_verifySignatureWithArgs(sMsg, biSig, hN, hE) { |
| var hDigestInfo = _rsasign_getHexDigestInfoFromSig(biSig, hN, hE); |
| var digestInfoAry = _rsasign_getAlgNameAndHashFromHexDisgestInfo(hDigestInfo); |
| if (digestInfoAry.length == 0) return false; |
| var algName = digestInfoAry[0]; |
| var diHashValue = digestInfoAry[1]; |
| var ff = function(s) { return KJUR.crypto.Util.hashString(s, algName); }; |
| var msgHashValue = ff(sMsg); |
| return (diHashValue == msgHashValue); |
| } |
| |
| function _rsasign_verifyHexSignatureForMessage(hSig, sMsg) { |
| var biSig = parseBigInt(hSig, 16); |
| var result = _rsasign_verifySignatureWithArgs(sMsg, biSig, |
| this.n.toString(16), |
| this.e.toString(16)); |
| return result; |
| } |
| |
| /** |
| * verifies a sigature for a message string with RSA public key.<br/> |
| * @name verifyString |
| * @memberOf RSAKey# |
| * @function |
| * @param {String} sMsg message string to be verified. |
| * @param {String} hSig hexadecimal string of siganture.<br/> |
| * non-hexadecimal charactors including new lines will be ignored. |
| * @return returns 1 if valid, otherwise 0 |
| */ |
| function _rsasign_verifyString(sMsg, hSig) { |
| hSig = hSig.replace(_RE_HEXDECONLY, ''); |
| hSig = hSig.replace(/[ \n]+/g, ""); |
| var biSig = parseBigInt(hSig, 16); |
| if (biSig.bitLength() > this.n.bitLength()) return 0; |
| var biDecryptedSig = this.doPublic(biSig); |
| var hDigestInfo = biDecryptedSig.toString(16).replace(/^1f+00/, ''); |
| var digestInfoAry = _rsasign_getAlgNameAndHashFromHexDisgestInfo(hDigestInfo); |
| |
| if (digestInfoAry.length == 0) return false; |
| var algName = digestInfoAry[0]; |
| var diHashValue = digestInfoAry[1]; |
| var ff = function(s) { return KJUR.crypto.Util.hashString(s, algName); }; |
| var msgHashValue = ff(sMsg); |
| return (diHashValue == msgHashValue); |
| } |
| |
| /** |
| * verifies a sigature for a message string with RSA public key.<br/> |
| * @name verifyWithMessageHash |
| * @memberOf RSAKey |
| * @function |
| * @param {String} sHashHex hexadecimal hash value of message to be verified. |
| * @param {String} hSig hexadecimal string of siganture.<br/> |
| * non-hexadecimal charactors including new lines will be ignored. |
| * @return returns 1 if valid, otherwise 0 |
| * @since rsasign 1.2.6 |
| */ |
| function _rsasign_verifyWithMessageHash(sHashHex, hSig) { |
| hSig = hSig.replace(_RE_HEXDECONLY, ''); |
| hSig = hSig.replace(/[ \n]+/g, ""); |
| var biSig = parseBigInt(hSig, 16); |
| if (biSig.bitLength() > this.n.bitLength()) return 0; |
| var biDecryptedSig = this.doPublic(biSig); |
| var hDigestInfo = biDecryptedSig.toString(16).replace(/^1f+00/, ''); |
| var digestInfoAry = _rsasign_getAlgNameAndHashFromHexDisgestInfo(hDigestInfo); |
| |
| if (digestInfoAry.length == 0) return false; |
| var algName = digestInfoAry[0]; |
| var diHashValue = digestInfoAry[1]; |
| return (diHashValue == sHashHex); |
| } |
| |
| /** |
| * verifies a sigature for a message string with RSA public key by PKCS#1 PSS sign.<br/> |
| * @name verifyStringPSS |
| * @memberOf RSAKey |
| * @function |
| * @param {String} sMsg message string to be verified. |
| * @param {String} hSig hexadecimal string of signature value |
| * @param {String} hashAlg hash algorithm name |
| * @param {Integer} sLen salt byte length from 0 to (keybytelen - hashbytelen - 2). |
| * There are two special values: |
| * <ul> |
| * <li>-1: sets the salt length to the digest length</li> |
| * <li>-2: sets the salt length to maximum permissible value |
| * (i.e. keybytelen - hashbytelen - 2)</li> |
| * </ul> |
| * DEFAULT is -1. (NOTE: OpenSSL's default is -2.) |
| * @return returns true if valid, otherwise false |
| */ |
| function _rsasign_verifyStringPSS(sMsg, hSig, hashAlg, sLen) { |
| var hashFunc = function(sHex) { return KJUR.crypto.Util.hashHex(sHex, hashAlg); }; |
| var hHash = hashFunc(rstrtohex(sMsg)); |
| |
| if (sLen === undefined) sLen = -1; |
| return this.verifyWithMessageHashPSS(hHash, hSig, hashAlg, sLen); |
| } |
| |
| /** |
| * verifies a sigature for a hash value of message string with RSA public key by PKCS#1 PSS sign.<br/> |
| * @name verifyWithMessageHashPSS |
| * @memberOf RSAKey |
| * @function |
| * @param {String} hHash hexadecimal hash value of message string to be verified. |
| * @param {String} hSig hexadecimal string of signature value |
| * @param {String} hashAlg hash algorithm name |
| * @param {Integer} sLen salt byte length from 0 to (keybytelen - hashbytelen - 2). |
| * There are two special values: |
| * <ul> |
| * <li>-1: sets the salt length to the digest length</li> |
| * <li>-2: sets the salt length to maximum permissible value |
| * (i.e. keybytelen - hashbytelen - 2)</li> |
| * </ul> |
| * DEFAULT is -1 (NOTE: OpenSSL's default is -2.) |
| * @return returns true if valid, otherwise false |
| * @since rsasign 1.2.6 |
| */ |
| function _rsasign_verifyWithMessageHashPSS(hHash, hSig, hashAlg, sLen) { |
| var biSig = new BigInteger(hSig, 16); |
| |
| if (biSig.bitLength() > this.n.bitLength()) { |
| return false; |
| } |
| |
| var hashFunc = function(sHex) { return KJUR.crypto.Util.hashHex(sHex, hashAlg); }; |
| var mHash = hextorstr(hHash); |
| var hLen = mHash.length; |
| var emBits = this.n.bitLength() - 1; |
| var emLen = Math.ceil(emBits / 8); |
| var i; |
| |
| if (sLen === -1 || sLen === undefined) { |
| sLen = hLen; // same as hash length |
| } else if (sLen === -2) { |
| sLen = emLen - hLen - 2; // recover |
| } else if (sLen < -2) { |
| throw "invalid salt length"; |
| } |
| |
| if (emLen < (hLen + sLen + 2)) { |
| throw "data too long"; |
| } |
| |
| var em = this.doPublic(biSig).toByteArray(); |
| |
| for (i = 0; i < em.length; i += 1) { |
| em[i] &= 0xff; |
| } |
| |
| while (em.length < emLen) { |
| em.unshift(0); |
| } |
| |
| if (em[emLen -1] !== 0xbc) { |
| throw "encoded message does not end in 0xbc"; |
| } |
| |
| em = String.fromCharCode.apply(String, em); |
| |
| var maskedDB = em.substr(0, emLen - hLen - 1); |
| var H = em.substr(maskedDB.length, hLen); |
| |
| var mask = (0xff00 >> (8 * emLen - emBits)) & 0xff; |
| |
| if ((maskedDB.charCodeAt(0) & mask) !== 0) { |
| throw "bits beyond keysize not zero"; |
| } |
| |
| var dbMask = pss_mgf1_str(H, maskedDB.length, hashFunc); |
| var DB = []; |
| |
| for (i = 0; i < maskedDB.length; i += 1) { |
| DB[i] = maskedDB.charCodeAt(i) ^ dbMask.charCodeAt(i); |
| } |
| |
| DB[0] &= ~mask; |
| |
| var checkLen = emLen - hLen - sLen - 2; |
| |
| for (i = 0; i < checkLen; i += 1) { |
| if (DB[i] !== 0x00) { |
| throw "leftmost octets not zero"; |
| } |
| } |
| |
| if (DB[checkLen] !== 0x01) { |
| throw "0x01 marker not found"; |
| } |
| |
| return H === hextorstr(hashFunc(rstrtohex('\x00\x00\x00\x00\x00\x00\x00\x00' + mHash + |
| String.fromCharCode.apply(String, DB.slice(-sLen))))); |
| } |
| |
| RSAKey.prototype.signWithMessageHash = _rsasign_signWithMessageHash; |
| RSAKey.prototype.signString = _rsasign_signString; |
| RSAKey.prototype.signStringWithSHA1 = _rsasign_signStringWithSHA1; |
| RSAKey.prototype.signStringWithSHA256 = _rsasign_signStringWithSHA256; |
| RSAKey.prototype.sign = _rsasign_signString; |
| RSAKey.prototype.signWithSHA1 = _rsasign_signStringWithSHA1; |
| RSAKey.prototype.signWithSHA256 = _rsasign_signStringWithSHA256; |
| |
| RSAKey.prototype.signWithMessageHashPSS = _rsasign_signWithMessageHashPSS; |
| RSAKey.prototype.signStringPSS = _rsasign_signStringPSS; |
| RSAKey.prototype.signPSS = _rsasign_signStringPSS; |
| RSAKey.SALT_LEN_HLEN = -1; |
| RSAKey.SALT_LEN_MAX = -2; |
| |
| RSAKey.prototype.verifyWithMessageHash = _rsasign_verifyWithMessageHash; |
| RSAKey.prototype.verifyString = _rsasign_verifyString; |
| RSAKey.prototype.verifyHexSignatureForMessage = _rsasign_verifyHexSignatureForMessage; |
| RSAKey.prototype.verify = _rsasign_verifyString; |
| RSAKey.prototype.verifyHexSignatureForByteArrayMessage = _rsasign_verifyHexSignatureForMessage; |
| |
| RSAKey.prototype.verifyWithMessageHashPSS = _rsasign_verifyWithMessageHashPSS; |
| RSAKey.prototype.verifyStringPSS = _rsasign_verifyStringPSS; |
| RSAKey.prototype.verifyPSS = _rsasign_verifyStringPSS; |
| RSAKey.SALT_LEN_RECOVER = -2; |
| |
| /** |
| * @name RSAKey |
| * @class key of RSA public key algorithm |
| * @description Tom Wu's RSA Key class and extension |
| */ |
| |
| exports.RSAKey = RSAKey; |
| module.exports = exports; |
| /*! ecdsa-modified-1.0.4.js (c) Stephan Thomas, Kenji Urushima | github.com/bitcoinjs/bitcoinjs-lib/blob/master/LICENSE |
| */ |
| /* |
| * ecdsa-modified.js - modified Bitcoin.ECDSA class |
| * |
| * Copyright (c) 2013 Stefan Thomas (github.com/justmoon) |
| * Kenji Urushima (kenji.urushima@gmail.com) |
| * LICENSE |
| * https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/LICENSE |
| */ |
| |
| /** |
| * @fileOverview |
| * @name ecdsa-modified-1.0.js |
| * @author Stefan Thomas (github.com/justmoon) and Kenji Urushima (kenji.urushima@gmail.com) |
| * @version 1.0.4 (2013-Oct-06) |
| * @since jsrsasign 4.0 |
| * @license <a href="https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/LICENSE">MIT License</a> |
| */ |
| |
| if (typeof KJUR == "undefined" || !KJUR) KJUR = {}; |
| if (typeof KJUR.crypto == "undefined" || !KJUR.crypto) KJUR.crypto = {}; |
| |
| /** |
| * class for EC key generation, ECDSA signing and verifcation |
| * @name KJUR.crypto.ECDSA |
| * @class class for EC key generation, ECDSA signing and verifcation |
| * @description |
| * <p> |
| * CAUTION: Most of the case, you don't need to use this class except |
| * for generating an EC key pair. Please use {@link KJUR.crypto.Signature} class instead. |
| * </p> |
| * <p> |
| * This class was originally developped by Stefan Thomas for Bitcoin JavaScript library. |
| * (See {@link https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/src/ecdsa.js}) |
| * Currently this class supports following named curves and their aliases. |
| * <ul> |
| * <li>secp256r1, NIST P-256, P-256, prime256v1 (*)</li> |
| * <li>secp256k1 (*)</li> |
| * <li>secp384r1, NIST P-384, P-384 (*)</li> |
| * </ul> |
| * </p> |
| */ |
| KJUR.crypto.ECDSA = function(params) { |
| var curveName = "secp256r1"; // curve name default |
| var ecparams = null; |
| var prvKeyHex = null; |
| var pubKeyHex = null; |
| |
| var rng = new SecureRandom(); |
| |
| var P_OVER_FOUR = null; |
| |
| this.type = "EC"; |
| |
| function implShamirsTrick(P, k, Q, l) { |
| var m = Math.max(k.bitLength(), l.bitLength()); |
| var Z = P.add2D(Q); |
| var R = P.curve.getInfinity(); |
| |
| for (var i = m - 1; i >= 0; --i) { |
| R = R.twice2D(); |
| |
| R.z = BigInteger.ONE; |
| |
| if (k.testBit(i)) { |
| if (l.testBit(i)) { |
| R = R.add2D(Z); |
| } else { |
| R = R.add2D(P); |
| } |
| } else { |
| if (l.testBit(i)) { |
| R = R.add2D(Q); |
| } |
| } |
| } |
| |
| return R; |
| }; |
| |
| //=========================== |
| // PUBLIC METHODS |
| //=========================== |
| this.getBigRandom = function (limit) { |
| return new BigInteger(limit.bitLength(), rng) |
| .mod(limit.subtract(BigInteger.ONE)) |
| .add(BigInteger.ONE) |
| ; |
| }; |
| |
| this.setNamedCurve = function(curveName) { |
| this.ecparams = KJUR.crypto.ECParameterDB.getByName(curveName); |
| this.prvKeyHex = null; |
| this.pubKeyHex = null; |
| this.curveName = curveName; |
| } |
| |
| this.setPrivateKeyHex = function(prvKeyHex) { |
| this.isPrivate = true; |
| this.prvKeyHex = prvKeyHex; |
| } |
| |
| this.setPublicKeyHex = function(pubKeyHex) { |
| this.isPublic = true; |
| this.pubKeyHex = pubKeyHex; |
| } |
| |
| /** |
| * generate a EC key pair |
| * @name generateKeyPairHex |
| * @memberOf KJUR.crypto.ECDSA |
| * @function |
| * @return {Array} associative array of hexadecimal string of private and public key |
| * @since ecdsa-modified 1.0.1 |
| * @example |
| * var ec = new KJUR.crypto.ECDSA({'curve': 'secp256r1'}); |
| * var keypair = ec.generateKeyPairHex(); |
| * var pubhex = keypair.ecpubhex; // hexadecimal string of EC private key (=d) |
| * var prvhex = keypair.ecprvhex; // hexadecimal string of EC public key |
| */ |
| this.generateKeyPairHex = function() { |
| var biN = this.ecparams['n']; |
| var biPrv = this.getBigRandom(biN); |
| var epPub = this.ecparams['G'].multiply(biPrv); |
| var biX = epPub.getX().toBigInteger(); |
| var biY = epPub.getY().toBigInteger(); |
| |
| var charlen = this.ecparams['keylen'] / 4; |
| var hPrv = ("0000000000" + biPrv.toString(16)).slice(- charlen); |
| var hX = ("0000000000" + biX.toString(16)).slice(- charlen); |
| var hY = ("0000000000" + biY.toString(16)).slice(- charlen); |
| var hPub = "04" + hX + hY; |
| |
| this.setPrivateKeyHex(hPrv); |
| this.setPublicKeyHex(hPub); |
| return {'ecprvhex': hPrv, 'ecpubhex': hPub}; |
| }; |
| |
| this.signWithMessageHash = function(hashHex) { |
| return this.signHex(hashHex, this.prvKeyHex); |
| }; |
| |
| /** |
| * signing to message hash |
| * @name signHex |
| * @memberOf KJUR.crypto.ECDSA |
| * @function |
| * @param {String} hashHex hexadecimal string of hash value of signing message |
| * @param {String} privHex hexadecimal string of EC private key |
| * @return {String} hexadecimal string of ECDSA signature |
| * @since ecdsa-modified 1.0.1 |
| * @example |
| * var ec = new KJUR.crypto.ECDSA({'curve': 'secp256r1'}); |
| * var sigValue = ec.signHex(hash, prvKey); |
| */ |
| this.signHex = function (hashHex, privHex) { |
| var d = new BigInteger(privHex, 16); |
| var n = this.ecparams['n']; |
| var e = new BigInteger(hashHex, 16); |
| |
| do { |
| var k = this.getBigRandom(n); |
| var G = this.ecparams['G']; |
| var Q = G.multiply(k); |
| var r = Q.getX().toBigInteger().mod(n); |
| } while (r.compareTo(BigInteger.ZERO) <= 0); |
| |
| var s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n); |
| |
| return KJUR.crypto.ECDSA.biRSSigToASN1Sig(r, s); |
| }; |
| |
| this.sign = function (hash, priv) { |
| var d = priv; |
| var n = this.ecparams['n']; |
| var e = BigInteger.fromByteArrayUnsigned(hash); |
| |
| do { |
| var k = this.getBigRandom(n); |
| var G = this.ecparams['G']; |
| var Q = G.multiply(k); |
| var r = Q.getX().toBigInteger().mod(n); |
| } while (r.compareTo(BigInteger.ZERO) <= 0); |
| |
| var s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n); |
| return this.serializeSig(r, s); |
| }; |
| |
| this.verifyWithMessageHash = function(hashHex, sigHex) { |
| return this.verifyHex(hashHex, sigHex, this.pubKeyHex); |
| }; |
| |
| /** |
| * verifying signature with message hash and public key |
| * @name verifyHex |
| * @memberOf KJUR.crypto.ECDSA |
| * @function |
| * @param {String} hashHex hexadecimal string of hash value of signing message |
| * @param {String} sigHex hexadecimal string of signature value |
| * @param {String} pubkeyHex hexadecimal string of public key |
| * @return {Boolean} true if the signature is valid, otherwise false |
| * @since ecdsa-modified 1.0.1 |
| * @example |
| * var ec = new KJUR.crypto.ECDSA({'curve': 'secp256r1'}); |
| * var result = ec.verifyHex(msgHashHex, sigHex, pubkeyHex); |
| */ |
| this.verifyHex = function(hashHex, sigHex, pubkeyHex) { |
| var r,s; |
| |
| var obj = KJUR.crypto.ECDSA.parseSigHex(sigHex); |
| r = obj.r; |
| s = obj.s; |
| |
| var Q; |
| Q = ECPointFp.decodeFromHex(this.ecparams['curve'], pubkeyHex); |
| var e = new BigInteger(hashHex, 16); |
| |
| return this.verifyRaw(e, r, s, Q); |
| }; |
| |
| this.verify = function (hash, sig, pubkey) { |
| var r,s; |
| if (Bitcoin.Util.isArray(sig)) { |
| var obj = this.parseSig(sig); |
| r = obj.r; |
| s = obj.s; |
| } else if ("object" === typeof sig && sig.r && sig.s) { |
| r = sig.r; |
| s = sig.s; |
| } else { |
| throw "Invalid value for signature"; |
| } |
| |
| var Q; |
| if (pubkey instanceof ECPointFp) { |
| Q = pubkey; |
| } else if (Bitcoin.Util.isArray(pubkey)) { |
| Q = ECPointFp.decodeFrom(this.ecparams['curve'], pubkey); |
| } else { |
| throw "Invalid format for pubkey value, must be byte array or ECPointFp"; |
| } |
| var e = BigInteger.fromByteArrayUnsigned(hash); |
| |
| return this.verifyRaw(e, r, s, Q); |
| }; |
| |
| this.verifyRaw = function (e, r, s, Q) { |
| var n = this.ecparams['n']; |
| var G = this.ecparams['G']; |
| |
| if (r.compareTo(BigInteger.ONE) < 0 || |
| r.compareTo(n) >= 0) |
| return false; |
| |
| if (s.compareTo(BigInteger.ONE) < 0 || |
| s.compareTo(n) >= 0) |
| return false; |
| |
| var c = s.modInverse(n); |
| |
| var u1 = e.multiply(c).mod(n); |
| var u2 = r.multiply(c).mod(n); |
| |
| // TODO(!!!): For some reason Shamir's trick isn't working with |
| // signed message verification!? Probably an implementation |
| // error! |
| //var point = implShamirsTrick(G, u1, Q, u2); |
| var point = G.multiply(u1).add(Q.multiply(u2)); |
| |
| var v = point.getX().toBigInteger().mod(n); |
| |
| return v.equals(r); |
| }; |
| |
| /** |
| * Serialize a signature into DER format. |
| * |
| * Takes two BigIntegers representing r and s and returns a byte array. |
| */ |
| this.serializeSig = function (r, s) { |
| var rBa = r.toByteArraySigned(); |
| var sBa = s.toByteArraySigned(); |
| |
| var sequence = []; |
| sequence.push(0x02); // INTEGER |
| sequence.push(rBa.length); |
| sequence = sequence.concat(rBa); |
| |
| sequence.push(0x02); // INTEGER |
| sequence.push(sBa.length); |
| sequence = sequence.concat(sBa); |
| |
| sequence.unshift(sequence.length); |
| sequence.unshift(0x30); // SEQUENCE |
| return sequence; |
| }; |
| |
| /** |
| * Parses a byte array containing a DER-encoded signature. |
| * |
| * This function will return an object of the form: |
| * |
| * { |
| * r: BigInteger, |
| * s: BigInteger |
| * } |
| */ |
| this.parseSig = function (sig) { |
| var cursor; |
| if (sig[0] != 0x30) |
| throw new Error("Signature not a valid DERSequence"); |
| |
| cursor = 2; |
| if (sig[cursor] != 0x02) |
| throw new Error("First element in signature must be a DERInteger");; |
| var rBa = sig.slice(cursor+2, cursor+2+sig[cursor+1]); |
| |
| cursor += 2+sig[cursor+1]; |
| if (sig[cursor] != 0x02) |
| throw new Error("Second element in signature must be a DERInteger"); |
| var sBa = sig.slice(cursor+2, cursor+2+sig[cursor+1]); |
| |
| cursor += 2+sig[cursor+1]; |
| |
| //if (cursor != sig.length) |
| // throw new Error("Extra bytes in signature"); |
| |
| var r = BigInteger.fromByteArrayUnsigned(rBa); |
| var s = BigInteger.fromByteArrayUnsigned(sBa); |
| |
| return {r: r, s: s}; |
| }; |
| |
| this.parseSigCompact = function (sig) { |
| if (sig.length !== 65) { |
| throw "Signature has the wrong length"; |
| } |
| |
| // Signature is prefixed with a type byte storing three bits of |
| // information. |
| var i = sig[0] - 27; |
| if (i < 0 || i > 7) { |
| throw "Invalid signature type"; |
| } |
| |
| var n = this.ecparams['n']; |
| var r = BigInteger.fromByteArrayUnsigned(sig.slice(1, 33)).mod(n); |
| var s = BigInteger.fromByteArrayUnsigned(sig.slice(33, 65)).mod(n); |
| |
| return {r: r, s: s, i: i}; |
| }; |
| |
| /* |
| * Recover a public key from a signature. |
| * |
| * See SEC 1: Elliptic Curve Cryptography, section 4.1.6, "Public |
| * Key Recovery Operation". |
| * |
| * http://www.secg.org/download/aid-780/sec1-v2.pdf |
| */ |
| /* |
| recoverPubKey: function (r, s, hash, i) { |
| // The recovery parameter i has two bits. |
| i = i & 3; |
| |
| // The less significant bit specifies whether the y coordinate |
| // of the compressed point is even or not. |
| var isYEven = i & 1; |
| |
| // The more significant bit specifies whether we should use the |
| // first or second candidate key. |
| var isSecondKey = i >> 1; |
| |
| var n = this.ecparams['n']; |
| var G = this.ecparams['G']; |
| var curve = this.ecparams['curve']; |
| var p = curve.getQ(); |
| var a = curve.getA().toBigInteger(); |
| var b = curve.getB().toBigInteger(); |
| |
| // We precalculate (p + 1) / 4 where p is if the field order |
| if (!P_OVER_FOUR) { |
| P_OVER_FOUR = p.add(BigInteger.ONE).divide(BigInteger.valueOf(4)); |
| } |
| |
| // 1.1 Compute x |
| var x = isSecondKey ? r.add(n) : r; |
| |
| // 1.3 Convert x to point |
| var alpha = x.multiply(x).multiply(x).add(a.multiply(x)).add(b).mod(p); |
| var beta = alpha.modPow(P_OVER_FOUR, p); |
| |
| var xorOdd = beta.isEven() ? (i % 2) : ((i+1) % 2); |
| // If beta is even, but y isn't or vice versa, then convert it, |
| // otherwise we're done and y == beta. |
| var y = (beta.isEven() ? !isYEven : isYEven) ? beta : p.subtract(beta); |
| |
| // 1.4 Check that nR is at infinity |
| var R = new ECPointFp(curve, |
| curve.fromBigInteger(x), |
| curve.fromBigInteger(y)); |
| R.validate(); |
| |
| // 1.5 Compute e from M |
| var e = BigInteger.fromByteArrayUnsigned(hash); |
| var eNeg = BigInteger.ZERO.subtract(e).mod(n); |
| |
| // 1.6 Compute Q = r^-1 (sR - eG) |
| var rInv = r.modInverse(n); |
| var Q = implShamirsTrick(R, s, G, eNeg).multiply(rInv); |
| |
| Q.validate(); |
| if (!this.verifyRaw(e, r, s, Q)) { |
| throw "Pubkey recovery unsuccessful"; |
| } |
| |
| var pubKey = new Bitcoin.ECKey(); |
| pubKey.pub = Q; |
| return pubKey; |
| }, |
| */ |
| |
| /* |
| * Calculate pubkey extraction parameter. |
| * |
| * When extracting a pubkey from a signature, we have to |
| * distinguish four different cases. Rather than putting this |
| * burden on the verifier, Bitcoin includes a 2-bit value with the |
| * signature. |
| * |
| * This function simply tries all four cases and returns the value |
| * that resulted in a successful pubkey recovery. |
| */ |
| /* |
| calcPubkeyRecoveryParam: function (address, r, s, hash) { |
| for (var i = 0; i < 4; i++) { |
| try { |
| var pubkey = Bitcoin.ECDSA.recoverPubKey(r, s, hash, i); |
| if (pubkey.getBitcoinAddress().toString() == address) { |
| return i; |
| } |
| } catch (e) {} |
| } |
| throw "Unable to find valid recovery factor"; |
| } |
| */ |
| |
| if (params !== undefined) { |
| if (params['curve'] !== undefined) { |
| this.curveName = params['curve']; |
| } |
| } |
| if (this.curveName === undefined) this.curveName = curveName; |
| this.setNamedCurve(this.curveName); |
| if (params !== undefined) { |
| if (params['prv'] !== undefined) this.setPrivateKeyHex(params['prv']); |
| if (params['pub'] !== undefined) this.setPublicKeyHex(params['pub']); |
| } |
| }; |
| |
| /** |
| * parse ASN.1 DER encoded ECDSA signature |
| * @name parseSigHex |
| * @memberOf KJUR.crypto.ECDSA |
| * @function |
| * @static |
| * @param {String} sigHex hexadecimal string of ECDSA signature value |
| * @return {Array} associative array of signature field r and s of BigInteger |
| * @since ecdsa-modified 1.0.1 |
| * @example |
| * var ec = new KJUR.crypto.ECDSA({'curve': 'secp256r1'}); |
| * var sig = ec.parseSigHex('30...'); |
| * var biR = sig.r; // BigInteger object for 'r' field of signature. |
| * var biS = sig.s; // BigInteger object for 's' field of signature. |
| */ |
| KJUR.crypto.ECDSA.parseSigHex = function(sigHex) { |
| var p = KJUR.crypto.ECDSA.parseSigHexInHexRS(sigHex); |
| var biR = new BigInteger(p.r, 16); |
| var biS = new BigInteger(p.s, 16); |
| |
| return {'r': biR, 's': biS}; |
| }; |
| |
| /** |
| * parse ASN.1 DER encoded ECDSA signature |
| * @name parseSigHexInHexRS |
| * @memberOf KJUR.crypto.ECDSA |
| * @function |
| * @static |
| * @param {String} sigHex hexadecimal string of ECDSA signature value |
| * @return {Array} associative array of signature field r and s in hexadecimal |
| * @since ecdsa-modified 1.0.3 |
| * @example |
| * var ec = new KJUR.crypto.ECDSA({'curve': 'secp256r1'}); |
| * var sig = ec.parseSigHexInHexRS('30...'); |
| * var hR = sig.r; // hexadecimal string for 'r' field of signature. |
| * var hS = sig.s; // hexadecimal string for 's' field of signature. |
| */ |
| KJUR.crypto.ECDSA.parseSigHexInHexRS = function(sigHex) { |
| // 1. ASN.1 Sequence Check |
| if (sigHex.substr(0, 2) != "30") |
| throw "signature is not a ASN.1 sequence"; |
| |
| // 2. Items of ASN.1 Sequence Check |
| var a = ASN1HEX.getPosArrayOfChildren_AtObj(sigHex, 0); |
| if (a.length != 2) |
| throw "number of signature ASN.1 sequence elements seem wrong"; |
| |
| // 3. Integer check |
| var iTLV1 = a[0]; |
| var iTLV2 = a[1]; |
| if (sigHex.substr(iTLV1, 2) != "02") |
| throw "1st item of sequene of signature is not ASN.1 integer"; |
| if (sigHex.substr(iTLV2, 2) != "02") |
| throw "2nd item of sequene of signature is not ASN.1 integer"; |
| |
| // 4. getting value |
| var hR = ASN1HEX.getHexOfV_AtObj(sigHex, iTLV1); |
| var hS = ASN1HEX.getHexOfV_AtObj(sigHex, iTLV2); |
| |
| return {'r': hR, 's': hS}; |
| }; |
| |
| /** |
| * convert hexadecimal ASN.1 encoded signature to concatinated signature |
| * @name asn1SigToConcatSig |
| * @memberOf KJUR.crypto.ECDSA |
| * @function |
| * @static |
| * @param {String} asn1Hex hexadecimal string of ASN.1 encoded ECDSA signature value |
| * @return {String} r-s concatinated format of ECDSA signature value |
| * @since ecdsa-modified 1.0.3 |
| */ |
| KJUR.crypto.ECDSA.asn1SigToConcatSig = function(asn1Sig) { |
| var pSig = KJUR.crypto.ECDSA.parseSigHexInHexRS(asn1Sig); |
| var hR = pSig.r; |
| var hS = pSig.s; |
| |
| if (hR.substr(0, 2) == "00" && (((hR.length / 2) * 8) % (16 * 8)) == 8) |
| hR = hR.substr(2); |
| |
| if (hS.substr(0, 2) == "00" && (((hS.length / 2) * 8) % (16 * 8)) == 8) |
| hS = hS.substr(2); |
| |
| if ((((hR.length / 2) * 8) % (16 * 8)) != 0) |
| throw "unknown ECDSA sig r length error"; |
| |
| if ((((hS.length / 2) * 8) % (16 * 8)) != 0) |
| throw "unknown ECDSA sig s length error"; |
| |
| return hR + hS; |
| }; |
| |
| /** |
| * convert hexadecimal concatinated signature to ASN.1 encoded signature |
| * @name concatSigToASN1Sig |
| * @memberOf KJUR.crypto.ECDSA |
| * @function |
| * @static |
| * @param {String} concatSig r-s concatinated format of ECDSA signature value |
| * @return {String} hexadecimal string of ASN.1 encoded ECDSA signature value |
| * @since ecdsa-modified 1.0.3 |
| */ |
| KJUR.crypto.ECDSA.concatSigToASN1Sig = function(concatSig) { |
| if ((((concatSig.length / 2) * 8) % (16 * 8)) != 0) |
| throw "unknown ECDSA concatinated r-s sig length error"; |
| |
| var hR = concatSig.substr(0, concatSig.length / 2); |
| var hS = concatSig.substr(concatSig.length / 2); |
| return KJUR.crypto.ECDSA.hexRSSigToASN1Sig(hR, hS); |
| }; |
| |
| /** |
| * convert hexadecimal R and S value of signature to ASN.1 encoded signature |
| * @name hexRSSigToASN1Sig |
| * @memberOf KJUR.crypto.ECDSA |
| * @function |
| * @static |
| * @param {String} hR hexadecimal string of R field of ECDSA signature value |
| * @param {String} hS hexadecimal string of S field of ECDSA signature value |
| * @return {String} hexadecimal string of ASN.1 encoded ECDSA signature value |
| * @since ecdsa-modified 1.0.3 |
| */ |
| KJUR.crypto.ECDSA.hexRSSigToASN1Sig = function(hR, hS) { |
| var biR = new BigInteger(hR, 16); |
| var biS = new BigInteger(hS, 16); |
| return KJUR.crypto.ECDSA.biRSSigToASN1Sig(biR, biS); |
| }; |
| |
| /** |
| * convert R and S BigInteger object of signature to ASN.1 encoded signature |
| * @name biRSSigToASN1Sig |
| * @memberOf KJUR.crypto.ECDSA |
| * @function |
| * @static |
| * @param {BigInteger} biR BigInteger object of R field of ECDSA signature value |
| * @param {BigInteger} biS BIgInteger object of S field of ECDSA signature value |
| * @return {String} hexadecimal string of ASN.1 encoded ECDSA signature value |
| * @since ecdsa-modified 1.0.3 |
| */ |
| KJUR.crypto.ECDSA.biRSSigToASN1Sig = function(biR, biS) { |
| var derR = new KJUR.asn1.DERInteger({'bigint': biR}); |
| var derS = new KJUR.asn1.DERInteger({'bigint': biS}); |
| var derSeq = new KJUR.asn1.DERSequence({'array': [derR, derS]}); |
| return derSeq.getEncodedHex(); |
| }; |
| |
| /*! asn1hex-1.1.6.js (c) 2012-2015 Kenji Urushima | kjur.github.com/jsrsasign/license |
| */ |
| /* |
| * asn1hex.js - Hexadecimal represented ASN.1 string library |
| * |
| * Copyright (c) 2010-2015 Kenji Urushima (kenji.urushima@gmail.com) |
| * |
| * This software is licensed under the terms of the MIT License. |
| * http://kjur.github.com/jsrsasign/license/ |
| * |
| * The above copyright and license notice shall be |
| * included in all copies or substantial portions of the Software. |
| */ |
| |
| /** |
| * @fileOverview |
| * @name asn1hex-1.1.js |
| * @author Kenji Urushima kenji.urushima@gmail.com |
| * @version asn1hex 1.1.6 (2015-Jun-11) |
| * @license <a href="http://kjur.github.io/jsrsasign/license/">MIT License</a> |
| */ |
| |
| /* |
| * MEMO: |
| * f('3082025b02...', 2) ... 82025b ... 3bytes |
| * f('020100', 2) ... 01 ... 1byte |
| * f('0203001...', 2) ... 03 ... 1byte |
| * f('02818003...', 2) ... 8180 ... 2bytes |
| * f('3080....0000', 2) ... 80 ... -1 |
| * |
| * Requirements: |
| * - ASN.1 type octet length MUST be 1. |
| * (i.e. ASN.1 primitives like SET, SEQUENCE, INTEGER, OCTETSTRING ...) |
| */ |
| |
| /** |
| * ASN.1 DER encoded hexadecimal string utility class |
| * @name ASN1HEX |
| * @class ASN.1 DER encoded hexadecimal string utility class |
| * @since jsrsasign 1.1 |
| */ |
| var intShim = require('jsbn'); |
| |
| var ASN1HEX = new function() { |
| /** |
| * get byte length for ASN.1 L(length) bytes |
| * @name getByteLengthOfL_AtObj |
| * @memberOf ASN1HEX |
| * @function |
| * @param {String} s hexadecimal string of ASN.1 DER encoded data |
| * @param {Number} pos string index |
| * @return byte length for ASN.1 L(length) bytes |
| */ |
| this.getByteLengthOfL_AtObj = function(s, pos) { |
| if (s.substring(pos + 2, pos + 3) != '8') return 1; |
| var i = parseInt(s.substring(pos + 3, pos + 4)); |
| if (i == 0) return -1; // length octet '80' indefinite length |
| if (0 < i && i < 10) return i + 1; // including '8?' octet; |
| return -2; // malformed format |
| }; |
| |
| /** |
| * get hexadecimal string for ASN.1 L(length) bytes |
| * @name getHexOfL_AtObj |
| * @memberOf ASN1HEX |
| * @function |
| * @param {String} s hexadecimal string of ASN.1 DER encoded data |
| * @param {Number} pos string index |
| * @return {String} hexadecimal string for ASN.1 L(length) bytes |
| */ |
| this.getHexOfL_AtObj = function(s, pos) { |
| var len = this.getByteLengthOfL_AtObj(s, pos); |
| if (len < 1) return ''; |
| return s.substring(pos + 2, pos + 2 + len * 2); |
| }; |
| |
| // getting ASN.1 length value at the position 'idx' of |
| // hexa decimal string 's'. |
| // |
| // f('3082025b02...', 0) ... 82025b ... ??? |
| // f('020100', 0) ... 01 ... 1 |
| // f('0203001...', 0) ... 03 ... 3 |
| // f('02818003...', 0) ... 8180 ... 128 |
| /** |
| * get integer value of ASN.1 length for ASN.1 data |
| * @name getIntOfL_AtObj |
| * @memberOf ASN1HEX |
| * @function |
| * @param {String} s hexadecimal string of ASN.1 DER encoded data |
| * @param {Number} pos string index |
| * @return ASN.1 L(length) integer value |
| */ |
| this.getIntOfL_AtObj = function(s, pos) { |
| var hLength = this.getHexOfL_AtObj(s, pos); |
| if (hLength == '') return -1; |
| var bi; |
| if (parseInt(hLength.substring(0, 1)) < 8) { |
| bi = new BigInteger(hLength, 16); |
| } else { |
| bi = new BigInteger(hLength.substring(2), 16); |
| } |
| return bi.intValue(); |
| }; |
| |
| /** |
| * get ASN.1 value starting string position for ASN.1 object refered by index 'idx'. |
| * @name getStartPosOfV_AtObj |
| * @memberOf ASN1HEX |
| * @function |
| * @param {String} s hexadecimal string of ASN.1 DER encoded data |
| * @param {Number} pos string index |
| */ |
| this.getStartPosOfV_AtObj = function(s, pos) { |
| var l_len = this.getByteLengthOfL_AtObj(s, pos); |
| if (l_len < 0) return l_len; |
| return pos + (l_len + 1) * 2; |
| }; |
| |
| /** |
| * get hexadecimal string of ASN.1 V(value) |
| * @name getHexOfV_AtObj |
| * @memberOf ASN1HEX |
| * @function |
| * @param {String} s hexadecimal string of ASN.1 DER encoded data |
| * @param {Number} pos string index |
| * @return {String} hexadecimal string of ASN.1 value. |
| */ |
| this.getHexOfV_AtObj = function(s, pos) { |
| var pos1 = this.getStartPosOfV_AtObj(s, pos); |
| var len = this.getIntOfL_AtObj(s, pos); |
| return s.substring(pos1, pos1 + len * 2); |
| }; |
| |
| /** |
| * get hexadecimal string of ASN.1 TLV at |
| * @name getHexOfTLV_AtObj |
| * @memberOf ASN1HEX |
| * @function |
| * @param {String} s hexadecimal string of ASN.1 DER encoded data |
| * @param {Number} pos string index |
| * @return {String} hexadecimal string of ASN.1 TLV. |
| * @since 1.1 |
| */ |
| this.getHexOfTLV_AtObj = function(s, pos) { |
| var hT = s.substr(pos, 2); |
| var hL = this.getHexOfL_AtObj(s, pos); |
| var hV = this.getHexOfV_AtObj(s, pos); |
| return hT + hL + hV; |
| }; |
| |
| /** |
| * get next sibling starting index for ASN.1 object string |
| * @name getPosOfNextSibling_AtObj |
| * @memberOf ASN1HEX |
| * @function |
| * @param {String} s hexadecimal string of ASN.1 DER encoded data |
| * @param {Number} pos string index |
| * @return next sibling starting index for ASN.1 object string |
| */ |
| this.getPosOfNextSibling_AtObj = function(s, pos) { |
| var pos1 = this.getStartPosOfV_AtObj(s, pos); |
| var len = this.getIntOfL_AtObj(s, pos); |
| return pos1 + len * 2; |
| }; |
| |
| /** |
| * get array of indexes of child ASN.1 objects |
| * @name getPosArrayOfChildren_AtObj |
| * @memberOf ASN1HEX |
| * @function |
| * @param {String} s hexadecimal string of ASN.1 DER encoded data |
| * @param {Number} start string index of ASN.1 object |
| * @return {Array of Number} array of indexes for childen of ASN.1 objects |
| */ |
| this.getPosArrayOfChildren_AtObj = function(h, pos) { |
| var a = new Array(); |
| var p0 = this.getStartPosOfV_AtObj(h, pos); |
| a.push(p0); |
| |
| var len = this.getIntOfL_AtObj(h, pos); |
| var p = p0; |
| var k = 0; |
| while (1) { |
| var pNext = this.getPosOfNextSibling_AtObj(h, p); |
| if (pNext == null || (pNext - p0 >= (len * 2))) break; |
| if (k >= 200) break; |
| |
| a.push(pNext); |
| p = pNext; |
| |
| k++; |
| } |
| |
| return a; |
| }; |
| |
| /** |
| * get string index of nth child object of ASN.1 object refered by h, idx |
| * @name getNthChildIndex_AtObj |
| * @memberOf ASN1HEX |
| * @function |
| * @param {String} h hexadecimal string of ASN.1 DER encoded data |
| * @param {Number} idx start string index of ASN.1 object |
| * @param {Number} nth for child |
| * @return {Number} string index of nth child. |
| * @since 1.1 |
| */ |
| this.getNthChildIndex_AtObj = function(h, idx, nth) { |
| var a = this.getPosArrayOfChildren_AtObj(h, idx); |
| return a[nth]; |
| }; |
| |
| // ========== decendant methods ============================== |
| /** |
| * get string index of nth child object of ASN.1 object refered by h, idx |
| * @name getDecendantIndexByNthList |
| * @memberOf ASN1HEX |
| * @function |
| * @param {String} h hexadecimal string of ASN.1 DER encoded data |
| * @param {Number} currentIndex start string index of ASN.1 object |
| * @param {Array of Number} nthList array list of nth |
| * @return {Number} string index refered by nthList |
| * @since 1.1 |
| * @example |
| * The "nthList" is a index list of structured ASN.1 object |
| * reference. Here is a sample structure and "nthList"s which |
| * refers each objects. |
| * |
| * SQUENCE - |
| * SEQUENCE - [0] |
| * IA5STRING 000 - [0, 0] |
| * UTF8STRING 001 - [0, 1] |
| * SET - [1] |
| * IA5STRING 010 - [1, 0] |
| * UTF8STRING 011 - [1, 1] |
| */ |
| this.getDecendantIndexByNthList = function(h, currentIndex, nthList) { |
| if (nthList.length == 0) { |
| return currentIndex; |
| } |
| var firstNth = nthList.shift(); |
| var a = this.getPosArrayOfChildren_AtObj(h, currentIndex); |
| return this.getDecendantIndexByNthList(h, a[firstNth], nthList); |
| }; |
| |
| /** |
| * get hexadecimal string of ASN.1 TLV refered by current index and nth index list. |
| * @name getDecendantHexTLVByNthList |
| * @memberOf ASN1HEX |
| * @function |
| * @param {String} h hexadecimal string of ASN.1 DER encoded data |
| * @param {Number} currentIndex start string index of ASN.1 object |
| * @param {Array of Number} nthList array list of nth |
| * @return {Number} hexadecimal string of ASN.1 TLV refered by nthList |
| * @since 1.1 |
| */ |
| this.getDecendantHexTLVByNthList = function(h, currentIndex, nthList) { |
| var idx = this.getDecendantIndexByNthList(h, currentIndex, nthList); |
| return this.getHexOfTLV_AtObj(h, idx); |
| }; |
| |
| /** |
| * get hexadecimal string of ASN.1 V refered by current index and nth index list. |
| * @name getDecendantHexVByNthList |
| * @memberOf ASN1HEX |
| * @function |
| * @param {String} h hexadecimal string of ASN.1 DER encoded data |
| * @param {Number} currentIndex start string index of ASN.1 object |
| * @param {Array of Number} nthList array list of nth |
| * @return {Number} hexadecimal string of ASN.1 V refered by nthList |
| * @since 1.1 |
| */ |
| this.getDecendantHexVByNthList = function(h, currentIndex, nthList) { |
| var idx = this.getDecendantIndexByNthList(h, currentIndex, nthList); |
| return this.getHexOfV_AtObj(h, idx); |
| }; |
| }; |
| |
| /* |
| * @since asn1hex 1.1.4 |
| */ |
| ASN1HEX.getVbyList = function(h, currentIndex, nthList, checkingTag) { |
| var idx = this.getDecendantIndexByNthList(h, currentIndex, nthList); |
| if (idx === undefined) { |
| throw "can't find nthList object"; |
| } |
| if (checkingTag !== undefined) { |
| if (h.substr(idx, 2) != checkingTag) { |
| throw "checking tag doesn't match: " + |
| h.substr(idx,2) + "!=" + checkingTag; |
| } |
| } |
| return this.getHexOfV_AtObj(h, idx); |
| }; |
| |
| /** |
| * get OID string from hexadecimal encoded value |
| * @name hextooidstr |
| * @memberOf ASN1HEX |
| * @function |
| * @param {String} hex hexadecmal string of ASN.1 DER encoded OID value |
| * @return {String} OID string (ex. '1.2.3.4.567') |
| * @since asn1hex 1.1.5 |
| */ |
| ASN1HEX.hextooidstr = function(hex) { |
| var zeroPadding = function(s, len) { |
| if (s.length >= len) return s; |
| return new Array(len - s.length + 1).join('0') + s; |
| }; |
| |
| var a = []; |
| |
| // a[0], a[1] |
| var hex0 = hex.substr(0, 2); |
| var i0 = parseInt(hex0, 16); |
| a[0] = new String(Math.floor(i0 / 40)); |
| a[1] = new String(i0 % 40); |
| |
| // a[2]..a[n] |
| var hex1 = hex.substr(2); |
| var b = []; |
| for (var i = 0; i < hex1.length / 2; i++) { |
| b.push(parseInt(hex1.substr(i * 2, 2), 16)); |
| } |
| var c = []; |
| var cbin = ""; |
| for (var i = 0; i < b.length; i++) { |
| if (b[i] & 0x80) { |
| cbin = cbin + zeroPadding((b[i] & 0x7f).toString(2), 7); |
| } else { |
| cbin = cbin + zeroPadding((b[i] & 0x7f).toString(2), 7); |
| c.push(new String(parseInt(cbin, 2))); |
| cbin = ""; |
| } |
| } |
| |
| var s = a.join("."); |
| if (c.length > 0) s = s + "." + c.join("."); |
| return s; |
| }; |
| |
| /** |
| * get string of simple ASN.1 dump from hexadecimal ASN.1 data |
| * @name dump |
| * @memberOf ASN1HEX |
| * @function |
| * @param {String} hex hexadecmal string of ASN.1 data |
| * @param {Array} associative array of flags for dump (OPTION) |
| * @param {Number} idx string index for starting dump (OPTION) |
| * @param {String} indent string (OPTION) |
| * @return {String} string of simple ASN.1 dump |
| * @since jsrsasign 4.8.3 asn1hex 1.1.6 |
| * @description |
| * This method will get an ASN.1 dump from |
| * hexadecmal string of ASN.1 DER encoded data. |
| * Here are features: |
| * <ul> |
| * <li>ommit long hexadecimal string</li> |
| * <li>dump encapsulated OCTET STRING (good for X.509v3 extensions)</li> |
| * <li>structured/primitive context specific tag support (i.e. [0], [3] ...)</li> |
| * <li>automatic decode for implicit primitive context specific tag |
| * (good for X.509v3 extension value) |
| * <ul> |
| * <li>if hex starts '68747470'(i.e. http) it is decoded as utf8 encoded string.</li> |
| * <li>if it is in 'subjectAltName' extension value and is '[2]'(dNSName) tag |
| * value will be encoded as utf8 string</li> |
| * <li>otherwise it shows as hexadecimal string</li> |
| * </ul> |
| * </li> |
| * </ul> |
| * @example |
| * // ASN.1 INTEGER |
| * ASN1HEX.dump('0203012345') |
| * ↓ |
| * INTEGER 012345 |
| * // ASN.1 Object Identifier |
| * ASN1HEX.dump('06052b0e03021a') |
| * ↓ |
| * ObjectIdentifier sha1 (1 3 14 3 2 26) |
| * // ASN.1 SEQUENCE |
| * ASN1HEX.dump('3006020101020102') |
| * ↓ |
| * SEQUENCE |
| * INTEGER 01 |
| * INTEGER 02 |
| * // ASN.1 DUMP FOR X.509 CERTIFICATE |
| * ASN1HEX.dump(X509.pemToHex(certPEM)) |
| * ↓ |
| * SEQUENCE |
| * SEQUENCE |
| * [0] |
| * INTEGER 02 |
| * INTEGER 0c009310d206dbe337553580118ddc87 |
| * SEQUENCE |
| * ObjectIdentifier SHA256withRSA (1 2 840 113549 1 1 11) |
| * NULL |
| * SEQUENCE |
| * SET |
| * SEQUENCE |
| * ObjectIdentifier countryName (2 5 4 6) |
| * PrintableString 'US' |
| * : |
| */ |
| ASN1HEX.dump = function(hex, flags, idx, indent) { |
| var _skipLongHex = function(hex, limitNumOctet) { |
| if (hex.length <= limitNumOctet * 2) { |
| return hex; |
| } else { |
| var s = hex.substr(0, limitNumOctet) + |
| "..(total " + hex.length / 2 + "bytes).." + |
| hex.substr(hex.length - limitNumOctet, limitNumOctet); |
| return s; |
| }; |
| }; |
| |
| if (flags === undefined) flags = { "ommit_long_octet": 32 }; |
| if (idx === undefined) idx = 0; |
| if (indent === undefined) indent = ""; |
| var skipLongHex = flags.ommit_long_octet; |
| |
| if (hex.substr(idx, 2) == "01") { |
| var v = ASN1HEX.getHexOfV_AtObj(hex, idx); |
| if (v == "00") { |
| return indent + "BOOLEAN FALSE\n"; |
| } else { |
| return indent + "BOOLEAN TRUE\n"; |
| } |
| } |
| if (hex.substr(idx, 2) == "02") { |
| var v = ASN1HEX.getHexOfV_AtObj(hex, idx); |
| return indent + "INTEGER " + _skipLongHex(v, skipLongHex) + "\n"; |
| } |
| if (hex.substr(idx, 2) == "03") { |
| var v = ASN1HEX.getHexOfV_AtObj(hex, idx); |
| return indent + "BITSTRING " + _skipLongHex(v, skipLongHex) + "\n"; |
| } |
| if (hex.substr(idx, 2) == "04") { |
| var v = ASN1HEX.getHexOfV_AtObj(hex, idx); |
| if (ASN1HEX.isASN1HEX(v)) { |
| var s = indent + "OCTETSTRING, encapsulates\n"; |
| s = s + ASN1HEX.dump(v, flags, 0, indent + " "); |
| return s; |
| } else { |
| return indent + "OCTETSTRING " + _skipLongHex(v, skipLongHex) + "\n"; |
| } |
| } |
| if (hex.substr(idx, 2) == "05") { |
| return indent + "NULL\n"; |
| } |
| if (hex.substr(idx, 2) == "06") { |
| var hV = ASN1HEX.getHexOfV_AtObj(hex, idx); |
| var oidDot = KJUR.asn1.ASN1Util.oidHexToInt(hV); |
| var oidName = KJUR.asn1.x509.OID.oid2name(oidDot); |
| var oidSpc = oidDot.replace(/\./g, ' '); |
| if (oidName != '') { |
| return indent + "ObjectIdentifier " + oidName + " (" + oidSpc + ")\n"; |
| } else { |
| return indent + "ObjectIdentifier (" + oidSpc + ")\n"; |
| } |
| } |
| if (hex.substr(idx, 2) == "0c") { |
| return indent + "UTF8String '" + hextoutf8(ASN1HEX.getHexOfV_AtObj(hex, idx)) + "'\n"; |
| } |
| if (hex.substr(idx, 2) == "13") { |
| return indent + "PrintableString '" + hextoutf8(ASN1HEX.getHexOfV_AtObj(hex, idx)) + "'\n"; |
| } |
| if (hex.substr(idx, 2) == "14") { |
| return indent + "TeletexString '" + hextoutf8(ASN1HEX.getHexOfV_AtObj(hex, idx)) + "'\n"; |
| } |
| if (hex.substr(idx, 2) == "16") { |
| return indent + "IA5String '" + hextoutf8(ASN1HEX.getHexOfV_AtObj(hex, idx)) + "'\n"; |
| } |
| if (hex.substr(idx, 2) == "17") { |
| return indent + "UTCTime " + hextoutf8(ASN1HEX.getHexOfV_AtObj(hex, idx)) + "\n"; |
| } |
| if (hex.substr(idx, 2) == "18") { |
| return indent + "GeneralizedTime " + hextoutf8(ASN1HEX.getHexOfV_AtObj(hex, idx)) + "\n"; |
| } |
| if (hex.substr(idx, 2) == "30") { |
| if (hex.substr(idx, 4) == "3000") { |
| return indent + "SEQUENCE {}\n"; |
| } |
| |
| var s = indent + "SEQUENCE\n"; |
| var aIdx = ASN1HEX.getPosArrayOfChildren_AtObj(hex, idx); |
| |
| var flagsTemp = flags; |
| |
| if ((aIdx.length == 2 || aIdx.length == 3) && |
| hex.substr(aIdx[0], 2) == "06" && |
| hex.substr(aIdx[aIdx.length - 1], 2) == "04") { // supposed X.509v3 extension |
| var oidHex = ASN1HEX.getHexOfV_AtObj(hex, aIdx[0]); |
| var oidDot = KJUR.asn1.ASN1Util.oidHexToInt(oidHex); |
| var oidName = KJUR.asn1.x509.OID.oid2name(oidDot); |
| |
| var flagsClone = JSON.parse(JSON.stringify(flags)); |
| flagsClone.x509ExtName = oidName; |
| flagsTemp = flagsClone; |
| } |
| |
| for (var i = 0; i < aIdx.length; i++) { |
| s = s + ASN1HEX.dump(hex, flagsTemp, aIdx[i], indent + " "); |
| } |
| return s; |
| } |
| if (hex.substr(idx, 2) == "31") { |
| var s = indent + "SET\n"; |
| var aIdx = ASN1HEX.getPosArrayOfChildren_AtObj(hex, idx); |
| for (var i = 0; i < aIdx.length; i++) { |
| s = s + ASN1HEX.dump(hex, flags, aIdx[i], indent + " "); |
| } |
| return s; |
| } |
| var tag = parseInt(hex.substr(idx, 2), 16); |
| if ((tag & 128) != 0) { // context specific |
| var tagNumber = tag & 31; |
| if ((tag & 32) != 0) { // structured tag |
| var s = indent + "[" + tagNumber + "]\n"; |
| var aIdx = ASN1HEX.getPosArrayOfChildren_AtObj(hex, idx); |
| for (var i = 0; i < aIdx.length; i++) { |
| s = s + ASN1HEX.dump(hex, flags, aIdx[i], indent + " "); |
| } |
| return s; |
| } else { // primitive tag |
| var v = ASN1HEX.getHexOfV_AtObj(hex, idx); |
| if (v.substr(0, 8) == "68747470") { // http |
| v = hextoutf8(v); |
| } |
| if (flags.x509ExtName === "subjectAltName" && |
| tagNumber == 2) { |
| v = hextoutf8(v); |
| } |
| |
| var s = indent + "[" + tagNumber + "] " + v + "\n"; |
| return s; |
| } |
| } |
| return indent + "UNKNOWN(" + hex.substr(idx, 2) + ") " + ASN1HEX.getHexOfV_AtObj(hex, idx) + "\n"; |
| }; |
| |
| /** |
| * check wheather the string is ASN.1 hexadecimal string or not |
| * @name isASN1HEX |
| * @memberOf ASN1HEX |
| * @function |
| * @param {String} hex string to check whether it is hexadecmal string for ASN.1 DER or not |
| * @return {Boolean} true if it is hexadecimal string of ASN.1 data otherwise false |
| * @since jsrsasign 4.8.3 asn1hex 1.1.6 |
| * @description |
| * This method checks wheather the argument 'hex' is a hexadecimal string of |
| * ASN.1 data or not. |
| * @example |
| * ASN1HEX.isASN1HEX('0203012345') → true // PROPER ASN.1 INTEGER |
| * ASN1HEX.isASN1HEX('0203012345ff') → false // TOO LONG VALUE |
| * ASN1HEX.isASN1HEX('02030123') → false // TOO SHORT VALUE |
| * ASN1HEX.isASN1HEX('fa3bcd') → false // WRONG FOR ASN.1 |
| */ |
| ASN1HEX.isASN1HEX = function(hex) { |
| if (hex.length % 2 == 1) return false; |
| |
| var intL = ASN1HEX.getIntOfL_AtObj(hex, 0); |
| var tV = hex.substr(0, 2); |
| var lV = ASN1HEX.getHexOfL_AtObj(hex, 0); |
| var hVLength = hex.length - tV.length - lV.length; |
| if (hVLength == intL * 2) return true; |
| |
| return false; |
| }; |
| |
| exports.ASN1HEX = ASN1HEX; |
| module.exports = exports; |
| /*! x509-1.1.6.js (c) 2012-2015 Kenji Urushima | kjur.github.com/jsrsasign/license |
| */ |
| /* |
| * x509.js - X509 class to read subject public key from certificate. |
| * |
| * Copyright (c) 2010-2015 Kenji Urushima (kenji.urushima@gmail.com) |
| * |
| * This software is licensed under the terms of the MIT License. |
| * http://kjur.github.com/jsrsasign/license |
| * |
| * The above copyright and license notice shall be |
| * included in all copies or substantial portions of the Software. |
| */ |
| |
| /** |
| * @fileOverview |
| * @name x509-1.1.js |
| * @author Kenji Urushima kenji.urushima@gmail.com |
| * @version x509 1.1.6 (2015-May-20) |
| * @since jsrsasign 1.x.x |
| * @license <a href="http://kjur.github.io/jsrsasign/license/">MIT License</a> |
| */ |
| |
| /* |
| * Depends: |
| * base64.js |
| * rsa.js |
| * asn1hex.js |
| */ |
| |
| /** |
| * X.509 certificate class.<br/> |
| * @class X.509 certificate class |
| * @property {RSAKey} subjectPublicKeyRSA Tom Wu's RSAKey object |
| * @property {String} subjectPublicKeyRSA_hN hexadecimal string for modulus of RSA public key |
| * @property {String} subjectPublicKeyRSA_hE hexadecimal string for public exponent of RSA public key |
| * @property {String} hex hexacedimal string for X.509 certificate. |
| * @author Kenji Urushima |
| * @version 1.0.1 (08 May 2012) |
| * @see <a href="http://kjur.github.com/jsrsasigns/">'jwrsasign'(RSA Sign JavaScript Library) home page http://kjur.github.com/jsrsasign/</a> |
| */ |
| |
| var b64tohex = require('./base64.js').b64tohex; |
| var RSAKey = require('./rsa.js').RSAKey; |
| var ASN1HEX = require('./asn1hex-1.1.js').ASN1HEX; |
| |
| function X509() { |
| this.subjectPublicKeyRSA = null; |
| this.subjectPublicKeyRSA_hN = null; |
| this.subjectPublicKeyRSA_hE = null; |
| this.hex = null; |
| |
| // ===== get basic fields from hex ===================================== |
| |
| /** |
| * get hexadecimal string of serialNumber field of certificate.<br/> |
| * @name getSerialNumberHex |
| * @memberOf X509# |
| * @function |
| */ |
| this.getSerialNumberHex = function() { |
| return ASN1HEX.getDecendantHexVByNthList(this.hex, 0, [0, 1]); |
| }; |
| |
| /** |
| * get hexadecimal string of issuer field TLV of certificate.<br/> |
| * @name getIssuerHex |
| * @memberOf X509# |
| * @function |
| */ |
| this.getIssuerHex = function() { |
| return ASN1HEX.getDecendantHexTLVByNthList(this.hex, 0, [0, 3]); |
| }; |
| |
| /** |
| * get string of issuer field of certificate.<br/> |
| * @name getIssuerString |
| * @memberOf X509# |
| * @function |
| */ |
| this.getIssuerString = function() { |
| return X509.hex2dn(ASN1HEX.getDecendantHexTLVByNthList(this.hex, 0, [0, 3])); |
| }; |
| |
| /** |
| * get hexadecimal string of subject field of certificate.<br/> |
| * @name getSubjectHex |
| * @memberOf X509# |
| * @function |
| */ |
| this.getSubjectHex = function() { |
| return ASN1HEX.getDecendantHexTLVByNthList(this.hex, 0, [0, 5]); |
| }; |
| |
| /** |
| * get string of subject field of certificate.<br/> |
| * @name getSubjectString |
| * @memberOf X509# |
| * @function |
| */ |
| this.getSubjectString = function() { |
| return X509.hex2dn(ASN1HEX.getDecendantHexTLVByNthList(this.hex, 0, [0, 5])); |
| }; |
| |
| /** |
| * get notBefore field string of certificate.<br/> |
| * @name getNotBefore |
| * @memberOf X509# |
| * @function |
| */ |
| this.getNotBefore = function() { |
| var s = ASN1HEX.getDecendantHexVByNthList(this.hex, 0, [0, 4, 0]); |
| s = s.replace(/(..)/g, "%$1"); |
| s = decodeURIComponent(s); |
| return s; |
| }; |
| |
| /** |
| * get notAfter field string of certificate.<br/> |
| * @name getNotAfter |
| * @memberOf X509# |
| * @function |
| */ |
| this.getNotAfter = function() { |
| var s = ASN1HEX.getDecendantHexVByNthList(this.hex, 0, [0, 4, 1]); |
| s = s.replace(/(..)/g, "%$1"); |
| s = decodeURIComponent(s); |
| return s; |
| }; |
| |
| // ===== read certificate public key ========================== |
| |
| // ===== read certificate ===================================== |
| /** |
| * read PEM formatted X.509 certificate from string.<br/> |
| * @name readCertPEM |
| * @memberOf X509# |
| * @function |
| * @param {String} sCertPEM string for PEM formatted X.509 certificate |
| */ |
| this.readCertPEM = function(sCertPEM) { |
| var hCert = X509.pemToHex(sCertPEM); |
| var a = X509.getPublicKeyHexArrayFromCertHex(hCert); |
| var rsa = new RSAKey(); |
| rsa.setPublic(a[0], a[1]); |
| this.subjectPublicKeyRSA = rsa; |
| this.subjectPublicKeyRSA_hN = a[0]; |
| this.subjectPublicKeyRSA_hE = a[1]; |
| this.hex = hCert; |
| }; |
| |
| this.readCertPEMWithoutRSAInit = function(sCertPEM) { |
| var hCert = X509.pemToHex(sCertPEM); |
| var a = X509.getPublicKeyHexArrayFromCertHex(hCert); |
| this.subjectPublicKeyRSA.setPublic(a[0], a[1]); |
| this.subjectPublicKeyRSA_hN = a[0]; |
| this.subjectPublicKeyRSA_hE = a[1]; |
| this.hex = hCert; |
| }; |
| }; |
| |
| X509.pemToBase64 = function(sCertPEM) { |
| var s = sCertPEM; |
| s = s.replace("-----BEGIN CERTIFICATE-----", ""); |
| s = s.replace("-----END CERTIFICATE-----", ""); |
| s = s.replace(/[ \n]+/g, ""); |
| return s; |
| }; |
| |
| X509.pemToHex = function(sCertPEM) { |
| var b64Cert = X509.pemToBase64(sCertPEM); |
| var hCert = b64tohex(b64Cert); |
| return hCert; |
| }; |
| |
| // NOTE: Without BITSTRING encapsulation. |
| X509.getSubjectPublicKeyPosFromCertHex = function(hCert) { |
| var pInfo = X509.getSubjectPublicKeyInfoPosFromCertHex(hCert); |
| if (pInfo == -1) return -1; |
| var a = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, pInfo); |
| if (a.length != 2) return -1; |
| var pBitString = a[1]; |
| if (hCert.substring(pBitString, pBitString + 2) != '03') return -1; |
| var pBitStringV = ASN1HEX.getStartPosOfV_AtObj(hCert, pBitString); |
| |
| if (hCert.substring(pBitStringV, pBitStringV + 2) != '00') return -1; |
| return pBitStringV + 2; |
| }; |
| |
| // NOTE: privateKeyUsagePeriod field of X509v2 not supported. |
| // NOTE: v1 and v3 supported |
| X509.getSubjectPublicKeyInfoPosFromCertHex = function(hCert) { |
| var pTbsCert = ASN1HEX.getStartPosOfV_AtObj(hCert, 0); |
| var a = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, pTbsCert); |
| if (a.length < 1) return -1; |
| if (hCert.substring(a[0], a[0] + 10) == "a003020102") { // v3 |
| if (a.length < 6) return -1; |
| return a[6]; |
| } else { |
| if (a.length < 5) return -1; |
| return a[5]; |
| } |
| }; |
| |
| X509.getPublicKeyHexArrayFromCertHex = function(hCert) { |
| var p = X509.getSubjectPublicKeyPosFromCertHex(hCert); |
| var a = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, p); |
| if (a.length != 2) return []; |
| var hN = ASN1HEX.getHexOfV_AtObj(hCert, a[0]); |
| var hE = ASN1HEX.getHexOfV_AtObj(hCert, a[1]); |
| if (hN != null && hE != null) { |
| return [hN, hE]; |
| } else { |
| return []; |
| } |
| }; |
| |
| X509.getHexTbsCertificateFromCert = function(hCert) { |
| var pTbsCert = ASN1HEX.getStartPosOfV_AtObj(hCert, 0); |
| return pTbsCert; |
| }; |
| |
| X509.getPublicKeyHexArrayFromCertPEM = function(sCertPEM) { |
| var hCert = X509.pemToHex(sCertPEM); |
| var a = X509.getPublicKeyHexArrayFromCertHex(hCert); |
| return a; |
| }; |
| |
| X509.hex2dn = function(hDN) { |
| var s = ""; |
| var a = ASN1HEX.getPosArrayOfChildren_AtObj(hDN, 0); |
| for (var i = 0; i < a.length; i++) { |
| var hRDN = ASN1HEX.getHexOfTLV_AtObj(hDN, a[i]); |
| s = s + "/" + X509.hex2rdn(hRDN); |
| } |
| return s; |
| }; |
| |
| X509.hex2rdn = function(hRDN) { |
| var hType = ASN1HEX.getDecendantHexTLVByNthList(hRDN, 0, [0, 0]); |
| var hValue = ASN1HEX.getDecendantHexVByNthList(hRDN, 0, [0, 1]); |
| var type = ""; |
| try { type = X509.DN_ATTRHEX[hType]; } catch (ex) { type = hType; } |
| hValue = hValue.replace(/(..)/g, "%$1"); |
| var value = decodeURIComponent(hValue); |
| return type + "=" + value; |
| }; |
| |
| X509.DN_ATTRHEX = { |
| "0603550406": "C", |
| "060355040a": "O", |
| "060355040b": "OU", |
| "0603550403": "CN", |
| "0603550405": "SN", |
| "0603550408": "ST", |
| "0603550407": "L" |
| }; |
| |
| /** |
| * get RSAKey/ECDSA public key object from PEM certificate string |
| * @name getPublicKeyFromCertPEM |
| * @memberOf X509 |
| * @function |
| * @param {String} sCertPEM PEM formatted RSA/ECDSA/DSA X.509 certificate |
| * @return returns RSAKey/KJUR.crypto.{ECDSA,DSA} object of public key |
| * @since x509 1.1.1 |
| * @description |
| * NOTE: DSA is also supported since x509 1.1.2. |
| */ |
| X509.getPublicKeyFromCertPEM = function(sCertPEM) { |
| var info = X509.getPublicKeyInfoPropOfCertPEM(sCertPEM); |
| |
| if (info.algoid == "2a864886f70d010101") { // RSA |
| var aRSA = KEYUTIL.parsePublicRawRSAKeyHex(info.keyhex); |
| var key = new RSAKey(); |
| key.setPublic(aRSA.n, aRSA.e); |
| return key; |
| } else if (info.algoid == "2a8648ce3d0201") { // ECC |
| var curveName = KJUR.crypto.OID.oidhex2name[info.algparam]; |
| var key = new KJUR.crypto.ECDSA({'curve': curveName, 'info': info.keyhex}); |
| key.setPublicKeyHex(info.keyhex); |
| return key; |
| } else if (info.algoid == "2a8648ce380401") { // DSA 1.2.840.10040.4.1 |
| var p = ASN1HEX.getVbyList(info.algparam, 0, [0], "02"); |
| var q = ASN1HEX.getVbyList(info.algparam, 0, [1], "02"); |
| var g = ASN1HEX.getVbyList(info.algparam, 0, [2], "02"); |
| var y = ASN1HEX.getHexOfV_AtObj(info.keyhex, 0); |
| y = y.substr(2); |
| var key = new KJUR.crypto.DSA(); |
| key.setPublic(new BigInteger(p, 16), |
| new BigInteger(q, 16), |
| new BigInteger(g, 16), |
| new BigInteger(y, 16)); |
| return key; |
| } else { |
| throw "unsupported key"; |
| } |
| }; |
| |
| /** |
| * get public key information from PEM certificate |
| * @name getPublicKeyInfoPropOfCertPEM |
| * @memberOf X509 |
| * @function |
| * @param {String} sCertPEM string of PEM formatted certificate |
| * @return {Hash} hash of information for public key |
| * @since x509 1.1.1 |
| * @description |
| * Resulted associative array has following properties: |
| * <ul> |
| * <li>algoid - hexadecimal string of OID of asymmetric key algorithm</li> |
| * <li>algparam - hexadecimal string of OID of ECC curve name or null</li> |
| * <li>keyhex - hexadecimal string of key in the certificate</li> |
| * </ul> |
| * @since x509 1.1.1 |
| */ |
| X509.getPublicKeyInfoPropOfCertPEM = function(sCertPEM) { |
| var result = {}; |
| result.algparam = null; |
| var hCert = X509.pemToHex(sCertPEM); |
| |
| // 1. Certificate ASN.1 |
| var a1 = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, 0); |
| if (a1.length != 3) |
| throw "malformed X.509 certificate PEM (code:001)"; // not 3 item of seq Cert |
| |
| // 2. tbsCertificate |
| if (hCert.substr(a1[0], 2) != "30") |
| throw "malformed X.509 certificate PEM (code:002)"; // tbsCert not seq |
| |
| var a2 = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, a1[0]); |
| |
| // 3. subjectPublicKeyInfo |
| if (a2.length < 7) |
| throw "malformed X.509 certificate PEM (code:003)"; // no subjPubKeyInfo |
| |
| var a3 = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, a2[6]); |
| |
| if (a3.length != 2) |
| throw "malformed X.509 certificate PEM (code:004)"; // not AlgId and PubKey |
| |
| // 4. AlgId |
| var a4 = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, a3[0]); |
| |
| if (a4.length != 2) |
| throw "malformed X.509 certificate PEM (code:005)"; // not 2 item in AlgId |
| |
| result.algoid = ASN1HEX.getHexOfV_AtObj(hCert, a4[0]); |
| |
| if (hCert.substr(a4[1], 2) == "06") { // EC |
| result.algparam = ASN1HEX.getHexOfV_AtObj(hCert, a4[1]); |
| } else if (hCert.substr(a4[1], 2) == "30") { // DSA |
| result.algparam = ASN1HEX.getHexOfTLV_AtObj(hCert, a4[1]); |
| } |
| |
| // 5. Public Key Hex |
| if (hCert.substr(a3[1], 2) != "03") |
| throw "malformed X.509 certificate PEM (code:006)"; // not bitstring |
| |
| var unusedBitAndKeyHex = ASN1HEX.getHexOfV_AtObj(hCert, a3[1]); |
| result.keyhex = unusedBitAndKeyHex.substr(2); |
| |
| return result; |
| }; |
| |
| /** |
| * get position of subjectPublicKeyInfo field from HEX certificate |
| * @name getPublicKeyInfoPosOfCertHEX |
| * @memberOf X509 |
| * @function |
| * @param {String} hCert hexadecimal string of certificate |
| * @return {Integer} position in hexadecimal string |
| * @since x509 1.1.4 |
| * @description |
| * get position for SubjectPublicKeyInfo field in the hexadecimal string of |
| * certificate. |
| */ |
| X509.getPublicKeyInfoPosOfCertHEX = function(hCert) { |
| // 1. Certificate ASN.1 |
| var a1 = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, 0); |
| if (a1.length != 3) |
| throw "malformed X.509 certificate PEM (code:001)"; // not 3 item of seq Cert |
| |
| // 2. tbsCertificate |
| if (hCert.substr(a1[0], 2) != "30") |
| throw "malformed X.509 certificate PEM (code:002)"; // tbsCert not seq |
| |
| var a2 = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, a1[0]); |
| |
| // 3. subjectPublicKeyInfo |
| if (a2.length < 7) |
| throw "malformed X.509 certificate PEM (code:003)"; // no subjPubKeyInfo |
| |
| return a2[6]; |
| }; |
| |
| /** |
| * get array of X.509 V3 extension value information in hex string of certificate |
| * @name getV3ExtInfoListOfCertHex |
| * @memberOf X509 |
| * @function |
| * @param {String} hCert hexadecimal string of X.509 certificate binary |
| * @return {Array} array of result object by {@link X509.getV3ExtInfoListOfCertHex} |
| * @since x509 1.1.5 |
| * @description |
| * This method will get all extension information of a X.509 certificate. |
| * Items of resulting array has following properties: |
| * <ul> |
| * <li>posTLV - index of ASN.1 TLV for the extension. same as 'pos' argument.</li> |
| * <li>oid - dot noted string of extension oid (ex. 2.5.29.14)</li> |
| * <li>critical - critical flag value for this extension</li> |
| * <li>posV - index of ASN.1 TLV for the extension value. |
| * This is a position of a content of ENCAPSULATED OCTET STRING.</li> |
| * </ul> |
| * @example |
| * hCert = X509.pemToHex(certGithubPEM); |
| * a = X509.getV3ExtInfoListOfCertHex(hCert); |
| * // Then a will be an array of like following: |
| * [{posTLV: 1952, oid: "2.5.29.35", critical: false, posV: 1968}, |
| * {posTLV: 1974, oid: "2.5.29.19", critical: true, posV: 1986}, ...] |
| */ |
| X509.getV3ExtInfoListOfCertHex = function(hCert) { |
| // 1. Certificate ASN.1 |
| var a1 = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, 0); |
| if (a1.length != 3) |
| throw "malformed X.509 certificate PEM (code:001)"; // not 3 item of seq Cert |
| |
| // 2. tbsCertificate |
| if (hCert.substr(a1[0], 2) != "30") |
| throw "malformed X.509 certificate PEM (code:002)"; // tbsCert not seq |
| |
| var a2 = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, a1[0]); |
| |
| // 3. v3Extension EXPLICIT Tag [3] |
| // ver, seri, alg, iss, validity, subj, spki, (iui,) (sui,) ext |
| if (a2.length < 8) |
| throw "malformed X.509 certificate PEM (code:003)"; // tbsCert num field too short |
| |
| if (hCert.substr(a2[7], 2) != "a3") |
| throw "malformed X.509 certificate PEM (code:004)"; // not [3] tag |
| |
| var a3 = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, a2[7]); |
| if (a3.length != 1) |
| throw "malformed X.509 certificate PEM (code:005)"; // [3]tag numChild!=1 |
| |
| // 4. v3Extension SEQUENCE |
| if (hCert.substr(a3[0], 2) != "30") |
| throw "malformed X.509 certificate PEM (code:006)"; // not SEQ |
| |
| var a4 = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, a3[0]); |
| |
| // 5. v3Extension item position |
| var numExt = a4.length; |
| var aInfo = new Array(numExt); |
| for (var i = 0; i < numExt; i++) { |
| aInfo[i] = X509.getV3ExtItemInfo_AtObj(hCert, a4[i]); |
| } |
| return aInfo; |
| }; |
| |
| /** |
| * get X.509 V3 extension value information at the specified position |
| * @name getV3ExtItemInfo_AtObj |
| * @memberOf X509 |
| * @function |
| * @param {String} hCert hexadecimal string of X.509 certificate binary |
| * @param {Integer} pos index of hexadecimal string for the extension |
| * @return {Object} properties for the extension |
| * @since x509 1.1.5 |
| * @description |
| * This method will get some information of a X.509 V extension |
| * which is referred by an index of hexadecimal string of X.509 |
| * certificate. |
| * Resulting object has following properties: |
| * <ul> |
| * <li>posTLV - index of ASN.1 TLV for the extension. same as 'pos' argument.</li> |
| * <li>oid - dot noted string of extension oid (ex. 2.5.29.14)</li> |
| * <li>critical - critical flag value for this extension</li> |
| * <li>posV - index of ASN.1 TLV for the extension value. |
| * This is a position of a content of ENCAPSULATED OCTET STRING.</li> |
| * </ul> |
| * This method is used by {@link X509.getV3ExtInfoListOfCertHex} internally. |
| */ |
| X509.getV3ExtItemInfo_AtObj = function(hCert, pos) { |
| var info = {}; |
| |
| // posTLV - extension TLV |
| info.posTLV = pos; |
| |
| var a = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, pos); |
| if (a.length != 2 && a.length != 3) |
| throw "malformed X.509v3 Ext (code:001)"; // oid,(critical,)val |
| |
| // oid - extension OID |
| if (hCert.substr(a[0], 2) != "06") |
| throw "malformed X.509v3 Ext (code:002)"; // not OID "06" |
| var valueHex = ASN1HEX.getHexOfV_AtObj(hCert, a[0]); |
| info.oid = ASN1HEX.hextooidstr(valueHex); |
| |
| // critical - extension critical flag |
| info.critical = false; // critical false by default |
| if (a.length == 3) info.critical = true; |
| |
| // posV - content TLV position of encapsulated |
| // octet string of V3 extension value. |
| var posExtV = a[a.length - 1]; |
| if (hCert.substr(posExtV, 2) != "04") |
| throw "malformed X.509v3 Ext (code:003)"; // not EncapOctet "04" |
| info.posV = ASN1HEX.getStartPosOfV_AtObj(hCert, posExtV); |
| |
| return info; |
| }; |
| |
| /** |
| * get X.509 V3 extension value ASN.1 TLV for specified oid or name |
| * @name getHexOfTLV_V3ExtValue |
| * @memberOf X509 |
| * @function |
| * @param {String} hCert hexadecimal string of X.509 certificate binary |
| * @param {String} oidOrName oid or name for extension (ex. 'keyUsage' or '2.5.29.15') |
| * @return {String} hexadecimal string of extension ASN.1 TLV |
| * @since x509 1.1.6 |
| * @description |
| * This method will get X.509v3 extension value of ASN.1 TLV |
| * which is specifyed by extension name or oid. |
| * @example |
| * hExtValue = X509.getHexOfTLV_V3ExtValue(hCert, "keyUsage"); |
| * // hExtValue will be such like '030205a0'. |
| */ |
| X509.getHexOfTLV_V3ExtValue = function(hCert, oidOrName) { |
| var pos = X509.getPosOfTLV_V3ExtValue(hCert, oidOrName); |
| if (pos == -1) return ''; |
| return ASN1HEX.getHexOfTLV_AtObj(hCert, pos); |
| }; |
| |
| /** |
| * get X.509 V3 extension value ASN.1 V for specified oid or name |
| * @name getHexOfV_V3ExtValue |
| * @memberOf X509 |
| * @function |
| * @param {String} hCert hexadecimal string of X.509 certificate binary |
| * @param {String} oidOrName oid or name for extension (ex. 'keyUsage' or '2.5.29.15') |
| * @return {String} hexadecimal string of extension ASN.1 TLV |
| * @since x509 1.1.6 |
| * @description |
| * This method will get X.509v3 extension value of ASN.1 value |
| * which is specifyed by extension name or oid. |
| * If there is no such extension in the certificate, |
| * it returns empty string (i.e. ''). |
| * Available extension names and oids are defined |
| * in the {@link KJUR.asn1.x509.OID} class. |
| * @example |
| * hExtValue = X509.getHexOfV_V3ExtValue(hCert, "keyUsage"); |
| * // hExtValue will be such like '05a0'. |
| */ |
| X509.getHexOfV_V3ExtValue = function(hCert, oidOrName) { |
| var pos = X509.getPosOfTLV_V3ExtValue(hCert, oidOrName); |
| if (pos == -1) return ''; |
| return ASN1HEX.getHexOfV_AtObj(hCert, pos); |
| }; |
| |
| /** |
| * get index in the certificate hexa string for specified oid or name specified extension |
| * @name getPosOfTLV_V3ExtValue |
| * @memberOf X509 |
| * @function |
| * @param {String} hCert hexadecimal string of X.509 certificate binary |
| * @param {String} oidOrName oid or name for extension (ex. 'keyUsage' or '2.5.29.15') |
| * @return {Integer} index in the hexadecimal string of certficate for specified extension |
| * @since x509 1.1.6 |
| * @description |
| * This method will get X.509v3 extension value of ASN.1 V(value) |
| * which is specifyed by extension name or oid. |
| * If there is no such extension in the certificate, |
| * it returns empty string (i.e. ''). |
| * Available extension names and oids are defined |
| * in the {@link KJUR.asn1.x509.OID} class. |
| * @example |
| * idx = X509.getPosOfV_V3ExtValue(hCert, "keyUsage"); |
| * // The 'idx' will be index in the string for keyUsage value ASN.1 TLV. |
| */ |
| X509.getPosOfTLV_V3ExtValue = function(hCert, oidOrName) { |
| var oid = oidOrName; |
| if (! oidOrName.match(/^[0-9.]+$/)) oid = KJUR.asn1.x509.OID.name2oid(oidOrName); |
| if (oid == '') return -1; |
| |
| var infoList = X509.getV3ExtInfoListOfCertHex(hCert); |
| for (var i = 0; i < infoList.length; i++) { |
| var info = infoList[i]; |
| if (info.oid == oid) return info.posV; |
| } |
| return -1; |
| }; |
| |
| X509.KEYUSAGE_NAME = [ |
| "digitalSignature", |
| "nonRepudiation", |
| "keyEncipherment", |
| "dataEncipherment", |
| "keyAgreement", |
| "keyCertSign", |
| "cRLSign", |
| "encipherOnly", |
| "decipherOnly" |
| ]; |
| |
| /** |
| * get KeyUsage extension value as binary string in the certificate |
| * @name getExtKeyUsageBin |
| * @memberOf X509 |
| * @function |
| * @param {String} hCert hexadecimal string of X.509 certificate binary |
| * @return {String} binary string of key usage bits (ex. '101') |
| * @since x509 1.1.6 |
| * @description |
| * This method will get key usage extension value |
| * as binary string such like '101'. |
| * Key usage bits definition is in the RFC 5280. |
| * If there is no key usage extension in the certificate, |
| * it returns empty string (i.e. ''). |
| * @example |
| * bKeyUsage = X509.getExtKeyUsageBin(hCert); |
| * // bKeyUsage will be such like '101'. |
| * // 1 - digitalSignature |
| * // 0 - nonRepudiation |
| * // 1 - keyEncipherment |
| */ |
| X509.getExtKeyUsageBin = function(hCert) { |
| var hKeyUsage = X509.getHexOfV_V3ExtValue(hCert, "keyUsage"); |
| if (hKeyUsage == '') return ''; |
| if (hKeyUsage.length % 2 != 0 || hKeyUsage.length <= 2) |
| throw "malformed key usage value"; |
| var unusedBits = parseInt(hKeyUsage.substr(0, 2)); |
| var bKeyUsage = parseInt(hKeyUsage.substr(2), 16).toString(2); |
| return bKeyUsage.substr(0, bKeyUsage.length - unusedBits); |
| }; |
| |
| /** |
| * get KeyUsage extension value as names in the certificate |
| * @name getExtKeyUsageString |
| * @memberOf X509 |
| * @function |
| * @param {String} hCert hexadecimal string of X.509 certificate binary |
| * @return {String} comma separated string of key usage |
| * @since x509 1.1.6 |
| * @description |
| * This method will get key usage extension value |
| * as comma separated string of usage names. |
| * If there is no key usage extension in the certificate, |
| * it returns empty string (i.e. ''). |
| * @example |
| * sKeyUsage = X509.getExtKeyUsageString(hCert); |
| * // sKeyUsage will be such like 'digitalSignature,keyEncipherment'. |
| */ |
| X509.getExtKeyUsageString = function(hCert) { |
| var bKeyUsage = X509.getExtKeyUsageBin(hCert); |
| var a = new Array(); |
| for (var i = 0; i < bKeyUsage.length; i++) { |
| if (bKeyUsage.substr(i, 1) == "1") a.push(X509.KEYUSAGE_NAME[i]); |
| } |
| return a.join(","); |
| }; |
| |
| /** |
| * get AuthorityInfoAccess extension value in the certificate as associative array |
| * @name getExtAIAInfo |
| * @memberOf X509 |
| * @function |
| * @param {String} hCert hexadecimal string of X.509 certificate binary |
| * @return {Object} associative array of AIA extension properties |
| * @since x509 1.1.6 |
| * @description |
| * This method will get authority info access value |
| * as associate array which has following properties: |
| * <ul> |
| * <li>ocsp - array of string for OCSP responder URL</li> |
| * <li>caissuer - array of string for caIssuer value (i.e. CA certificates URL)</li> |
| * </ul> |
| * If there is no key usage extension in the certificate, |
| * it returns null; |
| * @example |
| * oAIA = X509.getExtAIAInfo(hCert); |
| * // result will be such like: |
| * // oAIA.ocsp = ["http://ocsp.foo.com"]; |
| * // oAIA.caissuer = ["http://rep.foo.com/aaa.p8m"]; |
| */ |
| X509.getExtAIAInfo = function(hCert) { |
| var result = {}; |
| result.ocsp = []; |
| result.caissuer = []; |
| var pos1 = X509.getPosOfTLV_V3ExtValue(hCert, "authorityInfoAccess"); |
| if (pos1 == -1) return null; |
| if (hCert.substr(pos1, 2) != "30") // extnValue SEQUENCE |
| throw "malformed AIA Extn Value"; |
| |
| var posAccDescList = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, pos1); |
| for (var i = 0; i < posAccDescList.length; i++) { |
| var p = posAccDescList[i]; |
| var posAccDescChild = ASN1HEX.getPosArrayOfChildren_AtObj(hCert, p); |
| if (posAccDescChild.length != 2) |
| throw "malformed AccessDescription of AIA Extn"; |
| var pOID = posAccDescChild[0]; |
| var pName = posAccDescChild[1]; |
| if (ASN1HEX.getHexOfV_AtObj(hCert, pOID) == "2b06010505073001") { |
| if (hCert.substr(pName, 2) == "86") { |
| result.ocsp.push(hextoutf8(ASN1HEX.getHexOfV_AtObj(hCert, pName))); |
| } |
| } |
| if (ASN1HEX.getHexOfV_AtObj(hCert, pOID) == "2b06010505073002") { |
| if (hCert.substr(pName, 2) == "86") { |
| result.caissuer.push(hextoutf8(ASN1HEX.getHexOfV_AtObj(hCert, pName))); |
| } |
| } |
| } |
| return result; |
| }; |
| |
| /* |
| X509.prototype.readCertPEM = _x509_readCertPEM; |
| X509.prototype.readCertPEMWithoutRSAInit = _x509_readCertPEMWithoutRSAInit; |
| X509.prototype.getSerialNumberHex = _x509_getSerialNumberHex; |
| X509.prototype.getIssuerHex = _x509_getIssuerHex; |
| X509.prototype.getSubjectHex = _x509_getSubjectHex; |
| X509.prototype.getIssuerString = _x509_getIssuerString; |
| X509.prototype.getSubjectString = _x509_getSubjectString; |
| X509.prototype.getNotBefore = _x509_getNotBefore; |
| X509.prototype.getNotAfter = _x509_getNotAfter; |
| */ |
| |
| exports.X509 = X509; |
| module.exports = exports; |
| /*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ |
| */ |
| // Copyright (c) 2005 Tom Wu |
| // All Rights Reserved. |
| // See "LICENSE" for details. |
| |
| // Basic JavaScript BN library - subset useful for RSA encryption. |
| |
| // Bits per digit |
| var dbits; |
| |
| // JavaScript engine analysis |
| var canary = 0xdeadbeefcafe; |
| var j_lm = ((canary&0xffffff)==0xefcafe); |
| |
| // (public) Constructor |
| var BigInteger = function BigInteger(a,b,c) { |
| if(a != null) |
| if("number" == typeof a) this.fromNumber(a,b,c); |
| else if(b == null && "string" != typeof a) this.fromString(a,256); |
| else this.fromString(a,b); |
| } |
| |
| // return new, unset BigInteger |
| function nbi() { return new BigInteger(null); } |
| |
| // am: Compute w_j += (x*this_i), propagate carries, |
| // c is initial carry, returns final carry. |
| // c < 3*dvalue, x < 2*dvalue, this_i < dvalue |
| // We need to select the fastest one that works in this environment. |
| |
| // am1: use a single mult and divide to get the high bits, |
| // max digit bits should be 26 because |
| // max internal value = 2*dvalue^2-2*dvalue (< 2^53) |
| function am1(i,x,w,j,c,n) { |
| while(--n >= 0) { |
| var v = x*this[i++]+w[j]+c; |
| c = Math.floor(v/0x4000000); |
| w[j++] = v&0x3ffffff; |
| } |
| return c; |
| } |
| // am2 avoids a big mult-and-extract completely. |
| // Max digit bits should be <= 30 because we do bitwise ops |
| // on values up to 2*hdvalue^2-hdvalue-1 (< 2^31) |
| function am2(i,x,w,j,c,n) { |
| var xl = x&0x7fff, xh = x>>15; |
| while(--n >= 0) { |
| var l = this[i]&0x7fff; |
| var h = this[i++]>>15; |
| var m = xh*l+h*xl; |
| l = xl*l+((m&0x7fff)<<15)+w[j]+(c&0x3fffffff); |
| c = (l>>>30)+(m>>>15)+xh*h+(c>>>30); |
| w[j++] = l&0x3fffffff; |
| } |
| return c; |
| } |
| // Alternately, set max digit bits to 28 since some |
| // browsers slow down when dealing with 32-bit numbers. |
| function am3(i,x,w,j,c,n) { |
| var xl = x&0x3fff, xh = x>>14; |
| while(--n >= 0) { |
| var l = this[i]&0x3fff; |
| var h = this[i++]>>14; |
| var m = xh*l+h*xl; |
| l = xl*l+((m&0x3fff)<<14)+w[j]+c; |
| c = (l>>28)+(m>>14)+xh*h; |
| w[j++] = l&0xfffffff; |
| } |
| return c; |
| } |
| if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) { |
| BigInteger.prototype.am = am2; |
| dbits = 30; |
| } |
| else if(j_lm && (navigator.appName != "Netscape")) { |
| BigInteger.prototype.am = am1; |
| dbits = 26; |
| } |
| else { // Mozilla/Netscape seems to prefer am3 |
| BigInteger.prototype.am = am3; |
| dbits = 28; |
| } |
| |
| BigInteger.prototype.DB = dbits; |
| BigInteger.prototype.DM = ((1<<dbits)-1); |
| BigInteger.prototype.DV = (1<<dbits); |
| |
| var BI_FP = 52; |
| BigInteger.prototype.FV = Math.pow(2,BI_FP); |
| BigInteger.prototype.F1 = BI_FP-dbits; |
| BigInteger.prototype.F2 = 2*dbits-BI_FP; |
| |
| // Digit conversions |
| var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz"; |
| var BI_RC = new Array(); |
| var rr,vv; |
| rr = "0".charCodeAt(0); |
| for(vv = 0; vv <= 9; ++vv) BI_RC[rr++] = vv; |
| rr = "a".charCodeAt(0); |
| for(vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv; |
| rr = "A".charCodeAt(0); |
| for(vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv; |
| |
| function int2char(n) { return BI_RM.charAt(n); } |
| function intAt(s,i) { |
| var c = BI_RC[s.charCodeAt(i)]; |
| return (c==null)?-1:c; |
| } |
| |
| // (protected) copy this to r |
| function bnpCopyTo(r) { |
| for(var i = this.t-1; i >= 0; --i) r[i] = this[i]; |
| r.t = this.t; |
| r.s = this.s; |
| } |
| |
| // (protected) set from integer value x, -DV <= x < DV |
| function bnpFromInt(x) { |
| this.t = 1; |
| this.s = (x<0)?-1:0; |
| if(x > 0) this[0] = x; |
| else if(x < -1) this[0] = x+this.DV; |
| else this.t = 0; |
| } |
| |
| // return bigint initialized to value |
| function nbv(i) { var r = nbi(); r.fromInt(i); return r; } |
| |
| // (protected) set from string and radix |
| function bnpFromString(s,b) { |
| var k; |
| if(b == 16) k = 4; |
| else if(b == 8) k = 3; |
| else if(b == 256) k = 8; // byte array |
| else if(b == 2) k = 1; |
| else if(b == 32) k = 5; |
| else if(b == 4) k = 2; |
| else { this.fromRadix(s,b); return; } |
| this.t = 0; |
| this.s = 0; |
| var i = s.length, mi = false, sh = 0; |
| while(--i >= 0) { |
| var x = (k==8)?s[i]&0xff:intAt(s,i); |
| if(x < 0) { |
| if(s.charAt(i) == "-") mi = true; |
| continue; |
| } |
| mi = false; |
| if(sh == 0) |
| this[this.t++] = x; |
| else if(sh+k > this.DB) { |
| this[this.t-1] |= (x&((1<<(this.DB-sh))-1))<<sh; |
| this[this.t++] = (x>>(this.DB-sh)); |
| } |
| else |
| this[this.t-1] |= x<<sh; |
| sh += k; |
| if(sh >= this.DB) sh -= this.DB; |
| } |
| if(k == 8 && (s[0]&0x80) != 0) { |
| this.s = -1; |
| if(sh > 0) this[this.t-1] |= ((1<<(this.DB-sh))-1)<<sh; |
| } |
| this.clamp(); |
| if(mi) BigInteger.ZERO.subTo(this,this); |
| } |
| |
| // (protected) clamp off excess high words |
| function bnpClamp() { |
| var c = this.s&this.DM; |
| while(this.t > 0 && this[this.t-1] == c) --this.t; |
| } |
| |
| // (public) return string representation in given radix |
| function bnToString(b) { |
| if(this.s < 0) return "-"+this.negate().toString(b); |
| var k; |
| if(b == 16) k = 4; |
| else if(b == 8) k = 3; |
| else if(b == 2) k = 1; |
| else if(b == 32) k = 5; |
| else if(b == 4) k = 2; |
| else return this.toRadix(b); |
| var km = (1<<k)-1, d, m = false, r = "", i = this.t; |
| var p = this.DB-(i*this.DB)%k; |
| if(i-- > 0) { |
| if(p < this.DB && (d = this[i]>>p) > 0) { m = true; r = int2char(d); } |
| while(i >= 0) { |
| if(p < k) { |
| d = (this[i]&((1<<p)-1))<<(k-p); |
| d |= this[--i]>>(p+=this.DB-k); |
| } |
| else { |
| d = (this[i]>>(p-=k))&km; |
| if(p <= 0) { p += this.DB; --i; } |
| } |
| if(d > 0) m = true; |
| if(m) r += int2char(d); |
| } |
| } |
| return m?r:"0"; |
| } |
| |
| // (public) -this |
| function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; } |
| |
| // (public) |this| |
| function bnAbs() { return (this.s<0)?this.negate():this; } |
| |
| // (public) return + if this > a, - if this < a, 0 if equal |
| function bnCompareTo(a) { |
| var r = this.s-a.s; |
| if(r != 0) return r; |
| var i = this.t; |
| r = i-a.t; |
| if(r != 0) return (this.s<0)?-r:r; |
| while(--i >= 0) if((r=this[i]-a[i]) != 0) return r; |
| return 0; |
| } |
| |
| // returns bit length of the integer x |
| function nbits(x) { |
| var r = 1, t; |
| if((t=x>>>16) != 0) { x = t; r += 16; } |
| if((t=x>>8) != 0) { x = t; r += 8; } |
| if((t=x>>4) != 0) { x = t; r += 4; } |
| if((t=x>>2) != 0) { x = t; r += 2; } |
| if((t=x>>1) != 0) { x = t; r += 1; } |
| return r; |
| } |
| |
| // (public) return the number of bits in "this" |
| function bnBitLength() { |
| if(this.t <= 0) return 0; |
| return this.DB*(this.t-1)+nbits(this[this.t-1]^(this.s&this.DM)); |
| } |
| |
| // (protected) r = this << n*DB |
| function bnpDLShiftTo(n,r) { |
| var i; |
| for(i = this.t-1; i >= 0; --i) r[i+n] = this[i]; |
| for(i = n-1; i >= 0; --i) r[i] = 0; |
| r.t = this.t+n; |
| r.s = this.s; |
| } |
| |
| // (protected) r = this >> n*DB |
| function bnpDRShiftTo(n,r) { |
| for(var i = n; i < this.t; ++i) r[i-n] = this[i]; |
| r.t = Math.max(this.t-n,0); |
| r.s = this.s; |
| } |
| |
| // (protected) r = this << n |
| function bnpLShiftTo(n,r) { |
| var bs = n%this.DB; |
| var cbs = this.DB-bs; |
| var bm = (1<<cbs)-1; |
| var ds = Math.floor(n/this.DB), c = (this.s<<bs)&this.DM, i; |
| for(i = this.t-1; i >= 0; --i) { |
| r[i+ds+1] = (this[i]>>cbs)|c; |
| c = (this[i]&bm)<<bs; |
| } |
| for(i = ds-1; i >= 0; --i) r[i] = 0; |
| r[ds] = c; |
| r.t = this.t+ds+1; |
| r.s = this.s; |
| r.clamp(); |
| } |
| |
| // (protected) r = this >> n |
| function bnpRShiftTo(n,r) { |
| r.s = this.s; |
| var ds = Math.floor(n/this.DB); |
| if(ds >= this.t) { r.t = 0; return; } |
| var bs = n%this.DB; |
| var cbs = this.DB-bs; |
| var bm = (1<<bs)-1; |
| r[0] = this[ds]>>bs; |
| for(var i = ds+1; i < this.t; ++i) { |
| r[i-ds-1] |= (this[i]&bm)<<cbs; |
| r[i-ds] = this[i]>>bs; |
| } |
| if(bs > 0) r[this.t-ds-1] |= (this.s&bm)<<cbs; |
| r.t = this.t-ds; |
| r.clamp(); |
| } |
| |
| // (protected) r = this - a |
| function bnpSubTo(a,r) { |
| var i = 0, c = 0, m = Math.min(a.t,this.t); |
| while(i < m) { |
| c += this[i]-a[i]; |
| r[i++] = c&this.DM; |
| c >>= this.DB; |
| } |
| if(a.t < this.t) { |
| c -= a.s; |
| while(i < this.t) { |
| c += this[i]; |
| r[i++] = c&this.DM; |
| c >>= this.DB; |
| } |
| c += this.s; |
| } |
| else { |
| c += this.s; |
| while(i < a.t) { |
| c -= a[i]; |
| r[i++] = c&this.DM; |
| c >>= this.DB; |
| } |
| c -= a.s; |
| } |
| r.s = (c<0)?-1:0; |
| if(c < -1) r[i++] = this.DV+c; |
| else if(c > 0) r[i++] = c; |
| r.t = i; |
| r.clamp(); |
| } |
| |
| // (protected) r = this * a, r != this,a (HAC 14.12) |
| // "this" should be the larger one if appropriate. |
| function bnpMultiplyTo(a,r) { |
| var x = this.abs(), y = a.abs(); |
| var i = x.t; |
| r.t = i+y.t; |
| while(--i >= 0) r[i] = 0; |
| for(i = 0; i < y.t; ++i) r[i+x.t] = x.am(0,y[i],r,i,0,x.t); |
| r.s = 0; |
| r.clamp(); |
| if(this.s != a.s) BigInteger.ZERO.subTo(r,r); |
| } |
| |
| // (protected) r = this^2, r != this (HAC 14.16) |
| function bnpSquareTo(r) { |
| var x = this.abs(); |
| var i = r.t = 2*x.t; |
| while(--i >= 0) r[i] = 0; |
| for(i = 0; i < x.t-1; ++i) { |
| var c = x.am(i,x[i],r,2*i,0,1); |
| if((r[i+x.t]+=x.am(i+1,2*x[i],r,2*i+1,c,x.t-i-1)) >= x.DV) { |
| r[i+x.t] -= x.DV; |
| r[i+x.t+1] = 1; |
| } |
| } |
| if(r.t > 0) r[r.t-1] += x.am(i,x[i],r,2*i,0,1); |
| r.s = 0; |
| r.clamp(); |
| } |
| |
| // (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) |
| // r != q, this != m. q or r may be null. |
| function bnpDivRemTo(m,q,r) { |
| var pm = m.abs(); |
| if(pm.t <= 0) return; |
| var pt = this.abs(); |
| if(pt.t < pm.t) { |
| if(q != null) q.fromInt(0); |
| if(r != null) this.copyTo(r); |
| return; |
| } |
| if(r == null) r = nbi(); |
| var y = nbi(), ts = this.s, ms = m.s; |
| var nsh = this.DB-nbits(pm[pm.t-1]); // normalize modulus |
| if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); } |
| else { pm.copyTo(y); pt.copyTo(r); } |
| var ys = y.t; |
| var y0 = y[ys-1]; |
| if(y0 == 0) return; |
| var yt = y0*(1<<this.F1)+((ys>1)?y[ys-2]>>this.F2:0); |
| var d1 = this.FV/yt, d2 = (1<<this.F1)/yt, e = 1<<this.F2; |
| var i = r.t, j = i-ys, t = (q==null)?nbi():q; |
| y.dlShiftTo(j,t); |
| if(r.compareTo(t) >= 0) { |
| r[r.t++] = 1; |
| r.subTo(t,r); |
| } |
| BigInteger.ONE.dlShiftTo(ys,t); |
| t.subTo(y,y); // "negative" y so we can replace sub with am later |
| while(y.t < ys) y[y.t++] = 0; |
| while(--j >= 0) { |
| // Estimate quotient digit |
| var qd = (r[--i]==y0)?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2); |
| if((r[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out |
| y.dlShiftTo(j,t); |
| r.subTo(t,r); |
| while(r[i] < --qd) r.subTo(t,r); |
| } |
| } |
| if(q != null) { |
| r.drShiftTo(ys,q); |
| if(ts != ms) BigInteger.ZERO.subTo(q,q); |
| } |
| r.t = ys; |
| r.clamp(); |
| if(nsh > 0) r.rShiftTo(nsh,r); // Denormalize remainder |
| if(ts < 0) BigInteger.ZERO.subTo(r,r); |
| } |
| |
| // (public) this mod a |
| function bnMod(a) { |
| var r = nbi(); |
| this.abs().divRemTo(a,null,r); |
| if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r); |
| return r; |
| } |
| |
| // Modular reduction using "classic" algorithm |
| function Classic(m) { this.m = m; } |
| function cConvert(x) { |
| if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); |
| else return x; |
| } |
| function cRevert(x) { return x; } |
| function cReduce(x) { x.divRemTo(this.m,null,x); } |
| function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } |
| function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); } |
| |
| Classic.prototype.convert = cConvert; |
| Classic.prototype.revert = cRevert; |
| Classic.prototype.reduce = cReduce; |
| Classic.prototype.mulTo = cMulTo; |
| Classic.prototype.sqrTo = cSqrTo; |
| |
| // (protected) return "-1/this % 2^DB"; useful for Mont. reduction |
| // justification: |
| // xy == 1 (mod m) |
| // xy = 1+km |
| // xy(2-xy) = (1+km)(1-km) |
| // x[y(2-xy)] = 1-k^2m^2 |
| // x[y(2-xy)] == 1 (mod m^2) |
| // if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 |
| // should reduce x and y(2-xy) by m^2 at each step to keep size bounded. |
| // JS multiply "overflows" differently from C/C++, so care is needed here. |
| function bnpInvDigit() { |
| if(this.t < 1) return 0; |
| var x = this[0]; |
| if((x&1) == 0) return 0; |
| var y = x&3; // y == 1/x mod 2^2 |
| y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4 |
| y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8 |
| y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16 |
| // last step - calculate inverse mod DV directly; |
| // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints |
| y = (y*(2-x*y%this.DV))%this.DV; // y == 1/x mod 2^dbits |
| // we really want the negative inverse, and -DV < y < DV |
| return (y>0)?this.DV-y:-y; |
| } |
| |
| // Montgomery reduction |
| function Montgomery(m) { |
| this.m = m; |
| this.mp = m.invDigit(); |
| this.mpl = this.mp&0x7fff; |
| this.mph = this.mp>>15; |
| this.um = (1<<(m.DB-15))-1; |
| this.mt2 = 2*m.t; |
| } |
| |
| // xR mod m |
| function montConvert(x) { |
| var r = nbi(); |
| x.abs().dlShiftTo(this.m.t,r); |
| r.divRemTo(this.m,null,r); |
| if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r); |
| return r; |
| } |
| |
| // x/R mod m |
| function montRevert(x) { |
| var r = nbi(); |
| x.copyTo(r); |
| this.reduce(r); |
| return r; |
| } |
| |
| // x = x/R mod m (HAC 14.32) |
| function montReduce(x) { |
| while(x.t <= this.mt2) // pad x so am has enough room later |
| x[x.t++] = 0; |
| for(var i = 0; i < this.m.t; ++i) { |
| // faster way of calculating u0 = x[i]*mp mod DV |
| var j = x[i]&0x7fff; |
| var u0 = (j*this.mpl+(((j*this.mph+(x[i]>>15)*this.mpl)&this.um)<<15))&x.DM; |
| // use am to combine the multiply-shift-add into one call |
| j = i+this.m.t; |
| x[j] += this.m.am(0,u0,x,i,0,this.m.t); |
| // propagate carry |
| while(x[j] >= x.DV) { x[j] -= x.DV; x[++j]++; } |
| } |
| x.clamp(); |
| x.drShiftTo(this.m.t,x); |
| if(x.compareTo(this.m) >= 0) x.subTo(this.m,x); |
| } |
| |
| // r = "x^2/R mod m"; x != r |
| function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); } |
| |
| // r = "xy/R mod m"; x,y != r |
| function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } |
| |
| Montgomery.prototype.convert = montConvert; |
| Montgomery.prototype.revert = montRevert; |
| Montgomery.prototype.reduce = montReduce; |
| Montgomery.prototype.mulTo = montMulTo; |
| Montgomery.prototype.sqrTo = montSqrTo; |
| |
| // (protected) true iff this is even |
| function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; } |
| |
| // (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) |
| function bnpExp(e,z) { |
| if(e > 0xffffffff || e < 1) return BigInteger.ONE; |
| var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1; |
| g.copyTo(r); |
| while(--i >= 0) { |
| z.sqrTo(r,r2); |
| if((e&(1<<i)) > 0) z.mulTo(r2,g,r); |
| else { var t = r; r = r2; r2 = t; } |
| } |
| return z.revert(r); |
| } |
| |
| // (public) this^e % m, 0 <= e < 2^32 |
| function bnModPowInt(e,m) { |
| var z; |
| if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m); |
| return this.exp(e,z); |
| } |
| |
| // protected |
| BigInteger.prototype.copyTo = bnpCopyTo; |
| BigInteger.prototype.fromInt = bnpFromInt; |
| BigInteger.prototype.fromString = bnpFromString; |
| BigInteger.prototype.clamp = bnpClamp; |
| BigInteger.prototype.dlShiftTo = bnpDLShiftTo; |
| BigInteger.prototype.drShiftTo = bnpDRShiftTo; |
| BigInteger.prototype.lShiftTo = bnpLShiftTo; |
| BigInteger.prototype.rShiftTo = bnpRShiftTo; |
| BigInteger.prototype.subTo = bnpSubTo; |
| BigInteger.prototype.multiplyTo = bnpMultiplyTo; |
| BigInteger.prototype.squareTo = bnpSquareTo; |
| BigInteger.prototype.divRemTo = bnpDivRemTo; |
| BigInteger.prototype.invDigit = bnpInvDigit; |
| BigInteger.prototype.isEven = bnpIsEven; |
| BigInteger.prototype.exp = bnpExp; |
| |
| // public |
| BigInteger.prototype.toString = bnToString; |
| BigInteger.prototype.negate = bnNegate; |
| BigInteger.prototype.abs = bnAbs; |
| BigInteger.prototype.compareTo = bnCompareTo; |
| BigInteger.prototype.bitLength = bnBitLength; |
| BigInteger.prototype.mod = bnMod; |
| BigInteger.prototype.modPowInt = bnModPowInt; |
| |
| // "constants" |
| BigInteger.ZERO = nbv(0); |
| BigInteger.ONE = nbv(1); |
| /*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/ |
| */ |
| // Copyright (c) 2005-2009 Tom Wu |
| // All Rights Reserved. |
| // See "LICENSE" for details. |
| |
| // Extended JavaScript BN functions, required for RSA private ops. |
| |
| // Version 1.1: new BigInteger("0", 10) returns "proper" zero |
| // Version 1.2: square() API, isProbablePrime fix |
| |
| // (public) |
| function bnClone() { var r = nbi(); this.copyTo(r); return r; } |
| |
| // (public) return value as integer |
| function bnIntValue() { |
| if(this.s < 0) { |
| if(this.t == 1) return this[0]-this.DV; |
| else if(this.t == 0) return -1; |
| } |
| else if(this.t == 1) return this[0]; |
| else if(this.t == 0) return 0; |
| // assumes 16 < DB < 32 |
| return ((this[1]&((1<<(32-this.DB))-1))<<this.DB)|this[0]; |
| } |
| |
| // (public) return value as byte |
| function bnByteValue() { return (this.t==0)?this.s:(this[0]<<24)>>24; } |
| |
| // (public) return value as short (assumes DB>=16) |
| function bnShortValue() { return (this.t==0)?this.s:(this[0]<<16)>>16; } |
| |
| // (protected) return x s.t. r^x < DV |
| function bnpChunkSize(r) { return Math.floor(Math.LN2*this.DB/Math.log(r)); } |
| |
| // (public) 0 if this == 0, 1 if this > 0 |
| function bnSigNum() { |
| if(this.s < 0) return -1; |
| else if(this.t <= 0 || (this.t == 1 && this[0] <= 0)) return 0; |
| else return 1; |
| } |
| |
| // (protected) convert to radix string |
| function bnpToRadix(b) { |
| if(b == null) b = 10; |
| if(this.signum() == 0 || b < 2 || b > 36) return "0"; |
| var cs = this.chunkSize(b); |
| var a = Math.pow(b,cs); |
| var d = nbv(a), y = nbi(), z = nbi(), r = ""; |
| this.divRemTo(d,y,z); |
| while(y.signum() > 0) { |
| r = (a+z.intValue()).toString(b).substr(1) + r; |
| y.divRemTo(d,y,z); |
| } |
| return z.intValue().toString(b) + r; |
| } |
| |
| // (protected) convert from radix string |
| function bnpFromRadix(s,b) { |
| this.fromInt(0); |
| if(b == null) b = 10; |
| var cs = this.chunkSize(b); |
| var d = Math.pow(b,cs), mi = false, j = 0, w = 0; |
| for(var i = 0; i < s.length; ++i) { |
| var x = intAt(s,i); |
| if(x < 0) { |
| if(s.charAt(i) == "-" && this.signum() == 0) mi = true; |
| continue; |
| } |
| w = b*w+x; |
| if(++j >= cs) { |
| this.dMultiply(d); |
| this.dAddOffset(w,0); |
| j = 0; |
| w = 0; |
| } |
| } |
| if(j > 0) { |
| this.dMultiply(Math.pow(b,j)); |
| this.dAddOffset(w,0); |
| } |
| if(mi) BigInteger.ZERO.subTo(this,this); |
| } |
| |
| // (protected) alternate constructor |
| function bnpFromNumber(a,b,c) { |
| if("number" == typeof b) { |
| // new BigInteger(int,int,RNG) |
| if(a < 2) this.fromInt(1); |
| else { |
| this.fromNumber(a,c); |
| if(!this.testBit(a-1)) // force MSB set |
| this.bitwiseTo(BigInteger.ONE.shiftLeft(a-1),op_or,this); |
| if(this.isEven()) this.dAddOffset(1,0); // force odd |
| while(!this.isProbablePrime(b)) { |
| this.dAddOffset(2,0); |
| if(this.bitLength() > a) this.subTo(BigInteger.ONE.shiftLeft(a-1),this); |
| } |
| } |
| } |
| else { |
| // new BigInteger(int,RNG) |
| var x = new Array(), t = a&7; |
| x.length = (a>>3)+1; |
| b.nextBytes(x); |
| if(t > 0) x[0] &= ((1<<t)-1); else x[0] = 0; |
| this.fromString(x,256); |
| } |
| } |
| |
| // (public) convert to bigendian byte array |
| function bnToByteArray() { |
| var i = this.t, r = new Array(); |
| r[0] = this.s; |
| var p = this.DB-(i*this.DB)%8, d, k = 0; |
| if(i-- > 0) { |
| if(p < this.DB && (d = this[i]>>p) != (this.s&this.DM)>>p) |
| r[k++] = d|(this.s<<(this.DB-p)); |
| while(i >= 0) { |
| if(p < 8) { |
| d = (this[i]&((1<<p)-1))<<(8-p); |
| d |= this[--i]>>(p+=this.DB-8); |
| } |
| else { |
| d = (this[i]>>(p-=8))&0xff; |
| if(p <= 0) { p += this.DB; --i; } |
| } |
| if((d&0x80) != 0) d |= -256; |
| if(k == 0 && (this.s&0x80) != (d&0x80)) ++k; |
| if(k > 0 || d != this.s) r[k++] = d; |
| } |
| } |
| return r; |
| } |
| |
| function bnEquals(a) { return(this.compareTo(a)==0); } |
| function bnMin(a) { return(this.compareTo(a)<0)?this:a; } |
| function bnMax(a) { return(this.compareTo(a)>0)?this:a; } |
| |
| // (protected) r = this op a (bitwise) |
| function bnpBitwiseTo(a,op,r) { |
| var i, f, m = Math.min(a.t,this.t); |
| for(i = 0; i < m; ++i) r[i] = op(this[i],a[i]); |
| if(a.t < this.t) { |
| f = a.s&this.DM; |
| for(i = m; i < this.t; ++i) r[i] = op(this[i],f); |
| r.t = this.t; |
| } |
| else { |
| f = this.s&this.DM; |
| for(i = m; i < a.t; ++i) r[i] = op(f,a[i]); |
| r.t = a.t; |
| } |
| r.s = op(this.s,a.s); |
| r.clamp(); |
| } |
| |
| // (public) this & a |
| function op_and(x,y) { return x&y; } |
| function bnAnd(a) { var r = nbi(); this.bitwiseTo(a,op_and,r); return r; } |
| |
| // (public) this | a |
| function op_or(x,y) { return x|y; } |
| function bnOr(a) { var r = nbi(); this.bitwiseTo(a,op_or,r); return r; } |
| |
| // (public) this ^ a |
| function op_xor(x,y) { return x^y; } |
| function bnXor(a) { var r = nbi(); this.bitwiseTo(a,op_xor,r); return r; } |
| |
| // (public) this & ~a |
| function op_andnot(x,y) { return x&~y; } |
| function bnAndNot(a) { var r = nbi(); this.bitwiseTo(a,op_andnot,r); return r; } |
| |
| // (public) ~this |
| function bnNot() { |
| var r = nbi(); |
| for(var i = 0; i < this.t; ++i) r[i] = this.DM&~this[i]; |
| r.t = this.t; |
| r.s = ~this.s; |
| return r; |
| } |
| |
| // (public) this << n |
| function bnShiftLeft(n) { |
| var r = nbi(); |
| if(n < 0) this.rShiftTo(-n,r); else this.lShiftTo(n,r); |
| return r; |
| } |
| |
| // (public) this >> n |
| function bnShiftRight(n) { |
| var r = nbi(); |
| if(n < 0) this.lShiftTo(-n,r); else this.rShiftTo(n,r); |
| return r; |
| } |
| |
| // return index of lowest 1-bit in x, x < 2^31 |
| function lbit(x) { |
| if(x == 0) return -1; |
| var r = 0; |
| if((x&0xffff) == 0) { x >>= 16; r += 16; } |
| if((x&0xff) == 0) { x >>= 8; r += 8; } |
| if((x&0xf) == 0) { x >>= 4; r += 4; } |
| if((x&3) == 0) { x >>= 2; r += 2; } |
| if((x&1) == 0) ++r; |
| return r; |
| } |
| |
| // (public) returns index of lowest 1-bit (or -1 if none) |
| function bnGetLowestSetBit() { |
| for(var i = 0; i < this.t; ++i) |
| if(this[i] != 0) return i*this.DB+lbit(this[i]); |
| if(this.s < 0) return this.t*this.DB; |
| return -1; |
| } |
| |
| // return number of 1 bits in x |
| function cbit(x) { |
| var r = 0; |
| while(x != 0) { x &= x-1; ++r; } |
| return r; |
| } |
| |
| // (public) return number of set bits |
| function bnBitCount() { |
| var r = 0, x = this.s&this.DM; |
| for(var i = 0; i < this.t; ++i) r += cbit(this[i]^x); |
| return r; |
| } |
| |
| // (public) true iff nth bit is set |
| function bnTestBit(n) { |
| var j = Math.floor(n/this.DB); |
| if(j >= this.t) return(this.s!=0); |
| return((this[j]&(1<<(n%this.DB)))!=0); |
| } |
| |
| // (protected) this op (1<<n) |
| function bnpChangeBit(n,op) { |
| var r = BigInteger.ONE.shiftLeft(n); |
| this.bitwiseTo(r,op,r); |
| return r; |
| } |
| |
| // (public) this | (1<<n) |
| function bnSetBit(n) { return this.changeBit(n,op_or); } |
| |
| // (public) this & ~(1<<n) |
| function bnClearBit(n) { return this.changeBit(n,op_andnot); } |
| |
| // (public) this ^ (1<<n) |
| function bnFlipBit(n) { return this.changeBit(n,op_xor); } |
| |
| // (protected) r = this + a |
| function bnpAddTo(a,r) { |
| var i = 0, c = 0, m = Math.min(a.t,this.t); |
| while(i < m) { |
| c += this[i]+a[i]; |
| r[i++] = c&this.DM; |
| c >>= this.DB; |
| } |
| if(a.t < this.t) { |
| c += a.s; |
| while(i < this.t) { |
| c += this[i]; |
| r[i++] = c&this.DM; |
| c >>= this.DB; |
| } |
| c += this.s; |
| } |
| else { |
| c += this.s; |
| while(i < a.t) { |
| c += a[i]; |
| r[i++] = c&this.DM; |
| c >>= this.DB; |
| } |
| c += a.s; |
| } |
| r.s = (c<0)?-1:0; |
| if(c > 0) r[i++] = c; |
| else if(c < -1) r[i++] = this.DV+c; |
| r.t = i; |
| r.clamp(); |
| } |
| |
| // (public) this + a |
| function bnAdd(a) { var r = nbi(); this.addTo(a,r); return r; } |
| |
| // (public) this - a |
| function bnSubtract(a) { var r = nbi(); this.subTo(a,r); return r; } |
| |
| // (public) this * a |
| function bnMultiply(a) { var r = nbi(); this.multiplyTo(a,r); return r; } |
| |
| // (public) this^2 |
| function bnSquare() { var r = nbi(); this.squareTo(r); return r; } |
| |
| // (public) this / a |
| function bnDivide(a) { var r = nbi(); this.divRemTo(a,r,null); return r; } |
| |
| // (public) this % a |
| function bnRemainder(a) { var r = nbi(); this.divRemTo(a,null,r); return r; } |
| |
| // (public) [this/a,this%a] |
| function bnDivideAndRemainder(a) { |
| var q = nbi(), r = nbi(); |
| this.divRemTo(a,q,r); |
| return new Array(q,r); |
| } |
| |
| // (protected) this *= n, this >= 0, 1 < n < DV |
| function bnpDMultiply(n) { |
| this[this.t] = this.am(0,n-1,this,0,0,this.t); |
| ++this.t; |
| this.clamp(); |
| } |
| |
| // (protected) this += n << w words, this >= 0 |
| function bnpDAddOffset(n,w) { |
| if(n == 0) return; |
| while(this.t <= w) this[this.t++] = 0; |
| this[w] += n; |
| while(this[w] >= this.DV) { |
| this[w] -= this.DV; |
| if(++w >= this.t) this[this.t++] = 0; |
| ++this[w]; |
| } |
| } |
| |
| // A "null" reducer |
| function NullExp() {} |
| function nNop(x) { return x; } |
| function nMulTo(x,y,r) { x.multiplyTo(y,r); } |
| function nSqrTo(x,r) { x.squareTo(r); } |
| |
| NullExp.prototype.convert = nNop; |
| NullExp.prototype.revert = nNop; |
| NullExp.prototype.mulTo = nMulTo; |
| NullExp.prototype.sqrTo = nSqrTo; |
| |
| // (public) this^e |
| function bnPow(e) { return this.exp(e,new NullExp()); } |
| |
| // (protected) r = lower n words of "this * a", a.t <= n |
| // "this" should be the larger one if appropriate. |
| function bnpMultiplyLowerTo(a,n,r) { |
| var i = Math.min(this.t+a.t,n); |
| r.s = 0; // assumes a,this >= 0 |
| r.t = i; |
| while(i > 0) r[--i] = 0; |
| var j; |
| for(j = r.t-this.t; i < j; ++i) r[i+this.t] = this.am(0,a[i],r,i,0,this.t); |
| for(j = Math.min(a.t,n); i < j; ++i) this.am(0,a[i],r,i,0,n-i); |
| r.clamp(); |
| } |
| |
| // (protected) r = "this * a" without lower n words, n > 0 |
| // "this" should be the larger one if appropriate. |
| function bnpMultiplyUpperTo(a,n,r) { |
| --n; |
| var i = r.t = this.t+a.t-n; |
| r.s = 0; // assumes a,this >= 0 |
| while(--i >= 0) r[i] = 0; |
| for(i = Math.max(n-this.t,0); i < a.t; ++i) |
| r[this.t+i-n] = this.am(n-i,a[i],r,0,0,this.t+i-n); |
| r.clamp(); |
| r.drShiftTo(1,r); |
| } |
| |
| // Barrett modular reduction |
| function Barrett(m) { |
| // setup Barrett |
| this.r2 = nbi(); |
| this.q3 = nbi(); |
| BigInteger.ONE.dlShiftTo(2*m.t,this.r2); |
| this.mu = this.r2.divide(m); |
| this.m = m; |
| } |
| |
| function barrettConvert(x) { |
| if(x.s < 0 || x.t > 2*this.m.t) return x.mod(this.m); |
| else if(x.compareTo(this.m) < 0) return x; |
| else { var r = nbi(); x.copyTo(r); this.reduce(r); return r; } |
| } |
| |
| function barrettRevert(x) { return x; } |
| |
| // x = x mod m (HAC 14.42) |
| function barrettReduce(x) { |
| x.drShiftTo(this.m.t-1,this.r2); |
| if(x.t > this.m.t+1) { x.t = this.m.t+1; x.clamp(); } |
| this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3); |
| this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2); |
| while(x.compareTo(this.r2) < 0) x.dAddOffset(1,this.m.t+1); |
| x.subTo(this.r2,x); |
| while(x.compareTo(this.m) >= 0) x.subTo(this.m,x); |
| } |
| |
| // r = x^2 mod m; x != r |
| function barrettSqrTo(x,r) { x.squareTo(r); this.reduce(r); } |
| |
| // r = x*y mod m; x,y != r |
| function barrettMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } |
| |
| Barrett.prototype.convert = barrettConvert; |
| Barrett.prototype.revert = barrettRevert; |
| Barrett.prototype.reduce = barrettReduce; |
| Barrett.prototype.mulTo = barrettMulTo; |
| Barrett.prototype.sqrTo = barrettSqrTo; |
| |
| // (public) this^e % m (HAC 14.85) |
| function bnModPow(e,m) { |
| var i = e.bitLength(), k, r = nbv(1), z; |
| if(i <= 0) return r; |
| else if(i < 18) k = 1; |
| else if(i < 48) k = 3; |
| else if(i < 144) k = 4; |
| else if(i < 768) k = 5; |
| else k = 6; |
| if(i < 8) |
| z = new Classic(m); |
| else if(m.isEven()) |
| z = new Barrett(m); |
| else |
| z = new Montgomery(m); |
| |
| // precomputation |
| var g = new Array(), n = 3, k1 = k-1, km = (1<<k)-1; |
| g[1] = z.convert(this); |
| if(k > 1) { |
| var g2 = nbi(); |
| z.sqrTo(g[1],g2); |
| while(n <= km) { |
| g[n] = nbi(); |
| z.mulTo(g2,g[n-2],g[n]); |
| n += 2; |
| } |
| } |
| |
| var j = e.t-1, w, is1 = true, r2 = nbi(), t; |
| i = nbits(e[j])-1; |
| while(j >= 0) { |
| if(i >= k1) w = (e[j]>>(i-k1))&km; |
| else { |
| w = (e[j]&((1<<(i+1))-1))<<(k1-i); |
| if(j > 0) w |= e[j-1]>>(this.DB+i-k1); |
| } |
| |
| n = k; |
| while((w&1) == 0) { w >>= 1; --n; } |
| if((i -= n) < 0) { i += this.DB; --j; } |
| if(is1) { // ret == 1, don't bother squaring or multiplying it |
| g[w].copyTo(r); |
| is1 = false; |
| } |
| else { |
| while(n > 1) { z.sqrTo(r,r2); z.sqrTo(r2,r); n -= 2; } |
| if(n > 0) z.sqrTo(r,r2); else { t = r; r = r2; r2 = t; } |
| z.mulTo(r2,g[w],r); |
| } |
| |
| while(j >= 0 && (e[j]&(1<<i)) == 0) { |
| z.sqrTo(r,r2); t = r; r = r2; r2 = t; |
| if(--i < 0) { i = this.DB-1; --j; } |
| } |
| } |
| return z.revert(r); |
| } |
| |
| // (public) gcd(this,a) (HAC 14.54) |
| function bnGCD(a) { |
| var x = (this.s<0)?this.negate():this.clone(); |
| var y = (a.s<0)?a.negate():a.clone(); |
| if(x.compareTo(y) < 0) { var t = x; x = y; y = t; } |
| var i = x.getLowestSetBit(), g = y.getLowestSetBit(); |
| if(g < 0) return x; |
| if(i < g) g = i; |
| if(g > 0) { |
| x.rShiftTo(g,x); |
| y.rShiftTo(g,y); |
| } |
| while(x.signum() > 0) { |
| if((i = x.getLowestSetBit()) > 0) x.rShiftTo(i,x); |
| if((i = y.getLowestSetBit()) > 0) y.rShiftTo(i,y); |
| if(x.compareTo(y) >= 0) { |
| x.subTo(y,x); |
| x.rShiftTo(1,x); |
| } |
| else { |
| y.subTo(x,y); |
| y.rShiftTo(1,y); |
| } |
| } |
| if(g > 0) y.lShiftTo(g,y); |
| return y; |
| } |
| |
| // (protected) this % n, n < 2^26 |
| function bnpModInt(n) { |
| if(n <= 0) return 0; |
| var d = this.DV%n, r = (this.s<0)?n-1:0; |
| if(this.t > 0) |
| if(d == 0) r = this[0]%n; |
| else for(var i = this.t-1; i >= 0; --i) r = (d*r+this[i])%n; |
| return r; |
| } |
| |
| // (public) 1/this % m (HAC 14.61) |
| function bnModInverse(m) { |
| var ac = m.isEven(); |
| if((this.isEven() && ac) || m.signum() == 0) return BigInteger.ZERO; |
| var u = m.clone(), v = this.clone(); |
| var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1); |
| while(u.signum() != 0) { |
| while(u.isEven()) { |
| u.rShiftTo(1,u); |
| if(ac) { |
| if(!a.isEven() || !b.isEven()) { a.addTo(this,a); b.subTo(m,b); } |
| a.rShiftTo(1,a); |
| } |
| else if(!b.isEven()) b.subTo(m,b); |
| b.rShiftTo(1,b); |
| } |
| while(v.isEven()) { |
| v.rShiftTo(1,v); |
| if(ac) { |
| if(!c.isEven() || !d.isEven()) { c.addTo(this,c); d.subTo(m,d); } |
| c.rShiftTo(1,c); |
| } |
| else if(!d.isEven()) d.subTo(m,d); |
| d.rShiftTo(1,d); |
| } |
| if(u.compareTo(v) >= 0) { |
| u.subTo(v,u); |
| if(ac) a.subTo(c,a); |
| b.subTo(d,b); |
| } |
| else { |
| v.subTo(u,v); |
| if(ac) c.subTo(a,c); |
| d.subTo(b,d); |
| } |
| } |
| if(v.compareTo(BigInteger.ONE) != 0) return BigInteger.ZERO; |
| if(d.compareTo(m) >= 0) return d.subtract(m); |
| if(d.signum() < 0) d.addTo(m,d); else return d; |
| if(d.signum() < 0) return d.add(m); else return d; |
| } |
| |
| var lowprimes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997]; |
| var lplim = (1<<26)/lowprimes[lowprimes.length-1]; |
| |
| // (public) test primality with certainty >= 1-.5^t |
| function bnIsProbablePrime(t) { |
| var i, x = this.abs(); |
| if(x.t == 1 && x[0] <= lowprimes[lowprimes.length-1]) { |
| for(i = 0; i < lowprimes.length; ++i) |
| if(x[0] == lowprimes[i]) return true; |
| return false; |
| } |
| if(x.isEven()) return false; |
| i = 1; |
| while(i < lowprimes.length) { |
| var m = lowprimes[i], j = i+1; |
| while(j < lowprimes.length && m < lplim) m *= lowprimes[j++]; |
| m = x.modInt(m); |
| while(i < j) if(m%lowprimes[i++] == 0) return false; |
| } |
| return x.millerRabin(t); |
| } |
| |
| // (protected) true if probably prime (HAC 4.24, Miller-Rabin) |
| function bnpMillerRabin(t) { |
| var n1 = this.subtract(BigInteger.ONE); |
| var k = n1.getLowestSetBit(); |
| if(k <= 0) return false; |
| var r = n1.shiftRight(k); |
| t = (t+1)>>1; |
| if(t > lowprimes.length) t = lowprimes.length; |
| var a = nbi(); |
| for(var i = 0; i < t; ++i) { |
| //Pick bases at random, instead of starting at 2 |
| a.fromInt(lowprimes[Math.floor(Math.random()*lowprimes.length)]); |
| var y = a.modPow(r,this); |
| if(y.compareTo(BigInteger.ONE) != 0 && y.compareTo(n1) != 0) { |
| var j = 1; |
| while(j++ < k && y.compareTo(n1) != 0) { |
| y = y.modPowInt(2,this); |
| if(y.compareTo(BigInteger.ONE) == 0) return false; |
| } |
| if(y.compareTo(n1) != 0) return false; |
| } |
| } |
| return true; |
| } |
| |
| // protected |
| BigInteger.prototype.chunkSize = bnpChunkSize; |
| BigInteger.prototype.toRadix = bnpToRadix; |
| BigInteger.prototype.fromRadix = bnpFromRadix; |
| BigInteger.prototype.fromNumber = bnpFromNumber; |
| BigInteger.prototype.bitwiseTo = bnpBitwiseTo; |
| BigInteger.prototype.changeBit = bnpChangeBit; |
| BigInteger.prototype.addTo = bnpAddTo; |
| BigInteger.prototype.dMultiply = bnpDMultiply; |
| BigInteger.prototype.dAddOffset = bnpDAddOffset; |
| BigInteger.prototype.multiplyLowerTo = bnpMultiplyLowerTo; |
| BigInteger.prototype.multiplyUpperTo = bnpMultiplyUpperTo; |
| BigInteger.prototype.modInt = bnpModInt; |
| BigInteger.prototype.millerRabin = bnpMillerRabin; |
| |
| // public |
| BigInteger.prototype.clone = bnClone; |
| BigInteger.prototype.intValue = bnIntValue; |
| BigInteger.prototype.byteValue = bnByteValue; |
| BigInteger.prototype.shortValue = bnShortValue; |
| BigInteger.prototype.signum = bnSigNum; |
| BigInteger.prototype.toByteArray = bnToByteArray; |
| BigInteger.prototype.equals = bnEquals; |
| BigInteger.prototype.min = bnMin; |
| BigInteger.prototype.max = bnMax; |
| BigInteger.prototype.and = bnAnd; |
| BigInteger.prototype.or = bnOr; |
| BigInteger.prototype.xor = bnXor; |
| BigInteger.prototype.andNot = bnAndNot; |
| BigInteger.prototype.not = bnNot; |
| BigInteger.prototype.shiftLeft = bnShiftLeft; |
| BigInteger.prototype.shiftRight = bnShiftRight; |
| BigInteger.prototype.getLowestSetBit = bnGetLowestSetBit; |
| BigInteger.prototype.bitCount = bnBitCount; |
| BigInteger.prototype.testBit = bnTestBit; |
| BigInteger.prototype.setBit = bnSetBit; |
| BigInteger.prototype.clearBit = bnClearBit; |
| BigInteger.prototype.flipBit = bnFlipBit; |
| BigInteger.prototype.add = bnAdd; |
| BigInteger.prototype.subtract = bnSubtract; |
| BigInteger.prototype.multiply = bnMultiply; |
| BigInteger.prototype.divide = bnDivide; |
| BigInteger.prototype.remainder = bnRemainder; |
| BigInteger.prototype.divideAndRemainder = bnDivideAndRemainder; |
| BigInteger.prototype.modPow = bnModPow; |
| BigInteger.prototype.modInverse = bnModInverse; |
| BigInteger.prototype.pow = bnPow; |
| BigInteger.prototype.gcd = bnGCD; |
| BigInteger.prototype.isProbablePrime = bnIsProbablePrime; |
| |
| // JSBN-specific extension |
| BigInteger.prototype.square = bnSquare; |
| |
| // BigInteger interfaces not implemented in jsbn: |
| |
| // BigInteger(int signum, byte[] magnitude) |
| // double doubleValue() |
| // float floatValue() |
| // int hashCode() |
| // long longValue() |
| // static BigInteger valueOf(long val) |
| |
| exports.BigInteger = BigInteger; |
| module.exports = exports; |
| /** |
| * Copyright (C) 2013-2016 Regents of the University of California. |
| * @author: Wentao Shang |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var ASN1HEX = require('../contrib/securityLib/asn1hex-1.1.js').ASN1HEX /** @ignore */ |
| var KJUR = require('../contrib/securityLib/crypto-1.0.js').KJUR /** @ignore */ |
| var RSAKey = require('../contrib/securityLib/rsasign-1.2.js').RSAKey /** @ignore */ |
| var b64tohex = require('../contrib/securityLib/base64.js').b64tohex |
| |
| // Library namespace |
| /** @ignore */ |
| var ndn = ndn || {}; |
| |
| /** @ignore */ |
| var exports = ndn; |
| |
| // Factory method to create hasher objects |
| exports.createHash = function(alg) |
| { |
| if (alg != 'sha256') |
| throw new Error('createHash: unsupported algorithm.'); |
| |
| var obj = {}; |
| |
| obj.md = new KJUR.crypto.MessageDigest({alg: "sha256", prov: "cryptojs"}); |
| |
| obj.update = function(buf) { |
| this.md.updateHex(buf.toString('hex')); |
| }; |
| |
| obj.digest = function(encoding) { |
| var hexDigest = this.md.digest(); |
| if (encoding == 'hex') |
| return hexDigest; |
| else if (encoding == 'base64') |
| return new Buffer(hexDigest, 'hex').toString('base64'); |
| else |
| return new Buffer(hexDigest, 'hex'); |
| }; |
| |
| return obj; |
| }; |
| |
| // Factory method to create HMAC objects. |
| exports.createHmac = function(algorithm, key) |
| { |
| if (algorithm !== 'sha256') |
| throw new Error('createHmac: unsupported algorithm.'); |
| |
| var obj = {}; |
| |
| obj.md = new KJUR.crypto.Mac({alg: "HmacSHA256", pass: {hex: key.toString('hex')}}); |
| |
| obj.update = function(buf) { |
| this.md.updateHex(buf.toString('hex')); |
| }; |
| |
| obj.digest = function(encoding) { |
| var hexDigest = this.md.doFinal(); |
| if (encoding == 'hex') |
| return hexDigest; |
| else if (encoding == 'base64') |
| return new Buffer(hexDigest, 'hex').toString('base64'); |
| else |
| return new Buffer(hexDigest, 'hex'); |
| }; |
| |
| return obj; |
| }; |
| |
| // Factory method to create RSA signer objects |
| exports.createSign = function(alg) |
| { |
| if (alg != 'RSA-SHA256') |
| throw new Error('createSign: unsupported algorithm.'); |
| |
| var obj = {}; |
| |
| obj.arr = []; |
| |
| obj.update = function(buf) { |
| this.arr.push(buf); |
| }; |
| |
| obj.sign = function(keypem) { |
| var rsa = new RSAKey(); |
| rsa.readPrivateKeyFromPEMString(keypem); |
| var signer = new KJUR.crypto.Signature({alg: "SHA256withRSA", prov: "cryptojs/jsrsa"}); |
| signer.initSign(rsa); |
| for (var i = 0; i < this.arr.length; ++i) |
| signer.updateHex(this.arr[i].toString('hex')); |
| |
| return new Buffer(signer.sign(), 'hex'); |
| }; |
| |
| return obj; |
| }; |
| |
| // Factory method to create RSA verifier objects |
| exports.createVerify = function(alg) |
| { |
| if (alg != 'RSA-SHA256') |
| throw new Error('createSign: unsupported algorithm.'); |
| |
| var obj = {}; |
| |
| obj.arr = []; |
| |
| obj.update = function(buf) { |
| this.arr.push(buf); |
| }; |
| |
| var getSubjectPublicKeyPosFromHex = function(hPub) { |
| var a = ASN1HEX.getPosArrayOfChildren_AtObj(hPub, 0); |
| if (a.length != 2) |
| return -1; |
| var pBitString = a[1]; |
| if (hPub.substring(pBitString, pBitString + 2) != '03') |
| return -1; |
| var pBitStringV = ASN1HEX.getStartPosOfV_AtObj(hPub, pBitString); |
| if (hPub.substring(pBitStringV, pBitStringV + 2) != '00') |
| return -1; |
| return pBitStringV + 2; |
| }; |
| |
| var publicKeyPemToDer = function(publicKeyPem) { |
| // Remove the '-----XXX-----' from the beginning and the end of the public |
| // key and also remove any \n in the public key string. |
| var lines = publicKeyPem.split('\n'); |
| var pub = ""; |
| for (var i = 1; i < lines.length - 1; i++) |
| pub += lines[i]; |
| return new Buffer(pub, 'base64'); |
| } |
| |
| var readPublicDER = function(pub_der) { |
| var hex = pub_der.toString('hex'); |
| var p = getSubjectPublicKeyPosFromHex(hex); |
| var a = ASN1HEX.getPosArrayOfChildren_AtObj(hex, p); |
| if (a.length != 2) |
| return null; |
| var hN = ASN1HEX.getHexOfV_AtObj(hex, a[0]); |
| var hE = ASN1HEX.getHexOfV_AtObj(hex, a[1]); |
| var rsaKey = new RSAKey(); |
| rsaKey.setPublic(hN, hE); |
| return rsaKey; |
| }; |
| |
| obj.verify = function(keypem, sig) { |
| var rsa = readPublicDER(publicKeyPemToDer(keypem)); |
| var signer = new KJUR.crypto.Signature({alg: "SHA256withRSA", prov: "cryptojs/jsrsa"}); |
| signer.initVerifyByPublicKey(rsa); |
| for (var i = 0; i < this.arr.length; i++) |
| signer.updateHex(this.arr[i].toString('hex')); |
| var hSig = sig.toString('hex'); |
| return signer.verify(hSig); |
| }; |
| |
| return obj; |
| }; |
| |
| exports.randomBytes = function(size) |
| { |
| // TODO: Use a cryptographic random number generator. |
| var result = new Buffer(size); |
| for (var i = 0; i < size; ++i) |
| result[i] = Math.floor(Math.random() * 256); |
| return result; |
| }; |
| |
| // contrib/feross/buffer.js needs base64.toByteArray. Define it here so that |
| // we don't have to include the entire base64 module. |
| exports.toByteArray = function(str) { |
| var hex = b64tohex(str); |
| var result = []; |
| hex.replace(/(..)/g, function(ss) { |
| result.push(parseInt(ss, 16)); |
| }); |
| return result; |
| }; |
| |
| module.exports = exports |
| // After this we include contrib/feross/buffer.js to define the Buffer class. |
| var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; |
| var base64js = {}; |
| |
| ;(function (exports) { |
| 'use strict'; |
| |
| var Arr = (typeof Uint8Array !== 'undefined') |
| ? Uint8Array |
| : Array |
| |
| var PLUS = '+'.charCodeAt(0) |
| var SLASH = '/'.charCodeAt(0) |
| var NUMBER = '0'.charCodeAt(0) |
| var LOWER = 'a'.charCodeAt(0) |
| var UPPER = 'A'.charCodeAt(0) |
| |
| function decode (elt) { |
| var code = elt.charCodeAt(0) |
| if (code === PLUS) |
| return 62 // '+' |
| if (code === SLASH) |
| return 63 // '/' |
| if (code < NUMBER) |
| return -1 //no match |
| if (code < NUMBER + 10) |
| return code - NUMBER + 26 + 26 |
| if (code < UPPER + 26) |
| return code - UPPER |
| if (code < LOWER + 26) |
| return code - LOWER + 26 |
| } |
| |
| function b64ToByteArray (b64) { |
| var i, j, l, tmp, placeHolders, arr |
| |
| if (b64.length % 4 > 0) { |
| throw new Error('Invalid string. Length must be a multiple of 4') |
| } |
| |
| // the number of equal signs (place holders) |
| // if there are two placeholders, than the two characters before it |
| // represent one byte |
| // if there is only one, then the three characters before it represent 2 bytes |
| // this is just a cheap hack to not do indexOf twice |
| var len = b64.length |
| placeHolders = '=' === b64.charAt(len - 2) ? 2 : '=' === b64.charAt(len - 1) ? 1 : 0 |
| |
| // base64 is 4/3 + up to two characters of the original data |
| arr = new Arr(b64.length * 3 / 4 - placeHolders) |
| |
| // if there are placeholders, only get up to the last complete 4 chars |
| l = placeHolders > 0 ? b64.length - 4 : b64.length |
| |
| var L = 0 |
| |
| function push (v) { |
| arr[L++] = v |
| } |
| |
| for (i = 0, j = 0; i < l; i += 4, j += 3) { |
| tmp = (decode(b64.charAt(i)) << 18) | (decode(b64.charAt(i + 1)) << 12) | (decode(b64.charAt(i + 2)) << 6) | decode(b64.charAt(i + 3)) |
| push((tmp & 0xFF0000) >> 16) |
| push((tmp & 0xFF00) >> 8) |
| push(tmp & 0xFF) |
| } |
| |
| if (placeHolders === 2) { |
| tmp = (decode(b64.charAt(i)) << 2) | (decode(b64.charAt(i + 1)) >> 4) |
| push(tmp & 0xFF) |
| } else if (placeHolders === 1) { |
| tmp = (decode(b64.charAt(i)) << 10) | (decode(b64.charAt(i + 1)) << 4) | (decode(b64.charAt(i + 2)) >> 2) |
| push((tmp >> 8) & 0xFF) |
| push(tmp & 0xFF) |
| } |
| |
| return arr |
| } |
| |
| function uint8ToBase64 (uint8) { |
| var i, |
| extraBytes = uint8.length % 3, // if we have 1 byte left, pad 2 bytes |
| output = "", |
| temp, length |
| |
| function encode (num) { |
| return lookup.charAt(num) |
| } |
| |
| function tripletToBase64 (num) { |
| return encode(num >> 18 & 0x3F) + encode(num >> 12 & 0x3F) + encode(num >> 6 & 0x3F) + encode(num & 0x3F) |
| } |
| |
| // go through the array every three bytes, we'll deal with trailing stuff later |
| for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) { |
| temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]) |
| output += tripletToBase64(temp) |
| } |
| |
| // pad the end with zeros, but make sure to not forget the extra bytes |
| switch (extraBytes) { |
| case 1: |
| temp = uint8[uint8.length - 1] |
| output += encode(temp >> 2) |
| output += encode((temp << 4) & 0x3F) |
| output += '==' |
| break |
| case 2: |
| temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1]) |
| output += encode(temp >> 10) |
| output += encode((temp >> 4) & 0x3F) |
| output += encode((temp << 2) & 0x3F) |
| output += '=' |
| break |
| } |
| |
| return output |
| } |
| |
| exports.toByteArray = b64ToByteArray |
| exports.fromByteArray = uint8ToBase64 |
| })(base64js); |
| var ieee = {}; |
| |
| ieee.read = function(buffer, offset, isLE, mLen, nBytes) { |
| var e, m, |
| eLen = nBytes * 8 - mLen - 1, |
| eMax = (1 << eLen) - 1, |
| eBias = eMax >> 1, |
| nBits = -7, |
| i = isLE ? (nBytes - 1) : 0, |
| d = isLE ? -1 : 1, |
| s = buffer[offset + i]; |
| |
| i += d; |
| |
| e = s & ((1 << (-nBits)) - 1); |
| s >>= (-nBits); |
| nBits += eLen; |
| for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8); |
| |
| m = e & ((1 << (-nBits)) - 1); |
| e >>= (-nBits); |
| nBits += mLen; |
| for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8); |
| |
| if (e === 0) { |
| e = 1 - eBias; |
| } else if (e === eMax) { |
| return m ? NaN : ((s ? -1 : 1) * Infinity); |
| } else { |
| m = m + Math.pow(2, mLen); |
| e = e - eBias; |
| } |
| return (s ? -1 : 1) * m * Math.pow(2, e - mLen); |
| }; |
| |
| ieee.write = function(buffer, value, offset, isLE, mLen, nBytes) { |
| var e, m, c, |
| eLen = nBytes * 8 - mLen - 1, |
| eMax = (1 << eLen) - 1, |
| eBias = eMax >> 1, |
| rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0), |
| i = isLE ? 0 : (nBytes - 1), |
| d = isLE ? 1 : -1, |
| s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0; |
| |
| value = Math.abs(value); |
| |
| if (isNaN(value) || value === Infinity) { |
| m = isNaN(value) ? 1 : 0; |
| e = eMax; |
| } else { |
| e = Math.floor(Math.log(value) / Math.LN2); |
| if (value * (c = Math.pow(2, -e)) < 1) { |
| e--; |
| c *= 2; |
| } |
| if (e + eBias >= 1) { |
| value += rt / c; |
| } else { |
| value += rt * Math.pow(2, 1 - eBias); |
| } |
| if (value * c >= 2) { |
| e++; |
| c /= 2; |
| } |
| |
| if (e + eBias >= eMax) { |
| m = 0; |
| e = eMax; |
| } else if (e + eBias >= 1) { |
| m = (value * c - 1) * Math.pow(2, mLen); |
| e = e + eBias; |
| } else { |
| m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen); |
| e = 0; |
| } |
| } |
| |
| for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8); |
| |
| e = (e << mLen) | m; |
| eLen += mLen; |
| for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8); |
| |
| buffer[offset + i - d] |= s * 128; |
| }; |
| |
| exports.ieee = ieee; |
| /** |
| * The buffer module from node.js, for the browser. |
| * @author Feross Aboukhadijeh <feross@feross.org> <http://feross.org> |
| * |
| * Copyright (C) 2013 Feross Aboukhadijeh, and other contributors. |
| * |
| * 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. |
| * |
| * @license MIT |
| */ |
| |
| var base64 = base64js; |
| var ieee754 = require('./ieee754.js').ieee |
| |
| exports.Buffer = Buffer |
| exports.SlowBuffer = Buffer |
| exports.INSPECT_MAX_BYTES = 50 |
| Buffer.poolSize = 8192 |
| |
| /** |
| * If `Buffer._useTypedArrays`: |
| * === true Use Uint8Array implementation (fastest) |
| * === false Use Object implementation (compatible down to IE6) |
| */ |
| Buffer._useTypedArrays = (function () { |
| // Detect if browser supports Typed Arrays. Supported browsers are IE 10+, Firefox 4+, |
| // Chrome 7+, Safari 5.1+, Opera 11.6+, iOS 4.2+. If the browser does not support adding |
| // properties to `Uint8Array` instances, then that's the same as no `Uint8Array` support |
| // because we need to be able to add all the node Buffer API methods. This is an issue |
| // in Firefox 4-29. Now fixed: https://bugzilla.mozilla.org/show_bug.cgi?id=695438 |
| try { |
| var buf = new ArrayBuffer(0) |
| var arr = new Uint8Array(buf) |
| arr.foo = function () { return 42 } |
| return 42 === arr.foo() && |
| typeof arr.subarray === 'function' // Chrome 9-10 lack `subarray` |
| } catch (e) { |
| return false |
| } |
| })() |
| |
| /** |
| * Class: Buffer |
| * ============= |
| * |
| * The Buffer constructor returns instances of `Uint8Array` that are augmented |
| * with function properties for all the node `Buffer` API functions. We use |
| * `Uint8Array` so that square bracket notation works as expected -- it returns |
| * a single octet. |
| * |
| * By augmenting the instances, we can avoid modifying the `Uint8Array` |
| * prototype. |
| */ |
| function Buffer (subject, encoding, noZero) { |
| if (!(this instanceof Buffer)) |
| return new Buffer(subject, encoding, noZero) |
| |
| var type = typeof subject |
| |
| // Workaround: node's base64 implementation allows for non-padded strings |
| // while base64-js does not. |
| if (encoding === 'base64' && type === 'string') { |
| subject = Buffer.stringtrim(subject) |
| while (subject.length % 4 !== 0) { |
| subject = subject + '=' |
| } |
| } |
| |
| // Find the length |
| var length |
| if (type === 'number') |
| length = Buffer.coerce(subject) |
| else if (type === 'string') |
| length = Buffer.byteLength(subject, encoding) |
| else if (type === 'object') |
| length = Buffer.coerce(subject.length) // assume that object is array-like |
| else |
| throw new Error('First argument needs to be a number, array or string.') |
| |
| var buf |
| if (Buffer._useTypedArrays) { |
| // Preferred: Return an augmented `Uint8Array` instance for best performance |
| buf = Buffer._augment(new Uint8Array(length)) |
| } else { |
| // Fallback: Return THIS instance of Buffer (created by `new`) |
| buf = this |
| buf.length = length |
| buf._isBuffer = true |
| } |
| |
| var i |
| if (Buffer._useTypedArrays && typeof subject.byteLength === 'number') { |
| // Speed optimization -- use set if we're copying from a typed array |
| buf._set(subject) |
| } else if (Buffer.isArrayish(subject)) { |
| // Treat array-ish objects as a byte array |
| if (Buffer.isBuffer(subject)) { |
| for (i = 0; i < length; i++) |
| buf[i] = subject.readUInt8(i) |
| } else { |
| for (i = 0; i < length; i++) |
| buf[i] = ((subject[i] % 256) + 256) % 256 |
| } |
| } else if (type === 'string') { |
| buf.write(subject, 0, encoding) |
| } else if (type === 'number' && !Buffer._useTypedArrays && !noZero) { |
| for (i = 0; i < length; i++) { |
| buf[i] = 0 |
| } |
| } |
| |
| return buf |
| } |
| |
| // STATIC METHODS |
| // ============== |
| |
| Buffer.isEncoding = function (encoding) { |
| switch (String(encoding).toLowerCase()) { |
| case 'hex': |
| case 'utf8': |
| case 'utf-8': |
| case 'ascii': |
| case 'binary': |
| case 'base64': |
| case 'raw': |
| case 'ucs2': |
| case 'ucs-2': |
| case 'utf16le': |
| case 'utf-16le': |
| return true |
| default: |
| return false |
| } |
| } |
| |
| Buffer.isBuffer = function (b) { |
| return !!(b !== null && b !== undefined && b._isBuffer) |
| } |
| |
| Buffer.byteLength = function (str, encoding) { |
| var ret |
| str = str.toString() |
| switch (encoding || 'utf8') { |
| case 'hex': |
| ret = str.length / 2 |
| break |
| case 'utf8': |
| case 'utf-8': |
| ret = Buffer.utf8ToBytes(str).length |
| break |
| case 'ascii': |
| case 'binary': |
| case 'raw': |
| ret = str.length |
| break |
| case 'base64': |
| ret = Buffer.base64ToBytes(str).length |
| break |
| case 'ucs2': |
| case 'ucs-2': |
| case 'utf16le': |
| case 'utf-16le': |
| ret = str.length * 2 |
| break |
| default: |
| throw new Error('Unknown encoding') |
| } |
| return ret |
| } |
| |
| Buffer.concat = function (list, totalLength) { |
| Buffer.assert(Buffer.isArray(list), 'Usage: Buffer.concat(list[, length])') |
| |
| if (list.length === 0) { |
| return new Buffer(0) |
| } else if (list.length === 1) { |
| return list[0] |
| } |
| |
| var i |
| if (totalLength === undefined) { |
| totalLength = 0 |
| for (i = 0; i < list.length; i++) { |
| totalLength += list[i].length |
| } |
| } |
| |
| var buf = new Buffer(totalLength) |
| var pos = 0 |
| for (i = 0; i < list.length; i++) { |
| var item = list[i] |
| item.copy(buf, pos) |
| pos += item.length |
| } |
| return buf |
| } |
| |
| Buffer.compare = function (a, b) { |
| Buffer.assert(Buffer.isBuffer(a) && Buffer.isBuffer(b), 'Arguments must be Buffers') |
| var x = a.length |
| var y = b.length |
| for (var i = 0, len = Math.min(x, y); i < len && a[i] === b[i]; i++) {} |
| if (i !== len) { |
| x = a[i] |
| y = b[i] |
| } |
| if (x < y) { |
| return -1 |
| } |
| if (y < x) { |
| return 1 |
| } |
| return 0 |
| } |
| |
| // BUFFER INSTANCE METHODS |
| // ======================= |
| |
| Buffer.hexWrite = function(buf, string, offset, length) { |
| offset = Number(offset) || 0 |
| var remaining = buf.length - offset |
| if (!length) { |
| length = remaining |
| } else { |
| length = Number(length) |
| if (length > remaining) { |
| length = remaining |
| } |
| } |
| |
| // must be an even number of digits |
| var strLen = string.length |
| Buffer.assert(strLen % 2 === 0, 'Invalid hex string') |
| |
| if (length > strLen / 2) { |
| length = strLen / 2 |
| } |
| for (var i = 0; i < length; i++) { |
| var b = parseInt(string.substr(i * 2, 2), 16) |
| Buffer.assert(!isNaN(b), 'Invalid hex string') |
| buf[offset + i] = b |
| } |
| return i |
| } |
| |
| Buffer.utf8Write = function(buf, string, offset, length) { |
| var charsWritten = Buffer.blitBuffer(Buffer.utf8ToBytes(string), buf, offset, length) |
| return charsWritten |
| } |
| |
| Buffer.asciiWrite = function(buf, string, offset, length) { |
| var charsWritten = Buffer.blitBuffer(Buffer.asciiToBytes(string), buf, offset, length) |
| return charsWritten |
| } |
| |
| Buffer.binaryWrite = function(buf, string, offset, length) { |
| return Buffer.asciiWrite(buf, string, offset, length) |
| } |
| |
| Buffer.base64Write = function(buf, string, offset, length) { |
| var charsWritten = Buffer.blitBuffer(Buffer.base64ToBytes(string), buf, offset, length) |
| return charsWritten |
| } |
| |
| Buffer.utf16leWrite = function(buf, string, offset, length) { |
| var charsWritten = Buffer.blitBuffer(Buffer.utf16leToBytes(string), buf, offset, length) |
| return charsWritten |
| } |
| |
| Buffer.prototype.write = function (string, offset, length, encoding) { |
| // Support both (string, offset, length, encoding) |
| // and the legacy (string, encoding, offset, length) |
| if (isFinite(offset)) { |
| if (!isFinite(length)) { |
| encoding = length |
| length = undefined |
| } |
| } else { // legacy |
| var swap = encoding |
| encoding = offset |
| offset = length |
| length = swap |
| } |
| |
| offset = Number(offset) || 0 |
| var remaining = this.length - offset |
| if (!length) { |
| length = remaining |
| } else { |
| length = Number(length) |
| if (length > remaining) { |
| length = remaining |
| } |
| } |
| encoding = String(encoding || 'utf8').toLowerCase() |
| |
| var ret |
| switch (encoding) { |
| case 'hex': |
| ret = Buffer.hexWrite(this, string, offset, length) |
| break |
| case 'utf8': |
| case 'utf-8': |
| ret = Buffer.utf8Write(this, string, offset, length) |
| break |
| case 'ascii': |
| ret = Buffer.asciiWrite(this, string, offset, length) |
| break |
| case 'binary': |
| ret = Buffer.binaryWrite(this, string, offset, length) |
| break |
| case 'base64': |
| ret = Buffer.base64Write(this, string, offset, length) |
| break |
| case 'ucs2': |
| case 'ucs-2': |
| case 'utf16le': |
| case 'utf-16le': |
| ret = Buffer.utf16leWrite(this, string, offset, length) |
| break |
| default: |
| throw new Error('Unknown encoding') |
| } |
| return ret |
| } |
| |
| Buffer.prototype.toString = function (encoding, start, end) { |
| var self = this |
| |
| encoding = String(encoding || 'utf8').toLowerCase() |
| start = Number(start) || 0 |
| end = (end === undefined) ? self.length : Number(end) |
| |
| // Fastpath empty strings |
| if (end === start) |
| return '' |
| |
| var ret |
| switch (encoding) { |
| case 'hex': |
| ret = Buffer.hexSlice(self, start, end) |
| break |
| case 'utf8': |
| case 'utf-8': |
| ret = Buffer.utf8Slice(self, start, end) |
| break |
| case 'ascii': |
| ret = Buffer.asciiSlice(self, start, end) |
| break |
| case 'binary': |
| ret = Buffer.binarySlice(self, start, end) |
| break |
| case 'base64': |
| ret = Buffer.base64Slice(self, start, end) |
| break |
| case 'ucs2': |
| case 'ucs-2': |
| case 'utf16le': |
| case 'utf-16le': |
| ret = utf16leSlice(self, start, end) |
| break |
| default: |
| throw new Error('Unknown encoding') |
| } |
| return ret |
| } |
| |
| Buffer.prototype.toJSON = function () { |
| return { |
| type: 'Buffer', |
| data: Array.prototype.slice.call(this._arr || this, 0) |
| } |
| } |
| |
| Buffer.prototype.equals = function (b) { |
| Buffer.assert(Buffer.isBuffer(b), 'Argument must be a Buffer') |
| return Buffer.compare(this, b) === 0 |
| } |
| |
| Buffer.prototype.compare = function (b) { |
| Buffer.assert(Buffer.isBuffer(b), 'Argument must be a Buffer') |
| return Buffer.compare(this, b) |
| } |
| |
| // copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) |
| Buffer.prototype.copy = function (target, target_start, start, end) { |
| var source = this |
| |
| if (!start) start = 0 |
| if (!end && end !== 0) end = this.length |
| if (!target_start) target_start = 0 |
| |
| // Copy 0 bytes; we're done |
| if (end === start) return |
| if (target.length === 0 || source.length === 0) return |
| |
| // Fatal error conditions |
| Buffer.assert(end >= start, 'sourceEnd < sourceStart') |
| Buffer.assert(target_start >= 0 && target_start < target.length, |
| 'targetStart out of bounds') |
| Buffer.assert(start >= 0 && start < source.length, 'sourceStart out of bounds') |
| Buffer.assert(end >= 0 && end <= source.length, 'sourceEnd out of bounds') |
| |
| // Are we oob? |
| if (end > this.length) |
| end = this.length |
| if (target.length - target_start < end - start) |
| end = target.length - target_start + start |
| |
| var len = end - start |
| |
| if (len < 100 || !Buffer._useTypedArrays) { |
| for (var i = 0; i < len; i++) { |
| target[i + target_start] = this[i + start] |
| } |
| } else { |
| target._set(this.subarray(start, start + len), target_start) |
| } |
| } |
| |
| Buffer.base64Slice = function(buf, start, end) { |
| if (start === 0 && end === buf.length) { |
| return base64.fromByteArray(buf) |
| } else { |
| return base64.fromByteArray(buf.slice(start, end)) |
| } |
| } |
| |
| Buffer.utf8Slice = function(buf, start, end) { |
| var res = '' |
| var tmp = '' |
| end = Math.min(buf.length, end) |
| |
| for (var i = start; i < end; i++) { |
| if (buf[i] <= 0x7F) { |
| res += Buffer.decodeUtf8Char(tmp) + String.fromCharCode(buf[i]) |
| tmp = '' |
| } else { |
| tmp += '%' + buf[i].toString(16) |
| } |
| } |
| |
| return res + Buffer.decodeUtf8Char(tmp) |
| } |
| |
| Buffer.asciiSlice = function(buf, start, end) { |
| var ret = '' |
| end = Math.min(buf.length, end) |
| |
| for (var i = start; i < end; i++) { |
| ret += String.fromCharCode(buf[i]) |
| } |
| return ret |
| } |
| |
| Buffer.binarySlice = function(buf, start, end) { |
| return Buffer.asciiSlice(buf, start, end) |
| } |
| |
| Buffer.hexSlice = function(buf, start, end) { |
| var len = buf.length |
| |
| if (!start || start < 0) start = 0 |
| if (!end || end < 0 || end > len) end = len |
| |
| var out = '' |
| for (var i = start; i < end; i++) { |
| out += Buffer.toHex(buf[i]) |
| } |
| return out |
| } |
| |
| function utf16leSlice (buf, start, end) { |
| var bytes = buf.slice(start, end) |
| var res = '' |
| for (var i = 0; i < bytes.length; i += 2) { |
| res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256) |
| } |
| return res |
| } |
| |
| Buffer.prototype.slice = function (start, end) { |
| var len = this.length |
| start = Buffer.clamp(start, len, 0) |
| end = Buffer.clamp(end, len, len) |
| |
| if (Buffer._useTypedArrays) { |
| return Buffer._augment(this.subarray(start, end)) |
| } else { |
| var sliceLen = end - start |
| var newBuf = new Buffer(sliceLen, undefined, true) |
| for (var i = 0; i < sliceLen; i++) { |
| newBuf[i] = this[i + start] |
| } |
| return newBuf |
| } |
| } |
| |
| // `get` will be removed in Node 0.13+ |
| Buffer.prototype.get = function (offset) { |
| console.log('.get() is deprecated. Access using array indexes instead.') |
| return this.readUInt8(offset) |
| } |
| |
| // `set` will be removed in Node 0.13+ |
| Buffer.prototype.set = function (v, offset) { |
| console.log('.set() is deprecated. Access using array indexes instead.') |
| return this.writeUInt8(v, offset) |
| } |
| |
| Buffer.prototype.readUInt8 = function (offset, noAssert) { |
| if (!noAssert) { |
| Buffer.assert(offset !== undefined && offset !== null, 'missing offset') |
| Buffer.assert(offset < this.length, 'Trying to read beyond buffer length') |
| } |
| |
| if (offset >= this.length) |
| return |
| |
| return this[offset] |
| } |
| |
| Buffer.readUInt16 = function(buf, offset, littleEndian, noAssert) { |
| if (!noAssert) { |
| Buffer.assert(typeof littleEndian === 'boolean', 'missing or invalid endian') |
| Buffer.assert(offset !== undefined && offset !== null, 'missing offset') |
| Buffer.assert(offset + 1 < buf.length, 'Trying to read beyond buffer length') |
| } |
| |
| var len = buf.length |
| if (offset >= len) |
| return |
| |
| var val |
| if (littleEndian) { |
| val = buf[offset] |
| if (offset + 1 < len) |
| val |= buf[offset + 1] << 8 |
| } else { |
| val = buf[offset] << 8 |
| if (offset + 1 < len) |
| val |= buf[offset + 1] |
| } |
| return val |
| } |
| |
| Buffer.prototype.readUInt16LE = function (offset, noAssert) { |
| return Buffer.readUInt16(this, offset, true, noAssert) |
| } |
| |
| Buffer.prototype.readUInt16BE = function (offset, noAssert) { |
| return Buffer.readUInt16(this, offset, false, noAssert) |
| } |
| |
| Buffer.readUInt32 = function(buf, offset, littleEndian, noAssert) { |
| if (!noAssert) { |
| Buffer.assert(typeof littleEndian === 'boolean', 'missing or invalid endian') |
| Buffer.assert(offset !== undefined && offset !== null, 'missing offset') |
| Buffer.assert(offset + 3 < buf.length, 'Trying to read beyond buffer length') |
| } |
| |
| var len = buf.length |
| if (offset >= len) |
| return |
| |
| var val |
| if (littleEndian) { |
| if (offset + 2 < len) |
| val = buf[offset + 2] << 16 |
| if (offset + 1 < len) |
| val |= buf[offset + 1] << 8 |
| val |= buf[offset] |
| if (offset + 3 < len) |
| val = val + (buf[offset + 3] << 24 >>> 0) |
| } else { |
| if (offset + 1 < len) |
| val = buf[offset + 1] << 16 |
| if (offset + 2 < len) |
| val |= buf[offset + 2] << 8 |
| if (offset + 3 < len) |
| val |= buf[offset + 3] |
| val = val + (buf[offset] << 24 >>> 0) |
| } |
| return val |
| } |
| |
| Buffer.prototype.readUInt32LE = function (offset, noAssert) { |
| return Buffer.readUInt32(this, offset, true, noAssert) |
| } |
| |
| Buffer.prototype.readUInt32BE = function (offset, noAssert) { |
| return Buffer.readUInt32(this, offset, false, noAssert) |
| } |
| |
| Buffer.prototype.readInt8 = function (offset, noAssert) { |
| if (!noAssert) { |
| Buffer.assert(offset !== undefined && offset !== null, |
| 'missing offset') |
| Buffer.assert(offset < this.length, 'Trying to read beyond buffer length') |
| } |
| |
| if (offset >= this.length) |
| return |
| |
| var neg = this[offset] & 0x80 |
| if (neg) |
| return (0xff - this[offset] + 1) * -1 |
| else |
| return this[offset] |
| } |
| |
| Buffer.readInt16 = function(buf, offset, littleEndian, noAssert) { |
| if (!noAssert) { |
| Buffer.assert(typeof littleEndian === 'boolean', 'missing or invalid endian') |
| Buffer.assert(offset !== undefined && offset !== null, 'missing offset') |
| Buffer.assert(offset + 1 < buf.length, 'Trying to read beyond buffer length') |
| } |
| |
| var len = buf.length |
| if (offset >= len) |
| return |
| |
| var val = Buffer.readUInt16(buf, offset, littleEndian, true) |
| var neg = val & 0x8000 |
| if (neg) |
| return (0xffff - val + 1) * -1 |
| else |
| return val |
| } |
| |
| Buffer.prototype.readInt16LE = function (offset, noAssert) { |
| return Buffer.readInt16(this, offset, true, noAssert) |
| } |
| |
| Buffer.prototype.readInt16BE = function (offset, noAssert) { |
| return Buffer.readInt16(this, offset, false, noAssert) |
| } |
| |
| Buffer.readInt32 = function(buf, offset, littleEndian, noAssert) { |
| if (!noAssert) { |
| Buffer.assert(typeof littleEndian === 'boolean', 'missing or invalid endian') |
| Buffer.assert(offset !== undefined && offset !== null, 'missing offset') |
| Buffer.assert(offset + 3 < buf.length, 'Trying to read beyond buffer length') |
| } |
| |
| var len = buf.length |
| if (offset >= len) |
| return |
| |
| var val = Buffer.readUInt32(buf, offset, littleEndian, true) |
| var neg = val & 0x80000000 |
| if (neg) |
| return (0xffffffff - val + 1) * -1 |
| else |
| return val |
| } |
| |
| Buffer.prototype.readInt32LE = function (offset, noAssert) { |
| return Buffer.readInt32(this, offset, true, noAssert) |
| } |
| |
| Buffer.prototype.readInt32BE = function (offset, noAssert) { |
| return Buffer.readInt32(this, offset, false, noAssert) |
| } |
| |
| Buffer.readFloat = function(buf, offset, littleEndian, noAssert) { |
| if (!noAssert) { |
| Buffer.assert(typeof littleEndian === 'boolean', 'missing or invalid endian') |
| Buffer.assert(offset + 3 < buf.length, 'Trying to read beyond buffer length') |
| } |
| |
| return ieee754.read(buf, offset, littleEndian, 23, 4) |
| } |
| |
| Buffer.prototype.readFloatLE = function (offset, noAssert) { |
| return Buffer.readFloat(this, offset, true, noAssert) |
| } |
| |
| Buffer.prototype.readFloatBE = function (offset, noAssert) { |
| return Buffer.readFloat(this, offset, false, noAssert) |
| } |
| |
| Buffer.readDouble = function(buf, offset, littleEndian, noAssert) { |
| if (!noAssert) { |
| Buffer.assert(typeof littleEndian === 'boolean', 'missing or invalid endian') |
| Buffer.assert(offset + 7 < buf.length, 'Trying to read beyond buffer length') |
| } |
| |
| return ieee754.read(buf, offset, littleEndian, 52, 8) |
| } |
| |
| Buffer.prototype.readDoubleLE = function (offset, noAssert) { |
| return Buffer.readDouble(this, offset, true, noAssert) |
| } |
| |
| Buffer.prototype.readDoubleBE = function (offset, noAssert) { |
| return Buffer.readDouble(this, offset, false, noAssert) |
| } |
| |
| Buffer.prototype.writeUInt8 = function (value, offset, noAssert) { |
| if (!noAssert) { |
| Buffer.assert(value !== undefined && value !== null, 'missing value') |
| Buffer.assert(offset !== undefined && offset !== null, 'missing offset') |
| Buffer.assert(offset < this.length, 'trying to write beyond buffer length') |
| Buffer.verifuint(value, 0xff) |
| } |
| |
| if (offset >= this.length) return |
| |
| this[offset] = value |
| return offset + 1 |
| } |
| |
| Buffer.writeUInt16 = function(buf, value, offset, littleEndian, noAssert) { |
| if (!noAssert) { |
| Buffer.assert(value !== undefined && value !== null, 'missing value') |
| Buffer.assert(typeof littleEndian === 'boolean', 'missing or invalid endian') |
| Buffer.assert(offset !== undefined && offset !== null, 'missing offset') |
| Buffer.assert(offset + 1 < buf.length, 'trying to write beyond buffer length') |
| Buffer.verifuint(value, 0xffff) |
| } |
| |
| var len = buf.length |
| if (offset >= len) |
| return |
| |
| for (var i = 0, j = Math.min(len - offset, 2); i < j; i++) { |
| buf[offset + i] = |
| (value & (0xff << (8 * (littleEndian ? i : 1 - i)))) >>> |
| (littleEndian ? i : 1 - i) * 8 |
| } |
| return offset + 2 |
| } |
| |
| Buffer.prototype.writeUInt16LE = function (value, offset, noAssert) { |
| return Buffer.writeUInt16(this, value, offset, true, noAssert) |
| } |
| |
| Buffer.prototype.writeUInt16BE = function (value, offset, noAssert) { |
| return Buffer.writeUInt16(this, value, offset, false, noAssert) |
| } |
| |
| Buffer.writeUInt32 = function(buf, value, offset, littleEndian, noAssert) { |
| if (!noAssert) { |
| Buffer.assert(value !== undefined && value !== null, 'missing value') |
| Buffer.assert(typeof littleEndian === 'boolean', 'missing or invalid endian') |
| Buffer.assert(offset !== undefined && offset !== null, 'missing offset') |
| Buffer.assert(offset + 3 < buf.length, 'trying to write beyond buffer length') |
| Buffer.verifuint(value, 0xffffffff) |
| } |
| |
| var len = buf.length |
| if (offset >= len) |
| return |
| |
| for (var i = 0, j = Math.min(len - offset, 4); i < j; i++) { |
| buf[offset + i] = |
| (value >>> (littleEndian ? i : 3 - i) * 8) & 0xff |
| } |
| return offset + 4 |
| } |
| |
| Buffer.prototype.writeUInt32LE = function (value, offset, noAssert) { |
| return Buffer.writeUInt32(this, value, offset, true, noAssert) |
| } |
| |
| Buffer.prototype.writeUInt32BE = function (value, offset, noAssert) { |
| return Buffer.writeUInt32(this, value, offset, false, noAssert) |
| } |
| |
| Buffer.prototype.writeInt8 = function (value, offset, noAssert) { |
| if (!noAssert) { |
| Buffer.assert(value !== undefined && value !== null, 'missing value') |
| Buffer.assert(offset !== undefined && offset !== null, 'missing offset') |
| Buffer.assert(offset < this.length, 'Trying to write beyond buffer length') |
| Buffer.verifsint(value, 0x7f, -0x80) |
| } |
| |
| if (offset >= this.length) |
| return |
| |
| if (value >= 0) |
| this.writeUInt8(value, offset, noAssert) |
| else |
| this.writeUInt8(0xff + value + 1, offset, noAssert) |
| return offset + 1 |
| } |
| |
| Buffer.writeInt16 = function(buf, value, offset, littleEndian, noAssert) { |
| if (!noAssert) { |
| Buffer.assert(value !== undefined && value !== null, 'missing value') |
| Buffer.assert(typeof littleEndian === 'boolean', 'missing or invalid endian') |
| Buffer.assert(offset !== undefined && offset !== null, 'missing offset') |
| Buffer.assert(offset + 1 < buf.length, 'Trying to write beyond buffer length') |
| Buffer.verifsint(value, 0x7fff, -0x8000) |
| } |
| |
| var len = buf.length |
| if (offset >= len) |
| return |
| |
| if (value >= 0) |
| Buffer.writeUInt16(buf, value, offset, littleEndian, noAssert) |
| else |
| Buffer.writeUInt16(buf, 0xffff + value + 1, offset, littleEndian, noAssert) |
| return offset + 2 |
| } |
| |
| Buffer.prototype.writeInt16LE = function (value, offset, noAssert) { |
| return Buffer.writeInt16(this, value, offset, true, noAssert) |
| } |
| |
| Buffer.prototype.writeInt16BE = function (value, offset, noAssert) { |
| return Buffer.writeInt16(this, value, offset, false, noAssert) |
| } |
| |
| Buffer.writeInt32 = function(buf, value, offset, littleEndian, noAssert) { |
| if (!noAssert) { |
| Buffer.assert(value !== undefined && value !== null, 'missing value') |
| Buffer.assert(typeof littleEndian === 'boolean', 'missing or invalid endian') |
| Buffer.assert(offset !== undefined && offset !== null, 'missing offset') |
| Buffer.assert(offset + 3 < buf.length, 'Trying to write beyond buffer length') |
| Buffer.verifsint(value, 0x7fffffff, -0x80000000) |
| } |
| |
| var len = buf.length |
| if (offset >= len) |
| return |
| |
| if (value >= 0) |
| Buffer.writeUInt32(buf, value, offset, littleEndian, noAssert) |
| else |
| Buffer.writeUInt32(buf, 0xffffffff + value + 1, offset, littleEndian, noAssert) |
| return offset + 4 |
| } |
| |
| Buffer.prototype.writeInt32LE = function (value, offset, noAssert) { |
| return Buffer.writeInt32(this, value, offset, true, noAssert) |
| } |
| |
| Buffer.prototype.writeInt32BE = function (value, offset, noAssert) { |
| return Buffer.writeInt32(this, value, offset, false, noAssert) |
| } |
| |
| Buffer.writeFloat = function(buf, value, offset, littleEndian, noAssert) { |
| if (!noAssert) { |
| Buffer.assert(value !== undefined && value !== null, 'missing value') |
| Buffer.assert(typeof littleEndian === 'boolean', 'missing or invalid endian') |
| Buffer.assert(offset !== undefined && offset !== null, 'missing offset') |
| Buffer.assert(offset + 3 < buf.length, 'Trying to write beyond buffer length') |
| Buffer.verifIEEE754(value, 3.4028234663852886e+38, -3.4028234663852886e+38) |
| } |
| |
| var len = buf.length |
| if (offset >= len) |
| return |
| |
| ieee754.write(buf, value, offset, littleEndian, 23, 4) |
| return offset + 4 |
| } |
| |
| Buffer.prototype.writeFloatLE = function (value, offset, noAssert) { |
| return Buffer.writeFloat(this, value, offset, true, noAssert) |
| } |
| |
| Buffer.prototype.writeFloatBE = function (value, offset, noAssert) { |
| return Buffer.writeFloat(this, value, offset, false, noAssert) |
| } |
| |
| Buffer.writeDouble = function(buf, value, offset, littleEndian, noAssert) { |
| if (!noAssert) { |
| Buffer.assert(value !== undefined && value !== null, 'missing value') |
| Buffer.assert(typeof littleEndian === 'boolean', 'missing or invalid endian') |
| Buffer.assert(offset !== undefined && offset !== null, 'missing offset') |
| Buffer.assert(offset + 7 < buf.length, |
| 'Trying to write beyond buffer length') |
| Buffer.verifIEEE754(value, 1.7976931348623157E+308, -1.7976931348623157E+308) |
| } |
| |
| var len = buf.length |
| if (offset >= len) |
| return |
| |
| ieee754.write(buf, value, offset, littleEndian, 52, 8) |
| return offset + 8 |
| } |
| |
| Buffer.prototype.writeDoubleLE = function (value, offset, noAssert) { |
| return Buffer.writeDouble(this, value, offset, true, noAssert) |
| } |
| |
| Buffer.prototype.writeDoubleBE = function (value, offset, noAssert) { |
| return Buffer.writeDouble(this, value, offset, false, noAssert) |
| } |
| |
| // fill(value, start=0, end=buffer.length) |
| Buffer.prototype.fill = function (value, start, end) { |
| if (!value) value = 0 |
| if (!start) start = 0 |
| if (!end) end = this.length |
| |
| Buffer.assert(end >= start, 'end < start') |
| |
| // Fill 0 bytes; we're done |
| if (end === start) return |
| if (this.length === 0) return |
| |
| Buffer.assert(start >= 0 && start < this.length, 'start out of bounds') |
| Buffer.assert(end >= 0 && end <= this.length, 'end out of bounds') |
| |
| var i |
| if (typeof value === 'number') { |
| for (i = start; i < end; i++) { |
| this[i] = value |
| } |
| } else { |
| var bytes = Buffer.utf8ToBytes(value.toString()) |
| var len = bytes.length |
| for (i = start; i < end; i++) { |
| this[i] = bytes[i % len] |
| } |
| } |
| |
| return this |
| } |
| |
| Buffer.prototype.inspect = function () { |
| var out = [] |
| var len = this.length |
| for (var i = 0; i < len; i++) { |
| out[i] = Buffer.toHex(this[i]) |
| if (i === exports.INSPECT_MAX_BYTES) { |
| out[i + 1] = '...' |
| break |
| } |
| } |
| return '<Buffer ' + out.join(' ') + '>' |
| } |
| |
| /** |
| * Creates a new `ArrayBuffer` with the *copied* memory of the buffer instance. |
| * Added in Node 0.12. Only available in browsers that support ArrayBuffer. |
| */ |
| Buffer.prototype.toArrayBuffer = function () { |
| if (typeof Uint8Array !== 'undefined') { |
| if (Buffer._useTypedArrays) { |
| return (new Buffer(this)).buffer |
| } else { |
| var buf = new Uint8Array(this.length) |
| for (var i = 0, len = buf.length; i < len; i += 1) { |
| buf[i] = this[i] |
| } |
| return buf.buffer |
| } |
| } else { |
| throw new Error('Buffer.toArrayBuffer not supported in this browser') |
| } |
| } |
| |
| // HELPER FUNCTIONS |
| // ================ |
| |
| var BP = Buffer.prototype |
| |
| /** |
| * Augment a Uint8Array *instance* (not the Uint8Array class!) with Buffer methods |
| */ |
| Buffer._augment = function (arr) { |
| arr._isBuffer = true |
| |
| // save reference to original Uint8Array get/set methods before overwriting |
| arr._get = arr.get |
| arr._set = arr.set |
| |
| // deprecated, will be removed in node 0.13+ |
| arr.get = BP.get |
| arr.set = BP.set |
| |
| arr.write = BP.write |
| arr.toString = BP.toString |
| arr.toLocaleString = BP.toString |
| arr.toJSON = BP.toJSON |
| arr.equals = BP.equals |
| arr.compare = BP.compare |
| arr.copy = BP.copy |
| arr.slice = BP.slice |
| arr.readUInt8 = BP.readUInt8 |
| arr.readUInt16LE = BP.readUInt16LE |
| arr.readUInt16BE = BP.readUInt16BE |
| arr.readUInt32LE = BP.readUInt32LE |
| arr.readUInt32BE = BP.readUInt32BE |
| arr.readInt8 = BP.readInt8 |
| arr.readInt16LE = BP.readInt16LE |
| arr.readInt16BE = BP.readInt16BE |
| arr.readInt32LE = BP.readInt32LE |
| arr.readInt32BE = BP.readInt32BE |
| arr.readFloatLE = BP.readFloatLE |
| arr.readFloatBE = BP.readFloatBE |
| arr.readDoubleLE = BP.readDoubleLE |
| arr.readDoubleBE = BP.readDoubleBE |
| arr.writeUInt8 = BP.writeUInt8 |
| arr.writeUInt16LE = BP.writeUInt16LE |
| arr.writeUInt16BE = BP.writeUInt16BE |
| arr.writeUInt32LE = BP.writeUInt32LE |
| arr.writeUInt32BE = BP.writeUInt32BE |
| arr.writeInt8 = BP.writeInt8 |
| arr.writeInt16LE = BP.writeInt16LE |
| arr.writeInt16BE = BP.writeInt16BE |
| arr.writeInt32LE = BP.writeInt32LE |
| arr.writeInt32BE = BP.writeInt32BE |
| arr.writeFloatLE = BP.writeFloatLE |
| arr.writeFloatBE = BP.writeFloatBE |
| arr.writeDoubleLE = BP.writeDoubleLE |
| arr.writeDoubleBE = BP.writeDoubleBE |
| arr.fill = BP.fill |
| arr.inspect = BP.inspect |
| arr.toArrayBuffer = BP.toArrayBuffer |
| |
| return arr |
| } |
| |
| Buffer.stringtrim = function(str) { |
| if (str.trim) return str.trim() |
| return str.replace(/^\s+|\s+$/g, '') |
| } |
| |
| // slice(start, end) |
| Buffer.clamp = function(index, len, defaultValue) { |
| if (typeof index !== 'number') return defaultValue |
| index = ~~index; // Coerce to integer. |
| if (index >= len) return len |
| if (index >= 0) return index |
| index += len |
| if (index >= 0) return index |
| return 0 |
| } |
| |
| Buffer.coerce = function(length) { |
| // Coerce length to a number (possibly NaN), round up |
| // in case it's fractional (e.g. 123.456) then do a |
| // double negate to coerce a NaN to 0. Easy, right? |
| length = ~~Math.ceil(+length) |
| return length < 0 ? 0 : length |
| } |
| |
| Buffer.isArray = function(subject) { |
| return (Array.isArray || function (subject) { |
| return Object.prototype.toString.call(subject) === '[object Array]' |
| })(subject) |
| } |
| |
| Buffer.isArrayish = function(subject) { |
| return Buffer.isArray(subject) || Buffer.isBuffer(subject) || |
| subject && typeof subject === 'object' && |
| typeof subject.length === 'number' |
| } |
| |
| Buffer.toHex = function(n) { |
| if (n < 16) return '0' + n.toString(16) |
| return n.toString(16) |
| } |
| |
| Buffer.utf8ToBytes = function(str) { |
| var byteArray = [] |
| for (var i = 0; i < str.length; i++) { |
| var b = str.charCodeAt(i) |
| if (b <= 0x7F) { |
| byteArray.push(b) |
| } else { |
| var start = i |
| if (b >= 0xD800 && b <= 0xDFFF) i++ |
| var h = encodeURIComponent(str.slice(start, i+1)).substr(1).split('%') |
| for (var j = 0; j < h.length; j++) { |
| byteArray.push(parseInt(h[j], 16)) |
| } |
| } |
| } |
| return byteArray |
| } |
| |
| Buffer.asciiToBytes = function(str) { |
| var byteArray = [] |
| for (var i = 0; i < str.length; i++) { |
| // Node's code seems to be doing this and not & 0x7F.. |
| byteArray.push(str.charCodeAt(i) & 0xFF) |
| } |
| return byteArray |
| } |
| |
| Buffer.utf16leToBytes = function(str) { |
| var c, hi, lo |
| var byteArray = [] |
| for (var i = 0; i < str.length; i++) { |
| c = str.charCodeAt(i) |
| hi = c >> 8 |
| lo = c % 256 |
| byteArray.push(lo) |
| byteArray.push(hi) |
| } |
| |
| return byteArray |
| } |
| |
| Buffer.base64ToBytes = function(str) { |
| return base64.toByteArray(str) |
| } |
| |
| Buffer.blitBuffer = function(src, dst, offset, length) { |
| for (var i = 0; i < length; i++) { |
| if ((i + offset >= dst.length) || (i >= src.length)) |
| break |
| dst[i + offset] = src[i] |
| } |
| return i |
| } |
| |
| Buffer.decodeUtf8Char = function(str) { |
| try { |
| return decodeURIComponent(str) |
| } catch (err) { |
| return String.fromCharCode(0xFFFD) // UTF 8 invalid char |
| } |
| } |
| |
| /* |
| * We have to make sure that the value is a valid integer. This means that it |
| * is non-negative. It has no fractional component and that it does not |
| * exceed the maximum allowed value. |
| */ |
| Buffer.verifuint = function(value, max) { |
| Buffer.assert(typeof value === 'number', 'cannot write a non-number as a number') |
| Buffer.assert(value >= 0, 'specified a negative value for writing an unsigned value') |
| Buffer.assert(value <= max, 'value is larger than maximum value for type') |
| Buffer.assert(Math.floor(value) === value, 'value has a fractional component') |
| } |
| |
| Buffer.verifsint = function(value, max, min) { |
| Buffer.assert(typeof value === 'number', 'cannot write a non-number as a number') |
| Buffer.assert(value <= max, 'value larger than maximum allowed value') |
| Buffer.assert(value >= min, 'value smaller than minimum allowed value') |
| Buffer.assert(Math.floor(value) === value, 'value has a fractional component') |
| } |
| |
| Buffer.verifIEEE754 = function(value, max, min) { |
| Buffer.assert(typeof value === 'number', 'cannot write a non-number as a number') |
| Buffer.assert(value <= max, 'value larger than maximum allowed value') |
| Buffer.assert(value >= min, 'value smaller than minimum allowed value') |
| } |
| |
| Buffer.assert = function(test, message) { |
| if (!test) throw new Error(message || 'Failed assertion') |
| } |
| /** |
| * Copyright (C) 2013-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * The Log class holds the global static variable LOG. |
| */ |
| var Log = function Log() |
| { |
| } |
| |
| exports.Log = Log; |
| |
| /** |
| * LOG is the level for logging debugging statements. 0 means no log messages. |
| * @type Number |
| */ |
| Log.LOG = 0; |
| /** |
| * Encapsulate a Buffer and support dynamic reallocation. |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var printStackTrace = require('../../contrib/stacktrace/stacktrace.js').printStackTrace; |
| |
| /** |
| * NdnCommon has static NDN utility methods and constants. |
| * @constructor |
| */ |
| var NdnCommon = {}; |
| |
| exports.NdnCommon = NdnCommon; |
| |
| /** |
| * The practical limit of the size of a network-layer packet. If a packet is |
| * larger than this, the library or application MAY drop it. This constant is |
| * defined in this low-level class so that internal code can use it, but |
| * applications should use the static API method |
| * Face.getMaxNdnPacketSize() which is equivalent. |
| */ |
| NdnCommon.MAX_NDN_PACKET_SIZE = 8800; |
| |
| /** |
| * Get the error message plus its stack trace. |
| * @param {Error} error The error object. |
| * @return {string} The error message, plus the stack trace with each line |
| * separated by '\n'. |
| */ |
| NdnCommon.getErrorWithStackTrace = function(error) |
| { |
| return error + '\n' + printStackTrace({e: error}).join('\n'); |
| }; |
| |
| /** |
| * Check for Indexed DB support and call onComplete with the result as described |
| * below. This has to use an onComplete callback since IndexedDB is async. |
| * @param {function} onComplete This calls onComplete(haveIndexedDb) where |
| * haveIndexedDb is true if the browser has Indexed DB support, otherwise false. |
| */ |
| NdnCommon.checkIndexedDb = function(onComplete) |
| { |
| try { |
| var database = new Dexie("test-Dexie-support"); |
| database.version(1).stores({}); |
| database.open(); |
| |
| // Give Dexie a little time to open. |
| setTimeout(function() { |
| try { |
| onComplete(database.isOpen()); |
| } catch (ex) { |
| onComplete(false); |
| } |
| }, 200); |
| } catch (ex) { |
| onComplete(false); |
| } |
| }; |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var NdnCommon = require('./ndn-common.js').NdnCommon; |
| |
| /** |
| * An ExponentialReExpress uses an internal onTimeout to express the interest again |
| * with double the interestLifetime. See ExponentialReExpress.makeOnTimeout, |
| * which you should call instead of the private constructor. |
| * Create a new ExponentialReExpress where onTimeout expresses the interest |
| * again with double the interestLifetime. If the interesLifetime goes |
| * over settings.maxInterestLifetime, then call the given onTimeout. If this |
| * internally gets onData, just call the given onData. |
| * @constructor |
| */ |
| var ExponentialReExpress = function ExponentialReExpress |
| (face, onData, onTimeout, settings) |
| { |
| settings = (settings || {}); |
| this.face = face; |
| this.callerOnData = onData; |
| this.callerOnTimeout = onTimeout; |
| |
| this.maxInterestLifetime = (settings.maxInterestLifetime || 16000); |
| }; |
| |
| exports.ExponentialReExpress = ExponentialReExpress; |
| |
| /** |
| * Return a callback to use in expressInterest for onTimeout which will express |
| * the interest again with double the interestLifetime. If the interesLifetime |
| * goes over maxInterestLifetime (see settings below), then call the provided |
| * onTimeout. If a Data packet is received, this calls the provided onData. |
| * Use it like this: |
| * var onData = function() { ... }; |
| * var onTimeout = function() { ... }; |
| * face.expressInterest |
| * (interest, onData, |
| * ExponentialReExpress.makeOnTimeout(face, onData, onTimeout)); |
| * @param {Face} face This calls face.expressInterest. |
| * @param {function} onData When a matching data packet is received, this calls |
| * onData(interest, data) where interest is the interest given to |
| * expressInterest and data is the received Data object. This is normally the |
| * same onData you initially passed to expressInterest. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onTimeout If the interesLifetime goes over |
| * maxInterestLifetime, this calls onTimeout(interest). However, if onTimeout is |
| * null, this does not use it. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {Object} settings (optional) If not null, an associative array with |
| * the following defaults: |
| * { |
| * maxInterestLifetime: 16000 // milliseconds |
| * } |
| * @return {function} The onTimeout callback to pass to expressInterest. |
| */ |
| ExponentialReExpress.makeOnTimeout = function(face, onData, onTimeout, settings) |
| { |
| var reExpress = new ExponentialReExpress(face, onData, onTimeout, settings); |
| return function(interest) { reExpress.onTimeout(interest); }; |
| }; |
| |
| ExponentialReExpress.prototype.onTimeout = function(interest) |
| { |
| var interestLifetime = interest.getInterestLifetimeMilliseconds(); |
| if (interestLifetime == null) { |
| // Can't re-express. |
| if (this.callerOnTimeout) { |
| try { |
| this.callerOnTimeout(interest); |
| } catch (ex) { |
| console.log("Error in onTimeout: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| return; |
| } |
| |
| var nextInterestLifetime = interestLifetime * 2; |
| if (nextInterestLifetime > this.maxInterestLifetime) { |
| if (this.callerOnTimeout) { |
| try { |
| this.callerOnTimeout(interest); |
| } catch (ex) { |
| console.log("Error in onTimeout: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| return; |
| } |
| |
| var nextInterest = interest.clone(); |
| nextInterest.setInterestLifetimeMilliseconds(nextInterestLifetime); |
| var thisObject = this; |
| this.face.expressInterest |
| (nextInterest, this.callerOnData, |
| function(localInterest) { thisObject.onTimeout(localInterest); }); |
| }; |
| /** |
| * Copyright (C) 2013 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * A Blob holds an immutable byte array implemented as a Buffer. This should be |
| * treated like a string which is a pointer to an immutable string. (It is OK to |
| * pass a pointer to the string because the new owner can’t change the bytes of |
| * the string.) Blob does not inherit from Buffer. Instead you must call buf() |
| * to get the byte array which reminds you that you should not change the |
| * contents. Also remember that buf() can return null. |
| * @param {Blob|Buffer|Array<number>} value (optional) If value is a Blob, take |
| * another pointer to the Buffer without copying. If value is a Buffer or byte |
| * array, copy to create a new Buffer. If omitted, buf() will return null. |
| * @param {boolean} copy (optional) If true, copy the contents of |
| * value into a new Buffer. If false, just use the existing value without |
| * copying. If omitted, then copy the contents (unless value is already a Blob). |
| * IMPORTANT: If copy is false, if you keep a pointer to the value then you must |
| * treat the value as immutable and promise not to change it. |
| * @constructor |
| */ |
| var Blob = function Blob(value, copy) |
| { |
| if (copy == null) |
| copy = true; |
| |
| if (value == null) |
| this.buffer = null; |
| else if (typeof value === 'object' && value instanceof Blob) |
| // Use the existing buffer. Don't need to check for copy. |
| this.buffer = value.buffer; |
| else { |
| if (typeof value === 'string') |
| // Convert from a string to utf-8 byte encoding. |
| this.buffer = new Buffer(value, 'utf8'); |
| else { |
| if (copy) |
| // We are copying, so just make another Buffer. |
| this.buffer = new Buffer(value); |
| else { |
| if (Buffer.isBuffer(value)) |
| // We can use as-is. |
| this.buffer = value; |
| else |
| // We need a Buffer, so copy. |
| this.buffer = new Buffer(value); |
| } |
| } |
| } |
| |
| // Set the length to be "JavaScript-like". |
| this.length = this.buffer != null ? this.buffer.length : 0; |
| }; |
| |
| exports.Blob = Blob; |
| |
| /** |
| * Return the length of the immutable byte array. |
| * @return {number} The length of the array. If buf() is null, return 0. |
| */ |
| Blob.prototype.size = function() |
| { |
| if (this.buffer != null) |
| return this.buffer.length; |
| else |
| return 0; |
| }; |
| |
| /** |
| * Return the immutable byte array. DO NOT change the contents of the Buffer. |
| * If you need to change it, make a copy. |
| * @return {Buffer} The Buffer holding the immutable byte array, or null. |
| */ |
| Blob.prototype.buf = function() |
| { |
| return this.buffer; |
| }; |
| |
| /** |
| * Return true if the array is null, otherwise false. |
| * @return {boolean} True if the array is null. |
| */ |
| Blob.prototype.isNull = function() |
| { |
| return this.buffer == null; |
| }; |
| |
| /** |
| * Return the hex representation of the bytes in the byte array. |
| * @return {string} The hex string. |
| */ |
| Blob.prototype.toHex = function() |
| { |
| if (this.buffer == null) |
| return ""; |
| else |
| return this.buffer.toString('hex'); |
| }; |
| |
| /** |
| * Decode the byte array as UTF8 and return the Unicode string. |
| * @return A unicode string, or "" if the buffer is null. |
| */ |
| Blob.prototype.toString = function() |
| { |
| if (this.buffer == null) |
| return ""; |
| else |
| return this.buffer.toString('utf8'); |
| }; |
| |
| /** |
| * Check if the value of this Blob equals the other blob. |
| * @param {Blob} other The other Blob to check. |
| * @return {boolean} if this isNull and other isNull or if the bytes of this |
| * blob equal the bytes of the other. |
| */ |
| Blob.prototype.equals = function(other) |
| { |
| if (this.isNull()) |
| return other.isNull(); |
| else if (other.isNull()) |
| return false; |
| else { |
| if (this.buffer.length != other.buffer.length) |
| return false; |
| |
| for (var i = 0; i < this.buffer.length; ++i) { |
| if (this.buffer[i] != other.buffer[i]) |
| return false; |
| } |
| |
| return true; |
| } |
| }; |
| /** |
| * Copyright (C) 2013 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Blob = require('./blob.js').Blob; |
| |
| /** |
| * A SignedBlob extends Blob to keep the offsets of a signed portion (e.g., the |
| * bytes of Data packet). This inherits from Blob, including Blob.size and Blob.buf. |
| * @param {Blob|Buffer|Array<number>} value (optional) If value is a Blob, take |
| * another pointer to the Buffer without copying. If value is a Buffer or byte |
| * array, copy to create a new Buffer. If omitted, buf() will return null. |
| * @param {number} signedPortionBeginOffset (optional) The offset in the |
| * encoding of the beginning of the signed portion. If omitted, set to 0. |
| * @param {number} signedPortionEndOffset (optional) The offset in the encoding |
| * of the end of the signed portion. If omitted, set to 0. |
| * @constructor |
| */ |
| var SignedBlob = function SignedBlob(value, signedPortionBeginOffset, signedPortionEndOffset) |
| { |
| // Call the base constructor. |
| Blob.call(this, value); |
| |
| if (this.buffer == null) { |
| this.signedPortionBeginOffset = 0; |
| this.signedPortionEndOffset = 0; |
| } |
| else if (typeof value === 'object' && value instanceof SignedBlob) { |
| // Copy the SignedBlob, allowing override for offsets. |
| this.signedPortionBeginOffset = signedPortionBeginOffset == null ? |
| value.signedPortionBeginOffset : signedPortionBeginOffset; |
| this.signedPortionEndOffset = signedPortionEndOffset == null ? |
| value.signedPortionEndOffset : signedPortionEndOffset; |
| } |
| else { |
| this.signedPortionBeginOffset = signedPortionBeginOffset || 0; |
| this.signedPortionEndOffset = signedPortionEndOffset || 0; |
| } |
| |
| if (this.buffer == null) |
| this.signedBuffer = null; |
| else |
| this.signedBuffer = this.buffer.slice |
| (this.signedPortionBeginOffset, this.signedPortionEndOffset); |
| }; |
| |
| SignedBlob.prototype = new Blob(); |
| SignedBlob.prototype.name = "SignedBlob"; |
| |
| exports.SignedBlob = SignedBlob; |
| |
| /** |
| * Return the length of the signed portion of the immutable byte array. |
| * @return {number} The length of the signed portion. If signedBuf() is null, |
| * return 0. |
| */ |
| SignedBlob.prototype.signedSize = function() |
| { |
| if (this.signedBuffer != null) |
| return this.signedBuffer.length; |
| else |
| return 0; |
| }; |
| |
| /** |
| * Return a the signed portion of the immutable byte array. |
| * @return {Buffer} A slice into the Buffer which is the signed portion. |
| * If the pointer to the array is null, return null. |
| */ |
| SignedBlob.prototype.signedBuf = function() { return this.signedBuffer; }; |
| |
| /** |
| * Return the offset in the array of the beginning of the signed portion. |
| * @return {number} The offset in the array. |
| */ |
| SignedBlob.prototype.getSignedPortionBeginOffset = function() |
| { |
| return this.signedPortionBeginOffset; |
| }; |
| |
| /** |
| * Return the offset in the array of the end of the signed portion. |
| * @return {number} The offset in the array. |
| */ |
| SignedBlob.prototype.getSignedPortionEndOffset = function() |
| { |
| return this.signedPortionEndOffset; |
| }; |
| /** |
| * Encapsulate a Buffer and support dynamic reallocation. |
| * Copyright (C) 2013-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * Create a DynamicBuffer where this.array is a Buffer of size length. |
| * To access the array, use this.array or call slice. |
| * @constructor |
| * @param {number} length the initial length of the array. If null, use a default. |
| */ |
| var DynamicBuffer = function DynamicBuffer(length) |
| { |
| if (!length) |
| length = 16; |
| |
| this.array = new Buffer(length); |
| }; |
| |
| exports.DynamicBuffer = DynamicBuffer; |
| |
| /** |
| * Ensure that this.array has the length, reallocate and copy if necessary. |
| * Update the length of this.array which may be greater than length. |
| * @param {number} length The minimum length for the array. |
| */ |
| DynamicBuffer.prototype.ensureLength = function(length) |
| { |
| if (this.array.length >= length) |
| return; |
| |
| // See if double is enough. |
| var newLength = this.array.length * 2; |
| if (length > newLength) |
| // The needed length is much greater, so use it. |
| newLength = length; |
| |
| var newArray = new Buffer(newLength); |
| this.array.copy(newArray); |
| this.array = newArray; |
| }; |
| |
| /** |
| * Copy the value to this.array at offset, reallocating if necessary. |
| * @param {Buffer} value The buffer to copy. |
| * @param {number} offset The offset in the buffer to start copying into. |
| * @return {number} The new offset which is offset + value.length. |
| */ |
| DynamicBuffer.prototype.copy = function(value, offset) |
| { |
| this.ensureLength(value.length + offset); |
| |
| if (Buffer.isBuffer(value)) |
| value.copy(this.array, offset); |
| else |
| // Need to make value a Buffer to copy. |
| new Buffer(value).copy(this.array, offset); |
| |
| return offset + value.length; |
| }; |
| |
| /** |
| * Ensure that this.array has the length. If necessary, reallocate the array |
| * and shift existing data to the back of the new array. |
| * Update the length of this.array which may be greater than length. |
| * @param {number} length The minimum length for the array. |
| */ |
| DynamicBuffer.prototype.ensureLengthFromBack = function(length) |
| { |
| if (this.array.length >= length) |
| return; |
| |
| // See if double is enough. |
| var newLength = this.array.length * 2; |
| if (length > newLength) |
| // The needed length is much greater, so use it. |
| newLength = length; |
| |
| var newArray = new Buffer(newLength); |
| // Copy to the back of newArray. |
| this.array.copy(newArray, newArray.length - this.array.length); |
| this.array = newArray; |
| }; |
| |
| /** |
| * First call ensureLengthFromBack to make sure the bytearray has |
| * offsetFromBack bytes, then copy value into the array starting |
| * offsetFromBack bytes from the back of the array. |
| * @param {Buffer} value The buffer to copy. |
| * @param {number} offsetFromBack The offset from the back of the array to start |
| * copying. |
| */ |
| DynamicBuffer.prototype.copyFromBack = function(value, offsetFromBack) |
| { |
| this.ensureLengthFromBack(offsetFromBack); |
| |
| if (Buffer.isBuffer(value)) |
| value.copy(this.array, this.array.length - offsetFromBack); |
| else |
| // Need to make value a Buffer to copy. |
| new Buffer(value).copy(this.array, this.array.length - offsetFromBack); |
| }; |
| |
| /** |
| * Return this.array.slice(begin, end); |
| * @param {number} begin The begin index for the slice. |
| * @param {number} end (optional) The end index for the slice. |
| * @return {Buffer} The buffer slice. |
| */ |
| DynamicBuffer.prototype.slice = function(begin, end) |
| { |
| if (end == undefined) |
| return this.array.slice(begin); |
| else |
| return this.array.slice(begin, end); |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * A ChangeCounter keeps a target object whose change count is tracked by a |
| * local change count. You can set to a new target which updates the local |
| * change count, and you can call checkChanged to check if the target (or one of |
| * the target's targets) has been changed. The target object must have a method |
| * getChangeCount. |
| * |
| * Create a new ChangeCounter to track the given target. If target is not null, |
| * this sets the local change counter to target.getChangeCount(). |
| * @param {object} target The target to track, as an object with the method |
| * getChangeCount(). |
| * @constructor |
| */ |
| var ChangeCounter = function ChangeCounter(target) |
| { |
| this.target = target; |
| this.changeCount = (target == null ? 0 : target.getChangeCount()); |
| }; |
| |
| exports.ChangeCounter = ChangeCounter; |
| |
| /** |
| * Get the target object. If the target is changed, then checkChanged will |
| * detect it. |
| * @return {object} The target, as an object with the method |
| * getChangeCount(). |
| */ |
| ChangeCounter.prototype.get = function() |
| { |
| return this.target; |
| }; |
| |
| /** |
| * Set the target to the given target. If target is not null, this sets the |
| * local change counter to target.getChangeCount(). |
| * @param {object} target The target to track, as an object with the method |
| * getChangeCount(). |
| */ |
| ChangeCounter.prototype.set = function(target) |
| { |
| this.target = target; |
| this.changeCount = (target == null ? 0 : target.getChangeCount()); |
| }; |
| |
| /** |
| * If the target's change count is different than the local change count, then |
| * update the local change count and return true. Otherwise return false, |
| * meaning that the target has not changed. Also, if the target is null, |
| * simply return false. This is useful since the target (or one of the target's |
| * targets) may be changed and you need to find out. |
| * @return {boolean} True if the change count has been updated, false if not. |
| */ |
| ChangeCounter.prototype.checkChanged = function() |
| { |
| if (this.target == null) |
| return false; |
| |
| var targetChangeCount = this.target.getChangeCount(); |
| if (this.changeCount != targetChangeCount) { |
| this.changeCount = targetChangeCount; |
| return true; |
| } |
| else |
| return false; |
| }; |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var NdnCommon = require('./ndn-common.js').NdnCommon; |
| |
| /** |
| * A SyncPromise is a promise which is immediately fulfilled or rejected, used |
| * to return a promise in synchronous code. |
| * This private constructor creates a SyncPromise fulfilled or rejected with the |
| * given value. You should normally not call this constructor but call |
| * SyncPromise.resolve or SyncPromise.reject. Note that we don't need a |
| * constructor like SyncPromise(function(resolve, reject)) because this would be |
| * for scheduling the function to be called later, which we don't do. |
| * @param {any} value If isRejected is false, this is the value of the fulfilled |
| * promise, else if isRejected is true, this is the error. |
| * @param {boolean} isRejected True to create a promise in the rejected state, |
| * where value is the error. |
| * @constructor |
| */ |
| var SyncPromise = function SyncPromise(value, isRejected) |
| { |
| this.value = value; |
| this.isRejected = isRejected; |
| }; |
| |
| exports.SyncPromise = SyncPromise; |
| |
| /** |
| * If this promise is fulfilled, immediately call onFulfilled with the fulfilled |
| * value as described below. Otherwise, if this promise is rejected, immediately |
| * call onRejected with the error as described below. |
| * @param {function} (optional) onFulfilled If this promise is fulfilled, this |
| * calls onFulfilled(value) with the value of this promise and returns the |
| * result. The function should return a promise. To use all synchronous code, |
| * onFulfilled should return SyncPromise.resolve(newValue). |
| * @param {function} (optional) onRejected If this promise is rejected, this |
| * calls onRejected(err) with the error value of this promise and returns the |
| * result. The function should return a promise. To use all synchronous code, |
| * onFulfilled should return SyncPromise.resolve(newValue) (or throw an |
| * exception). |
| * @return {Promise|SyncPromise} If this promise is fulfilled, return the result |
| * of calling onFulfilled(value). Note that this does not create a promise which |
| * is scheduled to execute later. Rather it immediately calls onFulfilled which |
| * should return a promise. But if onFulfilled is undefined, simply return this |
| * promise to pass it forward. If this promise is rejected, return the result of |
| * calling onRejected(err) with the error value. But if onRejected is undefined, |
| * simply return this promise to pass it forward. However, if onFulfilled or |
| * onRejected throws an exception, then return a new SyncPromise in the rejected |
| * state with the exception. |
| */ |
| SyncPromise.prototype.then = function(onFulfilled, onRejected) |
| { |
| if (this.isRejected) { |
| if (onRejected) { |
| try { |
| return onRejected(this.value); |
| } |
| catch(err) { |
| return new SyncPromise(err, true); |
| } |
| } |
| else |
| // Pass the error forward. |
| return this; |
| } |
| else { |
| if (onFulfilled) { |
| try { |
| return onFulfilled(this.value); |
| } |
| catch(err) { |
| return new SyncPromise(err, true); |
| } |
| } |
| else |
| // Pass the fulfilled value forward. |
| return this; |
| } |
| }; |
| |
| /** |
| * Call this.then(undefined, onRejected) and return the result. If this promise |
| * is rejected then onRejected will process it. If this promise is fulfilled, |
| * this simply passes it forward. |
| */ |
| SyncPromise.prototype.catch = function(onRejected) |
| { |
| return this.then(undefined, onRejected); |
| }; |
| |
| /** |
| * Return a new SyncPromise which is already fulfilled to the given value. |
| * @param {any} value The value of the promise. |
| */ |
| SyncPromise.resolve = function(value) |
| { |
| return new SyncPromise(value, false); |
| }; |
| |
| /** |
| * Return a new SyncPromise which is already rejected with the given error. |
| * @param {any} err The error for the rejected promise. |
| */ |
| SyncPromise.reject = function(err) |
| { |
| return new SyncPromise(err, true); |
| }; |
| |
| /** |
| * This static method checks if the promise is a SyncPromise and immediately |
| * returns its value or throws the error if promise is rejected. If promise is |
| * not a SyncPromise, this throws an exception since it is not possible to |
| * immediately get the value. This can be used with "promise-based" code which |
| * you expect to always return a SyncPromise to operate in synchronous mode. |
| * @param {SyncPromise} promise The SyncPromise with the value to get. |
| * @return {any} The value of the promise. |
| * @throws Error If promise is not a SyncPromise. |
| * @throws any If promise is a SyncPromise in the rejected state, this throws |
| * the error. |
| */ |
| SyncPromise.getValue = function(promise) |
| { |
| if (promise instanceof SyncPromise) { |
| if (promise.isRejected) |
| throw promise.value; |
| else |
| return promise.value; |
| } |
| else |
| throw new Error("Cannot return immediately because promise is not a SyncPromise"); |
| }; |
| |
| /** |
| * This can be called with complete(onComplete, promise) or |
| * complete(onComplete, onError, promise) to handle both synchronous and |
| * asynchronous code based on whether the caller supplies the onComlete callback. |
| * If onComplete is defined, call promise.then with a function which calls |
| * onComplete(value) when fulfilled (possibly in asynchronous mode). If |
| * onComplete is undefined, then we are in synchronous mode so return |
| * SyncPromise.getValue(promise) which will throw an exception if the promise is |
| * not a SyncPromise (or is a SyncPromise in the rejected state). |
| * @param {function} onComplete If defined, this calls promise.then to fulfill |
| * the promise, then calls onComplete(value) with the value of the promise. |
| * If onComplete is undefined, the return value is described below. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an error when this calls promise.then, this calls |
| * onError(err) with the value of the error. If onComplete is undefined, then |
| * onError is ignored and this will call SyncPromise.getValue(promise) which may |
| * throw an exception. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {Promise|SyncPromise} promise If onComplete is defined, this calls |
| * promise.then. Otherwise, this calls SyncPromise.getValue(promise). |
| * @return {any} If onComplete is undefined, return SyncPromise.getValue(promise). |
| * Otherwise, if onComplete is supplied then return undefined and use |
| * onComplete as described above. |
| * @throws Error If onComplete is undefined and promise is not a SyncPromise. |
| * @throws any If onComplete is undefined and promise is a SyncPromise in the |
| * rejected state. |
| */ |
| SyncPromise.complete = function(onComplete, onErrorOrPromise, promise) |
| { |
| var onError; |
| if (promise) |
| onError = onErrorOrPromise; |
| else { |
| promise = onErrorOrPromise; |
| onError = null; |
| } |
| |
| if (onComplete) |
| promise |
| .then(function(value) { |
| try { |
| onComplete(value); |
| } catch (ex) { |
| console.log("Error in onComplete: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| }, function(err) { |
| if (onError) { |
| try { |
| onError(err); |
| } catch (ex) { |
| console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| else { |
| if (promise instanceof SyncPromise) |
| throw err; |
| else |
| // We are in an async promise callback, so a thrown exception won't |
| // reach the caller. Just log it. |
| console.log("Uncaught exception from a Promise: " + |
| NdnCommon.getErrorWithStackTrace(err)); |
| } |
| }); |
| else |
| return SyncPromise.getValue(promise); |
| }; |
| /** |
| * This class contains utilities to help parse the data |
| * |
| * Copyright (C) 2013-2016 Regents of the University of California. |
| * @author: Meki Cheraoui |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * A DataUtils has static methods for converting data. |
| * @constructor |
| */ |
| var DataUtils = {}; |
| |
| exports.DataUtils = DataUtils; |
| |
| /* |
| * NOTE THIS IS CURRENTLY NOT BEING USED |
| * |
| */ |
| |
| DataUtils.keyStr = "ABCDEFGHIJKLMNOP" + |
| "QRSTUVWXYZabcdef" + |
| "ghijklmnopqrstuv" + |
| "wxyz0123456789+/" + |
| "="; |
| |
| /** |
| * Raw String to Base 64 |
| */ |
| DataUtils.stringtoBase64 = function stringtoBase64(input) |
| { |
| //input = escape(input); |
| var output = ""; |
| var chr1, chr2, chr3 = ""; |
| var enc1, enc2, enc3, enc4 = ""; |
| var i = 0; |
| |
| do { |
| chr1 = input.charCodeAt(i++); |
| chr2 = input.charCodeAt(i++); |
| chr3 = input.charCodeAt(i++); |
| |
| enc1 = chr1 >> 2; |
| enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); |
| enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); |
| enc4 = chr3 & 63; |
| |
| if (isNaN(chr2)) |
| enc3 = enc4 = 64; |
| else if (isNaN(chr3)) |
| enc4 = 64; |
| |
| output = output + |
| DataUtils.keyStr.charAt(enc1) + |
| DataUtils.keyStr.charAt(enc2) + |
| DataUtils.keyStr.charAt(enc3) + |
| DataUtils.keyStr.charAt(enc4); |
| chr1 = chr2 = chr3 = ""; |
| enc1 = enc2 = enc3 = enc4 = ""; |
| } while (i < input.length); |
| |
| return output; |
| }; |
| |
| /** |
| * Base 64 to Raw String |
| */ |
| DataUtils.base64toString = function base64toString(input) |
| { |
| var output = ""; |
| var chr1, chr2, chr3 = ""; |
| var enc1, enc2, enc3, enc4 = ""; |
| var i = 0; |
| |
| // remove all characters that are not A-Z, a-z, 0-9, +, /, or = |
| var base64test = /[^A-Za-z0-9\+\/\=]/g; |
| /* Test for invalid characters. */ |
| if (base64test.exec(input)) { |
| alert("There were invalid base64 characters in the input text.\n" + |
| "Valid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\n" + |
| "Expect errors in decoding."); |
| } |
| |
| input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); |
| |
| do { |
| enc1 = DataUtils.keyStr.indexOf(input.charAt(i++)); |
| enc2 = DataUtils.keyStr.indexOf(input.charAt(i++)); |
| enc3 = DataUtils.keyStr.indexOf(input.charAt(i++)); |
| enc4 = DataUtils.keyStr.indexOf(input.charAt(i++)); |
| |
| chr1 = (enc1 << 2) | (enc2 >> 4); |
| chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); |
| chr3 = ((enc3 & 3) << 6) | enc4; |
| |
| output = output + String.fromCharCode(chr1); |
| |
| if (enc3 != 64) |
| output = output + String.fromCharCode(chr2); |
| |
| if (enc4 != 64) |
| output = output + String.fromCharCode(chr3); |
| |
| chr1 = chr2 = chr3 = ""; |
| enc1 = enc2 = enc3 = enc4 = ""; |
| } while (i < input.length); |
| |
| return output; |
| }; |
| |
| /** |
| * Buffer to Hex String |
| */ |
| DataUtils.toHex = function(buffer) |
| { |
| return buffer.toString('hex'); |
| }; |
| |
| /** |
| * Raw string to hex string. |
| */ |
| DataUtils.stringToHex = function(args) |
| { |
| var ret = ""; |
| for (var i = 0; i < args.length; ++i) { |
| var value = args.charCodeAt(i); |
| ret += (value < 16 ? "0" : "") + value.toString(16); |
| } |
| return ret; |
| }; |
| |
| /** |
| * Buffer to raw string. |
| */ |
| DataUtils.toString = function(buffer) |
| { |
| return buffer.toString('binary'); |
| }; |
| |
| /** |
| * Hex String to Buffer. |
| */ |
| DataUtils.toNumbers = function(str) |
| { |
| return new Buffer(str, 'hex'); |
| }; |
| |
| /** |
| * Hex String to raw string. |
| */ |
| DataUtils.hexToRawString = function(str) |
| { |
| if (typeof str =='string') { |
| var ret = ""; |
| str.replace(/(..)/g, function(s) { |
| ret += String.fromCharCode(parseInt(s, 16)); |
| }); |
| return ret; |
| } |
| }; |
| |
| /** |
| * Raw String to Buffer. |
| */ |
| DataUtils.toNumbersFromString = function(str) |
| { |
| return new Buffer(str, 'binary'); |
| }; |
| |
| /** |
| * If value is a string, then interpret it as a raw string and convert to |
| * a Buffer. Otherwise assume it is a Buffer or array type and just return it. |
| * @param {string|any} value |
| * @return {Buffer} |
| */ |
| DataUtils.toNumbersIfString = function(value) |
| { |
| if (typeof value === 'string') |
| return new Buffer(value, 'binary'); |
| else |
| return value; |
| }; |
| |
| /** |
| * Encode str as utf8 and return as Buffer. |
| */ |
| DataUtils.stringToUtf8Array = function(str) |
| { |
| return new Buffer(str, 'utf8'); |
| }; |
| |
| /** |
| * arrays is an array of Buffer. Return a new Buffer which is the concatenation of all. |
| */ |
| DataUtils.concatArrays = function(arrays) |
| { |
| return Buffer.concat(arrays); |
| }; |
| |
| // TODO: Take Buffer and use TextDecoder when available. |
| DataUtils.decodeUtf8 = function(utftext) |
| { |
| var string = ""; |
| var i = 0; |
| var c = 0; |
| var c1 = 0; |
| var c2 = 0; |
| |
| while (i < utftext.length) { |
| c = utftext.charCodeAt(i); |
| |
| if (c < 128) { |
| string += String.fromCharCode(c); |
| i++; |
| } |
| else if (c > 191 && c < 224) { |
| c2 = utftext.charCodeAt(i + 1); |
| string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); |
| i += 2; |
| } |
| else { |
| c2 = utftext.charCodeAt(i+1); |
| var c3 = utftext.charCodeAt(i+2); |
| string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); |
| i += 3; |
| } |
| } |
| |
| return string; |
| }; |
| |
| /** |
| * Return true if a1 and a2 are the same length with equal elements. |
| */ |
| DataUtils.arraysEqual = function(a1, a2) |
| { |
| // A simple sanity check that it is an array. |
| if (!a1.slice) |
| throw new Error("DataUtils.arraysEqual: a1 is not an array"); |
| if (!a2.slice) |
| throw new Error("DataUtils.arraysEqual: a2 is not an array"); |
| |
| if (a1.length != a2.length) |
| return false; |
| |
| for (var i = 0; i < a1.length; ++i) { |
| if (a1[i] != a2[i]) |
| return false; |
| } |
| |
| return true; |
| }; |
| |
| /** |
| * Convert the big endian Buffer to an unsigned int. |
| * Don't check for overflow. |
| */ |
| DataUtils.bigEndianToUnsignedInt = function(bytes) |
| { |
| var result = 0; |
| for (var i = 0; i < bytes.length; ++i) { |
| // Multiply by 0x100 instead of shift by 8 because << is restricted to 32 bits. |
| result *= 0x100; |
| result += bytes[i]; |
| } |
| return result; |
| }; |
| |
| /** |
| * Convert the int value to a new big endian Buffer and return. |
| * If value is 0 or negative, return new Buffer(0). |
| */ |
| DataUtils.nonNegativeIntToBigEndian = function(value) |
| { |
| value = Math.round(value); |
| if (value <= 0) |
| return new Buffer(0); |
| |
| // Assume value is not over 64 bits. |
| var size = 8; |
| var result = new Buffer(size); |
| var i = 0; |
| while (value != 0) { |
| ++i; |
| result[size - i] = value & 0xff; |
| // Divide by 0x100 and floor instead of shift by 8 because >> is restricted to 32 bits. |
| value = Math.floor(value / 0x100); |
| } |
| return result.slice(size - i, size); |
| }; |
| |
| /** |
| * Modify array to randomly shuffle the elements. |
| */ |
| DataUtils.shuffle = function(array) |
| { |
| for (var i = array.length - 1; i >= 1; --i) { |
| // j is from 0 to i. |
| var j = Math.floor(Math.random() * (i + 1)); |
| var temp = array[i]; |
| array[i] = array[j]; |
| array[j] = temp; |
| } |
| }; |
| |
| /** |
| * Decode the base64-encoded private key PEM and return the binary DER. |
| * @param {string} The PEM-encoded private key. |
| * @return {Buffer} The binary DER. |
| * |
| */ |
| DataUtils.privateKeyPemToDer = function(privateKeyPem) |
| { |
| // Remove the '-----XXX-----' from the beginning and the end of the key and |
| // also remove any \n in the key string. |
| var lines = privateKeyPem.split('\n'); |
| var privateKey = ""; |
| for (var i = 1; i < lines.length - 1; i++) |
| privateKey += lines[i]; |
| |
| return new Buffer(privateKey, 'base64'); |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * Create a new DecodingException wrapping the given error object. |
| * Call with: throw new DecodingException(new Error("message")). |
| * @constructor |
| * @param {Error} error The exception created with new Error. |
| */ |
| function DecodingException(error) |
| { |
| if (error) { |
| error.__proto__ = DecodingException.prototype; |
| return error; |
| } |
| } |
| DecodingException.prototype = new Error(); |
| DecodingException.prototype.name = "DecodingException"; |
| |
| exports.DecodingException = DecodingException; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * The Tlv class has static type codes for the NDN-TLV wire format. |
| * @constructor |
| */ |
| var Tlv = function Tlv() |
| { |
| }; |
| |
| exports.Tlv = Tlv; |
| |
| Tlv.Interest = 5; |
| Tlv.Data = 6; |
| Tlv.Name = 7; |
| Tlv.ImplicitSha256DigestComponent = 1; |
| Tlv.NameComponent = 8; |
| Tlv.Selectors = 9; |
| Tlv.Nonce = 10; |
| // <Unassigned> = 11; |
| Tlv.InterestLifetime = 12; |
| Tlv.MinSuffixComponents = 13; |
| Tlv.MaxSuffixComponents = 14; |
| Tlv.PublisherPublicKeyLocator = 15; |
| Tlv.Exclude = 16; |
| Tlv.ChildSelector = 17; |
| Tlv.MustBeFresh = 18; |
| Tlv.Any = 19; |
| Tlv.MetaInfo = 20; |
| Tlv.Content = 21; |
| Tlv.SignatureInfo = 22; |
| Tlv.SignatureValue = 23; |
| Tlv.ContentType = 24; |
| Tlv.FreshnessPeriod = 25; |
| Tlv.FinalBlockId = 26; |
| Tlv.SignatureType = 27; |
| Tlv.KeyLocator = 28; |
| Tlv.KeyLocatorDigest = 29; |
| Tlv.SelectedDelegation = 32; |
| Tlv.FaceInstance = 128; |
| Tlv.ForwardingEntry = 129; |
| Tlv.StatusResponse = 130; |
| Tlv.Action = 131; |
| Tlv.FaceID = 132; |
| Tlv.IPProto = 133; |
| Tlv.Host = 134; |
| Tlv.Port = 135; |
| Tlv.MulticastInterface = 136; |
| Tlv.MulticastTTL = 137; |
| Tlv.ForwardingFlags = 138; |
| Tlv.StatusCode = 139; |
| Tlv.StatusText = 140; |
| |
| Tlv.SignatureType_DigestSha256 = 0; |
| Tlv.SignatureType_SignatureSha256WithRsa = 1; |
| Tlv.SignatureType_SignatureSha256WithEcdsa = 3; |
| Tlv.SignatureType_SignatureHmacWithSha256 = 4; |
| |
| Tlv.ContentType_Default = 0; |
| Tlv.ContentType_Link = 1; |
| Tlv.ContentType_Key = 2; |
| |
| Tlv.NfdCommand_ControlResponse = 101; |
| Tlv.NfdCommand_StatusCode = 102; |
| Tlv.NfdCommand_StatusText = 103; |
| |
| Tlv.ControlParameters_ControlParameters = 104; |
| Tlv.ControlParameters_FaceId = 105; |
| Tlv.ControlParameters_Uri = 114; |
| Tlv.ControlParameters_LocalControlFeature = 110; |
| Tlv.ControlParameters_Origin = 111; |
| Tlv.ControlParameters_Cost = 106; |
| Tlv.ControlParameters_Flags = 108; |
| Tlv.ControlParameters_Strategy = 107; |
| Tlv.ControlParameters_ExpirationPeriod = 109; |
| |
| Tlv.LpPacket_LpPacket = 100; |
| Tlv.LpPacket_Fragment = 80; |
| Tlv.LpPacket_Sequence = 81; |
| Tlv.LpPacket_FragIndex = 82; |
| Tlv.LpPacket_FragCount = 83; |
| Tlv.LpPacket_Nack = 800; |
| Tlv.LpPacket_NackReason = 801; |
| Tlv.LpPacket_NextHopFaceId = 816; |
| Tlv.LpPacket_IncomingFaceId = 817; |
| Tlv.LpPacket_CachePolicy = 820; |
| Tlv.LpPacket_CachePolicyType = 821; |
| Tlv.LpPacket_IGNORE_MIN = 800; |
| Tlv.LpPacket_IGNORE_MAX = 959; |
| |
| Tlv.Link_Preference = 30; |
| Tlv.Link_Delegation = 31; |
| |
| Tlv.Encrypt_EncryptedContent = 130; |
| Tlv.Encrypt_EncryptionAlgorithm = 131; |
| Tlv.Encrypt_EncryptedPayload = 132; |
| Tlv.Encrypt_InitialVector = 133; |
| |
| // For RepetitiveInterval. |
| Tlv.Encrypt_StartDate = 134; |
| Tlv.Encrypt_EndDate = 135; |
| Tlv.Encrypt_IntervalStartHour = 136; |
| Tlv.Encrypt_IntervalEndHour = 137; |
| Tlv.Encrypt_NRepeats = 138; |
| Tlv.Encrypt_RepeatUnit = 139; |
| Tlv.Encrypt_RepetitiveInterval = 140; |
| |
| // For Schedule. |
| Tlv.Encrypt_WhiteIntervalList = 141; |
| Tlv.Encrypt_BlackIntervalList = 142; |
| Tlv.Encrypt_Schedule = 143; |
| |
| /** |
| * Strip off the lower 32 bits of x and divide by 2^32, returning the "high |
| * bytes" above 32 bits. This is necessary because JavaScript << and >> are |
| * restricted to 32 bits. |
| * (This could be a general function, but we define it here so that the |
| * Tlv encoder/decoder is self-contained.) |
| * @param {number} x |
| * @return {number} |
| */ |
| Tlv.getHighBytes = function(x) |
| { |
| // Don't use floor because we expect the caller to use & and >> on the result |
| // which already strip off the fraction. |
| return (x - (x % 0x100000000)) / 0x100000000; |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var DynamicBuffer = require('../../util/dynamic-buffer.js').DynamicBuffer; /** @ignore */ |
| var Tlv = require('./tlv.js').Tlv; |
| |
| /** |
| * Create a new TlvEncoder with an initialCapacity for the encoding buffer. |
| * @constructor |
| * @param {number} initialCapacity (optional) The initial capacity of the |
| * encoding buffer. If omitted, use a default value. |
| */ |
| var TlvEncoder = function TlvEncoder(initialCapacity) |
| { |
| initialCapacity = initialCapacity || 16; |
| this.output = new DynamicBuffer(initialCapacity); |
| // length is the number of bytes that have been written to the back of |
| // this.output.array. |
| this.length = 0; |
| }; |
| |
| exports.TlvEncoder = TlvEncoder; |
| |
| /** |
| * Get the number of bytes that have been written to the output. You can |
| * save this number, write sub TLVs, then subtract the new length from this |
| * to get the total length of the sub TLVs. |
| * @return {number} The number of bytes that have been written to the output. |
| */ |
| TlvEncoder.prototype.getLength = function() |
| { |
| return this.length; |
| }; |
| |
| /** |
| * Encode varNumber as a VAR-NUMBER in NDN-TLV and write it to this.output just |
| * before this.length from the back. Advance this.length. |
| * @param {number} varNumber The non-negative number to encode. |
| */ |
| TlvEncoder.prototype.writeVarNumber = function(varNumber) |
| { |
| if (varNumber < 253) { |
| this.length += 1; |
| this.output.ensureLengthFromBack(this.length); |
| this.output.array[this.output.array.length - this.length] = varNumber & 0xff; |
| } |
| else if (varNumber <= 0xffff) { |
| this.length += 3; |
| this.output.ensureLengthFromBack(this.length); |
| var offset = this.output.array.length - this.length; |
| this.output.array[offset] = 253; |
| this.output.array[offset + 1] = (varNumber >> 8) & 0xff; |
| this.output.array[offset + 2] = varNumber & 0xff; |
| } |
| else if (varNumber <= 0xffffffff) { |
| this.length += 5; |
| this.output.ensureLengthFromBack(this.length); |
| var offset = this.output.array.length - this.length; |
| this.output.array[offset] = 254; |
| this.output.array[offset + 1] = (varNumber >> 24) & 0xff; |
| this.output.array[offset + 2] = (varNumber >> 16) & 0xff; |
| this.output.array[offset + 3] = (varNumber >> 8) & 0xff; |
| this.output.array[offset + 4] = varNumber & 0xff; |
| } |
| else { |
| this.length += 9; |
| this.output.ensureLengthFromBack(this.length); |
| var offset = this.output.array.length - this.length; |
| this.output.array[offset] = 255; |
| var highBytes = Tlv.getHighBytes(varNumber); |
| this.output.array[offset + 1] = (highBytes >> 24) & 0xff; |
| this.output.array[offset + 2] = (highBytes >> 16) & 0xff; |
| this.output.array[offset + 3] = (highBytes >> 8) & 0xff; |
| this.output.array[offset + 4] = (highBytes) & 0xff; |
| this.output.array[offset + 5] = (varNumber >> 24) & 0xff; |
| this.output.array[offset + 6] = (varNumber >> 16) & 0xff; |
| this.output.array[offset + 7] = (varNumber >> 8) & 0xff; |
| this.output.array[offset + 8] = varNumber & 0xff; |
| } |
| }; |
| |
| /** |
| * Encode the type and length as VAR-NUMBER and write to this.output just before |
| * this.length from the back. Advance this.length. |
| * @param {number} type The type of the TLV. |
| * @param {number} length The non-negative length of the TLV. |
| */ |
| TlvEncoder.prototype.writeTypeAndLength = function(type, length) |
| { |
| // Write backwards. |
| this.writeVarNumber(length); |
| this.writeVarNumber(type); |
| }; |
| |
| /** |
| * Write value as a non-negative integer and write it to this.output just before |
| * this.length from the back. Advance this.length. |
| * @param {number} value The non-negative integer to encode. |
| */ |
| TlvEncoder.prototype.writeNonNegativeInteger = function(value) |
| { |
| if (value < 0) |
| throw new Error("TLV integer value may not be negative"); |
| |
| // JavaScript doesn't distinguish int from float, so round. |
| value = Math.round(value); |
| |
| if (value <= 0xff) { |
| this.length += 1; |
| this.output.ensureLengthFromBack(this.length); |
| this.output.array[this.output.array.length - this.length] = value & 0xff; |
| } |
| else if (value <= 0xffff) { |
| this.length += 2; |
| this.output.ensureLengthFromBack(this.length); |
| var offset = this.output.array.length - this.length; |
| this.output.array[offset] = (value >> 8) & 0xff; |
| this.output.array[offset + 1] = value & 0xff; |
| } |
| else if (value <= 0xffffffff) { |
| this.length += 4; |
| this.output.ensureLengthFromBack(this.length); |
| var offset = this.output.array.length - this.length; |
| this.output.array[offset] = (value >> 24) & 0xff; |
| this.output.array[offset + 1] = (value >> 16) & 0xff; |
| this.output.array[offset + 2] = (value >> 8) & 0xff; |
| this.output.array[offset + 3] = value & 0xff; |
| } |
| else { |
| this.length += 8; |
| this.output.ensureLengthFromBack(this.length); |
| var offset = this.output.array.length - this.length; |
| var highBytes = Tlv.getHighBytes(value); |
| this.output.array[offset] = (highBytes >> 24) & 0xff; |
| this.output.array[offset + 1] = (highBytes >> 16) & 0xff; |
| this.output.array[offset + 2] = (highBytes >> 8) & 0xff; |
| this.output.array[offset + 3] = (highBytes) & 0xff; |
| this.output.array[offset + 4] = (value >> 24) & 0xff; |
| this.output.array[offset + 5] = (value >> 16) & 0xff; |
| this.output.array[offset + 6] = (value >> 8) & 0xff; |
| this.output.array[offset + 7] = value & 0xff; |
| } |
| }; |
| |
| /** |
| * Write the type, then the length of the encoded value then encode value as a |
| * non-negative integer and write it to this.output just before this.length from |
| * the back. Advance this.length. |
| * @param {number} type The type of the TLV. |
| * @param {number} value The non-negative integer to encode. |
| */ |
| TlvEncoder.prototype.writeNonNegativeIntegerTlv = function(type, value) |
| { |
| // Write backwards. |
| var saveNBytes = this.length; |
| this.writeNonNegativeInteger(value); |
| this.writeTypeAndLength(type, this.length - saveNBytes); |
| }; |
| |
| /** |
| * If value is negative or null then do nothing, otherwise call |
| * writeNonNegativeIntegerTlv. |
| * @param {number} type The type of the TLV. |
| * @param {number} value If negative or null do nothing, otherwise the integer |
| * to encode. |
| */ |
| TlvEncoder.prototype.writeOptionalNonNegativeIntegerTlv = function(type, value) |
| { |
| if (value != null && value >= 0) |
| this.writeNonNegativeIntegerTlv(type, value); |
| }; |
| |
| /** |
| * Write the buffer value to this.output just before this.length from the back. |
| * Advance this.length. |
| * @param {Buffer} buffer The byte array with the bytes to write. If value is |
| * null, then do nothing. |
| */ |
| TlvEncoder.prototype.writeBuffer = function(buffer) |
| { |
| if (buffer == null) |
| return; |
| |
| this.length += buffer.length; |
| this.output.copyFromBack(buffer, this.length); |
| }; |
| |
| /** |
| * Write the type, then the length of the buffer then the buffer value to |
| * this.output just before this.length from the back. Advance this.length. |
| * @param {number} type The type of the TLV. |
| * @param {Buffer} value The byte array with the bytes of the blob. If value is |
| null, then just write the type and length 0. |
| */ |
| TlvEncoder.prototype.writeBlobTlv = function(type, value) |
| { |
| if (value == null) { |
| this.writeTypeAndLength(type, 0); |
| return; |
| } |
| |
| // Write backwards, starting with the blob array. |
| this.writeBuffer(value); |
| this.writeTypeAndLength(type, value.length); |
| }; |
| |
| /** |
| * If the byte array is null or zero length then do nothing, otherwise call |
| * writeBlobTlv. |
| * @param {number} type The type of the TLV. |
| * @param {Buffer} value If null or zero length do nothing, otherwise the byte |
| * array with the bytes of the blob. |
| */ |
| TlvEncoder.prototype.writeOptionalBlobTlv = function(type, value) |
| { |
| if (value != null && value.length > 0) |
| this.writeBlobTlv(type, value); |
| }; |
| |
| /** |
| * Get a slice of the encoded bytes. |
| * @return {Buffer} A slice backed by the encoding Buffer. |
| */ |
| TlvEncoder.prototype.getOutput = function() |
| { |
| return this.output.array.slice(this.output.array.length - this.length); |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var DecodingException = require('../decoding-exception.js').DecodingException; |
| |
| /** |
| * Create a new TlvDecoder for decoding the input in the NDN-TLV wire format. |
| * @constructor |
| * @param {Buffer} input The buffer with the bytes to decode. |
| */ |
| var TlvDecoder = function TlvDecoder(input) |
| { |
| this.input = input; |
| this.offset = 0; |
| }; |
| |
| exports.TlvDecoder = TlvDecoder; |
| |
| /** |
| * Decode VAR-NUMBER in NDN-TLV and return it. Update offset. |
| * @return {number} The decoded VAR-NUMBER. |
| */ |
| TlvDecoder.prototype.readVarNumber = function() |
| { |
| // Assume array values are in the range 0 to 255. |
| var firstOctet = this.input[this.offset]; |
| this.offset += 1; |
| if (firstOctet < 253) |
| return firstOctet; |
| else |
| return this.readExtendedVarNumber(firstOctet); |
| }; |
| |
| /** |
| * A private function to do the work of readVarNumber, given the firstOctet |
| * which is >= 253. |
| * @param {number} firstOctet The first octet which is >= 253, used to decode |
| * the remaining bytes. |
| * @return {number} The decoded VAR-NUMBER. |
| */ |
| TlvDecoder.prototype.readExtendedVarNumber = function(firstOctet) |
| { |
| var result; |
| // This is a private function so we know firstOctet >= 253. |
| if (firstOctet == 253) { |
| result = ((this.input[this.offset] << 8) + |
| this.input[this.offset + 1]); |
| this.offset += 2; |
| } |
| else if (firstOctet == 254) { |
| // Use abs because << 24 can set the high bit of the 32-bit int making it negative. |
| result = (Math.abs(this.input[this.offset] << 24) + |
| (this.input[this.offset + 1] << 16) + |
| (this.input[this.offset + 2] << 8) + |
| this.input[this.offset + 3]); |
| this.offset += 4; |
| } |
| else { |
| // Get the high byte first because JavaScript << is restricted to 32 bits. |
| // Use abs because << 24 can set the high bit of the 32-bit int making it negative. |
| var highByte = Math.abs(this.input[this.offset] << 24) + |
| (this.input[this.offset + 1] << 16) + |
| (this.input[this.offset + 2] << 8) + |
| this.input[this.offset + 3]; |
| result = (highByte * 0x100000000 + |
| (this.input[this.offset + 4] << 24) + |
| (this.input[this.offset + 5] << 16) + |
| (this.input[this.offset + 6] << 8) + |
| this.input[this.offset + 7]); |
| this.offset += 8; |
| } |
| |
| return result; |
| }; |
| |
| /** |
| * Decode the type and length from this's input starting at offset, expecting |
| * the type to be expectedType and return the length. Update offset. Also make |
| * sure the decoded length does not exceed the number of bytes remaining in the |
| * input. |
| * @param {number} expectedType The expected type. |
| * @return {number} The length of the TLV. |
| * @throws DecodingException if (did not get the expected TLV type or the TLV length |
| * exceeds the buffer length. |
| */ |
| TlvDecoder.prototype.readTypeAndLength = function(expectedType) |
| { |
| var type = this.readVarNumber(); |
| if (type != expectedType) |
| throw new DecodingException(new Error("Did not get the expected TLV type")); |
| |
| var length = this.readVarNumber(); |
| if (this.offset + length > this.input.length) |
| throw new DecodingException(new Error("TLV length exceeds the buffer length")); |
| |
| return length; |
| }; |
| |
| /** |
| * Decode the type and length from the input starting at offset, expecting the |
| * type to be expectedType. Update offset. Also make sure the decoded length |
| * does not exceed the number of bytes remaining in the input. Return the offset |
| * of the end of this parent TLV, which is used in decoding optional nested |
| * TLVs. After reading all nested TLVs, call finishNestedTlvs. |
| * @param {number} expectedType The expected type. |
| * @return {number} The offset of the end of the parent TLV. |
| * @throws DecodingException if did not get the expected TLV type or the TLV |
| * length exceeds the buffer length. |
| */ |
| TlvDecoder.prototype.readNestedTlvsStart = function(expectedType) |
| { |
| return this.readTypeAndLength(expectedType) + this.offset; |
| }; |
| |
| /** |
| * Call this after reading all nested TLVs to skip any remaining unrecognized |
| * TLVs and to check if the offset after the final nested TLV matches the |
| * endOffset returned by readNestedTlvsStart. |
| * @param {number} endOffset The offset of the end of the parent TLV, returned |
| * by readNestedTlvsStart. |
| * @throws DecodingException if the TLV length does not equal the total length |
| * of the nested TLVs. |
| */ |
| TlvDecoder.prototype.finishNestedTlvs = function(endOffset) |
| { |
| // We expect offset to be endOffset, so check this first. |
| if (this.offset == endOffset) |
| return; |
| |
| // Skip remaining TLVs. |
| while (this.offset < endOffset) { |
| // Skip the type VAR-NUMBER. |
| this.readVarNumber(); |
| // Read the length and update offset. |
| var length = this.readVarNumber(); |
| this.offset += length; |
| |
| if (this.offset > this.input.length) |
| throw new DecodingException(new Error("TLV length exceeds the buffer length")); |
| } |
| |
| if (this.offset != endOffset) |
| throw new DecodingException(new Error |
| ("TLV length does not equal the total length of the nested TLVs")); |
| }; |
| |
| /** |
| * Decode the type from this's input starting at offset, and if it is the |
| * expectedType, then return true, else false. However, if this's offset is |
| * greater than or equal to endOffset, then return false and don't try to read |
| * the type. Do not update offset. |
| * @param {number} expectedType The expected type. |
| * @param {number} endOffset The offset of the end of the parent TLV, returned |
| * by readNestedTlvsStart. |
| * @return {boolean} true if the type of the next TLV is the expectedType, |
| * otherwise false. |
| */ |
| TlvDecoder.prototype.peekType = function(expectedType, endOffset) |
| { |
| if (this.offset >= endOffset) |
| // No more sub TLVs to look at. |
| return false; |
| else { |
| var saveOffset = this.offset; |
| var type = this.readVarNumber(); |
| // Restore offset. |
| this.offset = saveOffset; |
| |
| return type == expectedType; |
| } |
| }; |
| |
| /** |
| * Decode a non-negative integer in NDN-TLV and return it. Update offset by |
| * length. |
| * @param {number} length The number of bytes in the encoded integer. |
| * @return {number} The integer. |
| * @throws DecodingException if length is an invalid length for a TLV |
| * non-negative integer. |
| */ |
| TlvDecoder.prototype.readNonNegativeInteger = function(length) |
| { |
| var result; |
| if (length == 1) |
| result = this.input[this.offset]; |
| else if (length == 2) |
| result = ((this.input[this.offset] << 8) + |
| this.input[this.offset + 1]); |
| else if (length == 4) |
| // Use abs because << 24 can set the high bit of the 32-bit int making it negative. |
| result = (Math.abs(this.input[this.offset] << 24) + |
| (this.input[this.offset + 1] << 16) + |
| (this.input[this.offset + 2] << 8) + |
| this.input[this.offset + 3]); |
| else if (length == 8) { |
| // Use abs because << 24 can set the high bit of the 32-bit int making it negative. |
| var highByte = Math.abs(this.input[this.offset] << 24) + |
| (this.input[this.offset + 1] << 16) + |
| (this.input[this.offset + 2] << 8) + |
| this.input[this.offset + 3]; |
| result = (highByte * 0x100000000 + |
| Math.abs(this.input[this.offset + 4] << 24) + |
| (this.input[this.offset + 5] << 16) + |
| (this.input[this.offset + 6] << 8) + |
| this.input[this.offset + 7]); |
| } |
| else |
| throw new DecodingException(new Error("Invalid length for a TLV nonNegativeInteger")); |
| |
| this.offset += length; |
| return result; |
| }; |
| |
| /** |
| * Decode the type and length from this's input starting at offset, expecting |
| * the type to be expectedType. Then decode a non-negative integer in NDN-TLV |
| * and return it. Update offset. |
| * @param {number} expectedType The expected type. |
| * @return {number} The integer. |
| * @throws DecodingException if did not get the expected TLV type or can't |
| * decode the value. |
| */ |
| TlvDecoder.prototype.readNonNegativeIntegerTlv = function(expectedType) |
| { |
| var length = this.readTypeAndLength(expectedType); |
| return this.readNonNegativeInteger(length); |
| }; |
| |
| /** |
| * Peek at the next TLV, and if it has the expectedType then call |
| * readNonNegativeIntegerTlv and return the integer. Otherwise, return null. |
| * However, if this's offset is greater than or equal to endOffset, then return |
| * null and don't try to read the type. |
| * @param {number} expectedType The expected type. |
| * @param {number} endOffset The offset of the end of the parent TLV, returned |
| * by readNestedTlvsStart. |
| * @return {number} The integer or null if the next TLV doesn't have the |
| * expected type. |
| */ |
| TlvDecoder.prototype.readOptionalNonNegativeIntegerTlv = function |
| (expectedType, endOffset) |
| { |
| if (this.peekType(expectedType, endOffset)) |
| return this.readNonNegativeIntegerTlv(expectedType); |
| else |
| return null; |
| }; |
| |
| /** |
| * Decode the type and length from this's input starting at offset, expecting |
| * the type to be expectedType. Then return an array of the bytes in the value. |
| * Update offset. |
| * @param {number} expectedType The expected type. |
| * @return {Buffer} The bytes in the value as a slice on the buffer. This is |
| * not a copy of the bytes in the input buffer. If you need a copy, then you |
| * must make a copy of the return value. |
| * @throws DecodingException if did not get the expected TLV type. |
| */ |
| TlvDecoder.prototype.readBlobTlv = function(expectedType) |
| { |
| var length = this.readTypeAndLength(expectedType); |
| var result = this.input.slice(this.offset, this.offset + length); |
| |
| // readTypeAndLength already checked if length exceeds the input buffer. |
| this.offset += length; |
| return result; |
| }; |
| |
| /** |
| * Peek at the next TLV, and if it has the expectedType then call readBlobTlv |
| * and return the value. Otherwise, return null. However, if this's offset is |
| * greater than or equal to endOffset, then return null and don't try to read |
| * the type. |
| * @param {number} expectedType The expected type. |
| * @param {number} endOffset The offset of the end of the parent TLV, returned |
| * by readNestedTlvsStart. |
| * @return {Buffer} The bytes in the value as a slice on the buffer or null if |
| * the next TLV doesn't have the expected type. This is not a copy of the bytes |
| * in the input buffer. If you need a copy, then you must make a copy of the |
| * return value. |
| */ |
| TlvDecoder.prototype.readOptionalBlobTlv = function(expectedType, endOffset) |
| { |
| if (this.peekType(expectedType, endOffset)) |
| return this.readBlobTlv(expectedType); |
| else |
| return null; |
| }; |
| |
| /** |
| * Peek at the next TLV, and if it has the expectedType then read a type and |
| * value, ignoring the value, and return true. Otherwise, return false. |
| * However, if this's offset is greater than or equal to endOffset, then return |
| * false and don't try to read the type. |
| * @param {number} expectedType The expected type. |
| * @param {number} endOffset The offset of the end of the parent TLV, returned |
| * by readNestedTlvsStart. |
| * @return {boolean} true, or else false if the next TLV doesn't have the |
| * expected type. |
| */ |
| TlvDecoder.prototype.readBooleanTlv = function(expectedType, endOffset) |
| { |
| if (this.peekType(expectedType, endOffset)) { |
| var length = this.readTypeAndLength(expectedType); |
| // We expect the length to be 0, but update offset anyway. |
| this.offset += length; |
| return true; |
| } |
| else |
| return false; |
| }; |
| |
| /** |
| * Get the offset into the input, used for the next read. |
| * @return {number} The offset. |
| */ |
| TlvDecoder.prototype.getOffset = function() |
| { |
| return this.offset; |
| }; |
| |
| /** |
| * Set the offset into the input, used for the next read. |
| * @param {number} offset The new offset. |
| */ |
| TlvDecoder.prototype.seek = function(offset) |
| { |
| this.offset = offset; |
| }; |
| |
| /** |
| * Return an array of a slice of the input for the given offset range. |
| * @param {number} beginOffset The offset in the input of the beginning of the |
| * slice. |
| * @param {number} endOffset The offset in the input of the end of the slice. |
| * @return {Buffer} The bytes in the value as a slice on the buffer. This is |
| * not a copy of the bytes in the input buffer. If you need a copy, then you |
| * must make a copy of the return value. |
| */ |
| TlvDecoder.prototype.getSlice = function(beginOffset, endOffset) |
| { |
| return this.input.slice(beginOffset, endOffset); |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var TlvDecoder = require('./tlv-decoder.js').TlvDecoder; |
| |
| /** |
| * A TlvStructureDecoder finds the end of an NDN-TLV element, even if the |
| * element is supplied in parts. |
| * Create and initialize a TlvStructureDecoder. |
| * @constructor |
| */ |
| var TlvStructureDecoder = function TlvStructureDecoder() |
| { |
| this.gotElementEnd_ = false; |
| this.offset_ = 0; |
| this.state_ = TlvStructureDecoder.READ_TYPE; |
| this.headerLength_ = 0; |
| this.useHeaderBuffer_ = false; |
| // 8 bytes is enough to hold the extended bytes in the length encoding |
| // where it is an 8-byte number. |
| this.headerBuffer_ = new Buffer(8); |
| this.nBytesToRead_ = 0; |
| }; |
| |
| exports.TlvStructureDecoder = TlvStructureDecoder; |
| |
| TlvStructureDecoder.READ_TYPE = 0; |
| TlvStructureDecoder.READ_TYPE_BYTES = 1; |
| TlvStructureDecoder.READ_LENGTH = 2; |
| TlvStructureDecoder.READ_LENGTH_BYTES = 3; |
| TlvStructureDecoder.READ_VALUE_BYTES = 4; |
| |
| /** |
| * Continue scanning input starting from this.offset_ to find the element end. |
| * If the end of the element which started at offset 0 is found, this returns |
| * true and getOffset() is the length of the element. Otherwise, this returns |
| * false which means you should read more into input and call again. |
| * @param {Buffer} input The input buffer. You have to pass in input each time |
| * because the buffer could be reallocated. |
| * @return {boolean} true if found the element end, false if not. |
| */ |
| TlvStructureDecoder.prototype.findElementEnd = function(input) |
| { |
| if (this.gotElementEnd_) |
| // Someone is calling when we already got the end. |
| return true; |
| |
| var decoder = new TlvDecoder(input); |
| |
| while (true) { |
| if (this.offset_ >= input.length) |
| // All the cases assume we have some input. Return and wait for more. |
| return false; |
| |
| if (this.state_ == TlvStructureDecoder.READ_TYPE) { |
| var firstOctet = input[this.offset_]; |
| this.offset_ += 1; |
| if (firstOctet < 253) |
| // The value is simple, so we can skip straight to reading the length. |
| this.state_ = TlvStructureDecoder.READ_LENGTH; |
| else { |
| // Set up to skip the type bytes. |
| if (firstOctet == 253) |
| this.nBytesToRead_ = 2; |
| else if (firstOctet == 254) |
| this.nBytesToRead_ = 4; |
| else |
| // value == 255. |
| this.nBytesToRead_ = 8; |
| |
| this.state_ = TlvStructureDecoder.READ_TYPE_BYTES; |
| } |
| } |
| else if (this.state_ == TlvStructureDecoder.READ_TYPE_BYTES) { |
| var nRemainingBytes = input.length - this.offset_; |
| if (nRemainingBytes < this.nBytesToRead_) { |
| // Need more. |
| this.offset_ += nRemainingBytes; |
| this.nBytesToRead_ -= nRemainingBytes; |
| return false; |
| } |
| |
| // Got the type bytes. Move on to read the length. |
| this.offset_ += this.nBytesToRead_; |
| this.state_ = TlvStructureDecoder.READ_LENGTH; |
| } |
| else if (this.state_ == TlvStructureDecoder.READ_LENGTH) { |
| var firstOctet = input[this.offset_]; |
| this.offset_ += 1; |
| if (firstOctet < 253) { |
| // The value is simple, so we can skip straight to reading |
| // the value bytes. |
| this.nBytesToRead_ = firstOctet; |
| if (this.nBytesToRead_ == 0) { |
| // No value bytes to read. We're finished. |
| this.gotElementEnd_ = true; |
| return true; |
| } |
| |
| this.state_ = TlvStructureDecoder.READ_VALUE_BYTES; |
| } |
| else { |
| // We need to read the bytes in the extended encoding of |
| // the length. |
| if (firstOctet == 253) |
| this.nBytesToRead_ = 2; |
| else if (firstOctet == 254) |
| this.nBytesToRead_ = 4; |
| else |
| // value == 255. |
| this.nBytesToRead_ = 8; |
| |
| // We need to use firstOctet in the next state. |
| this.firstOctet_ = firstOctet; |
| this.state_ = TlvStructureDecoder.READ_LENGTH_BYTES; |
| } |
| } |
| else if (this.state_ == TlvStructureDecoder.READ_LENGTH_BYTES) { |
| var nRemainingBytes = input.length - this.offset_; |
| if (!this.useHeaderBuffer_ && nRemainingBytes >= this.nBytesToRead_) { |
| // We don't have to use the headerBuffer. Set nBytesToRead. |
| decoder.seek(this.offset_); |
| |
| this.nBytesToRead_ = decoder.readExtendedVarNumber(this.firstOctet_); |
| // Update this.offset_ to the decoder's offset after reading. |
| this.offset_ = decoder.getOffset(); |
| } |
| else { |
| this.useHeaderBuffer_ = true; |
| |
| var nNeededBytes = this.nBytesToRead_ - this.headerLength_; |
| if (nNeededBytes > nRemainingBytes) { |
| // We can't get all of the header bytes from this input. |
| // Save in headerBuffer. |
| if (this.headerLength_ + nRemainingBytes > this.headerBuffer_.length) |
| // We don't expect this to happen. |
| throw new Error |
| ("Cannot store more header bytes than the size of headerBuffer"); |
| input.slice(this.offset_, this.offset_ + nRemainingBytes).copy |
| (this.headerBuffer_, this.headerLength_); |
| this.offset_ += nRemainingBytes; |
| this.headerLength_ += nRemainingBytes; |
| |
| return false; |
| } |
| |
| // Copy the remaining bytes into headerBuffer, read the |
| // length and set nBytesToRead. |
| if (this.headerLength_ + nNeededBytes > this.headerBuffer_.length) |
| // We don't expect this to happen. |
| throw new Error |
| ("Cannot store more header bytes than the size of headerBuffer"); |
| input.slice(this.offset_, this.offset_ + nNeededBytes).copy |
| (this.headerBuffer_, this.headerLength_); |
| this.offset_ += nNeededBytes; |
| |
| // Use a local decoder just for the headerBuffer. |
| var bufferDecoder = new TlvDecoder(this.headerBuffer_); |
| // Replace nBytesToRead with the length of the value. |
| this.nBytesToRead_ = bufferDecoder.readExtendedVarNumber(this.firstOctet_); |
| } |
| |
| if (this.nBytesToRead_ == 0) { |
| // No value bytes to read. We're finished. |
| this.gotElementEnd_ = true; |
| return true; |
| } |
| |
| // Get ready to read the value bytes. |
| this.state_ = TlvStructureDecoder.READ_VALUE_BYTES; |
| } |
| else if (this.state_ == TlvStructureDecoder.READ_VALUE_BYTES) { |
| var nRemainingBytes = input.length - this.offset_; |
| if (nRemainingBytes < this.nBytesToRead_) { |
| // Need more. |
| this.offset_ += nRemainingBytes; |
| this.nBytesToRead_ -= nRemainingBytes; |
| return false; |
| } |
| |
| // Got the bytes. We're finished. |
| this.offset_ += this.nBytesToRead_; |
| this.gotElementEnd_ = true; |
| return true; |
| } |
| else |
| // We don't expect this to happen. |
| throw new Error("findElementEnd: unrecognized state"); |
| } |
| }; |
| |
| /** |
| * Get the current offset into the input buffer. |
| * @return {number} The offset. |
| */ |
| TlvStructureDecoder.prototype.getOffset = function() |
| { |
| return this.offset_; |
| }; |
| |
| /** |
| * Set the offset into the input, used for the next read. |
| * @param {number} offset The new offset. |
| */ |
| TlvStructureDecoder.prototype.seek = function(offset) |
| { |
| this.offset_ = offset; |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var TlvEncoder = require('./tlv/tlv-encoder.js').TlvEncoder; /** @ignore */ |
| var TlvDecoder = require('./tlv/tlv-decoder.js').TlvDecoder; /** @ignore */ |
| var Blob = require('../util/blob.js').Blob; /** @ignore */ |
| var Name = require('../name.js').Name; /** @ignore */ |
| |
| /** |
| * ProtobufTlv has static methods to encode and decode an Protobuf Message |
| * object as NDN-TLV. The Protobuf tag value is used as the TLV type code. A |
| * Protobuf message is encoded/decoded as a nested TLV encoding. Protobuf types |
| * uint32, uint64 and enum are encoded/decoded as TLV nonNegativeInteger. (It is |
| * an error if an enum value is negative.) Protobuf types bytes and string are |
| * encoded/decoded as TLV bytes. The Protobuf type bool is encoded/decoded as a |
| * TLV boolean (a zero length value for True, omitted for False). The Protobuf |
| * type double is encoded/decoded as an 8-byte little-endian IEEE 754 double. |
| * Other Protobuf types are an error. |
| * |
| * Protobuf has no "outer" message type, so you need to put your TLV message |
| * inside an outer "typeless" message. |
| * @constructor |
| */ |
| var ProtobufTlv = function ProtobufTlv() |
| { |
| }; |
| |
| exports.ProtobufTlv = ProtobufTlv; |
| |
| // Load ProtoBuf.Reflect.Message.Field dynamically so that protobufjs is optional. |
| ProtobufTlv._Field = null; |
| ProtobufTlv.establishField = function() |
| { |
| if (ProtobufTlv._Field === null) { |
| try { |
| // Using protobuf.min.js in the browser. |
| ProtobufTlv._Field = dcodeIO.ProtoBuf.Reflect.Message.Field; |
| } |
| catch (ex) { |
| // Using protobufjs in node. |
| ProtobufTlv._Field = require("protobufjs").Reflect.Message.Field; |
| } |
| } |
| } |
| |
| /** |
| * Encode the Protobuf message object as NDN-TLV. This calls |
| * message.encodeAB() to ensure that all required fields are present and |
| * raises an exception if not. (This does not use the result of toArrayBuffer().) |
| * @param {ProtoBuf.Builder.Message} message The Protobuf message object. |
| * @param {ProtoBuf.Reflect.T} descriptor The reflection descriptor for the |
| * message. For example, if the message is of type "MyNamespace.MyMessage" then |
| * the descriptor is builder.lookup("MyNamespace.MyMessage"). |
| * @return {Blob} The encoded buffer in a Blob object. |
| */ |
| ProtobufTlv.encode = function(message, descriptor) |
| { |
| ProtobufTlv.establishField(); |
| |
| message.encodeAB(); |
| var encoder = new TlvEncoder(); |
| ProtobufTlv._encodeMessageValue(message, descriptor, encoder); |
| return new Blob(encoder.getOutput(), false); |
| }; |
| |
| /** |
| * Decode the input as NDN-TLV and update the fields of the Protobuf message |
| * object. |
| * @param {ProtoBuf.Builder.Message} message The Protobuf message object. This |
| * does not first clear the object. |
| * @param {ProtoBuf.Reflect.T} descriptor The reflection descriptor for the |
| * message. For example, if the message is of type "MyNamespace.MyMessage" then |
| * the descriptor is builder.lookup("MyNamespace.MyMessage"). |
| * @param {Blob|Buffer} input The buffer with the bytes to decode. |
| */ |
| ProtobufTlv.decode = function(message, descriptor, input) |
| { |
| ProtobufTlv.establishField(); |
| |
| // If input is a blob, get its buf(). |
| var decodeBuffer = typeof input === 'object' && input instanceof Blob ? |
| input.buf() : input; |
| |
| var decoder = new TlvDecoder(decodeBuffer); |
| ProtobufTlv._decodeMessageValue |
| (message, descriptor, decoder, decodeBuffer.length); |
| }; |
| |
| ProtobufTlv._encodeMessageValue = function(message, descriptor, encoder) |
| { |
| var fields = descriptor.getChildren(ProtobufTlv._Field); |
| // Encode the fields backwards. |
| for (var iField = fields.length - 1; iField >= 0; --iField) { |
| var field = fields[iField]; |
| var tlvType = field.id; |
| |
| var values; |
| if (field.repeated) |
| values = message[field.name]; |
| else { |
| if (message[field.name] != null) |
| // Make a singleton list. |
| values = [message[field.name]]; |
| else |
| continue; |
| } |
| |
| // Encode the values backwards. |
| for (var iValue = values.length - 1; iValue >= 0; --iValue) { |
| var value = values[iValue]; |
| |
| if (field.type.name == "message") { |
| var saveLength = encoder.getLength(); |
| |
| // Encode backwards. |
| ProtobufTlv._encodeMessageValue(value, field.resolvedType, encoder); |
| encoder.writeTypeAndLength(tlvType, encoder.getLength() - saveLength); |
| } |
| else if (field.type.name == "uint32" || |
| field.type.name == "uint64") |
| encoder.writeNonNegativeIntegerTlv(tlvType, value); |
| else if (field.type.name == "enum") { |
| if (value < 0) |
| throw new Error("ProtobufTlv::encode: ENUM value may not be negative"); |
| encoder.writeNonNegativeIntegerTlv(tlvType, value); |
| } |
| else if (field.type.name == "bytes") { |
| var buffer = value.toBuffer(); |
| if (buffer.length == undefined) |
| // We are not running in Node.js, so assume we are using the dcodeIO |
| // browser implementation based on ArrayBuffer. |
| buffer = new Uint8Array(value.toArrayBuffer()); |
| encoder.writeBlobTlv(tlvType, buffer); |
| } |
| else if (field.type.name == "string") |
| // Use Blob to convert. |
| encoder.writeBlobTlv(tlvType, new Blob(value, false).buf()); |
| else if (field.type.name == "bool") { |
| if (value) |
| encoder.writeTypeAndLength(tlvType, 0); |
| } |
| else if (field.type.name == "double") { |
| var encoding = new Buffer(8); |
| encoding.writeDoubleLE(value, 0); |
| encoder.writeBlobTlv(tlvType, encoding); |
| } |
| else |
| throw new Error("ProtobufTlv::encode: Unknown field type"); |
| } |
| } |
| }; |
| |
| ProtobufTlv._decodeMessageValue = function(message, descriptor, decoder, endOffset) |
| { |
| var fields = descriptor.getChildren(ProtobufTlv._Field); |
| for (var iField = 0; iField < fields.length; ++iField) { |
| var field = fields[iField]; |
| var tlvType = field.id; |
| |
| if (!field.required && !decoder.peekType(tlvType, endOffset)) |
| continue; |
| |
| if (field.repeated) { |
| while (decoder.peekType(tlvType, endOffset)) { |
| if (field.type.name == "message") { |
| var innerEndOffset = decoder.readNestedTlvsStart(tlvType); |
| var value = new (field.resolvedType.build())(); |
| message.add(field.name, value); |
| ProtobufTlv._decodeMessageValue |
| (value, field.resolvedType, decoder, innerEndOffset); |
| decoder.finishNestedTlvs(innerEndOffset); |
| } |
| else |
| message.add |
| (field.name, |
| ProtobufTlv._decodeFieldValue(field, tlvType, decoder, endOffset)); |
| } |
| } |
| else { |
| if (field.type.name == "message") { |
| var innerEndOffset = decoder.readNestedTlvsStart(tlvType); |
| var value = new (field.resolvedType.build())(); |
| message.set(field.name, value); |
| ProtobufTlv._decodeMessageValue |
| (value, field.resolvedType, decoder, innerEndOffset); |
| decoder.finishNestedTlvs(innerEndOffset); |
| } |
| else |
| message.set |
| (field.name, |
| ProtobufTlv._decodeFieldValue(field, tlvType, decoder, endOffset)); |
| } |
| } |
| }; |
| |
| /** |
| * This is a helper for _decodeMessageValue. Decode a single field and return |
| * the value. Assume the field.type.name is not "message". |
| */ |
| ProtobufTlv._decodeFieldValue = function(field, tlvType, decoder, endOffset) |
| { |
| if (field.type.name == "uint32" || |
| field.type.name == "uint64" || |
| field.type.name == "enum") |
| return decoder.readNonNegativeIntegerTlv(tlvType); |
| else if (field.type.name == "bytes") |
| return decoder.readBlobTlv(tlvType); |
| else if (field.type.name == "string") |
| return decoder.readBlobTlv(tlvType).toString(); |
| else if (field.type.name == "bool") |
| return decoder.readBooleanTlv(tlvType, endOffset); |
| else if (field.type.name == "double") |
| return decoder.readBlobTlv(tlvType).readDoubleLE(0); |
| else |
| throw new Error("ProtobufTlv.decode: Unknown field type"); |
| }; |
| |
| /** |
| * Return a Name made from the component array in a Protobuf message object, |
| * assuming that it was defined with "repeated bytes". For example: |
| * message Name { |
| * repeated bytes component = 8; |
| * } |
| * @param {Array} componentArray The array from the Protobuf message object |
| * representing the "repeated bytes" component array. |
| * @return A new Name. |
| */ |
| ProtobufTlv.toName = function(componentArray) |
| { |
| var name = new Name(); |
| for (var i = 0; i < componentArray.length; ++i) |
| name.append |
| (new Blob(new Buffer(componentArray[i].toBinary(), "binary")), false); |
| |
| return name; |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From code in ndn-cxx by Yingdi Yu <yingdi@cs.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * @constructor |
| */ |
| var OID = function OID(oid) |
| { |
| if (typeof oid === 'string') { |
| var splitString = oid.split("."); |
| this.oid = []; |
| for (var i = 0; i < splitString.length; ++i) |
| this.oid.push(parseInt(splitString[i])); |
| } |
| else |
| // Assume oid is an array of int. Make a copy. |
| this.oid = oid.slice(0, oid.length); |
| }; |
| |
| exports.OID = OID; |
| |
| OID.prototype.getIntegerList = function() |
| { |
| return this.oid; |
| }; |
| |
| OID.prototype.setIntegerList = function(oid) |
| { |
| // Make a copy. |
| this.oid = oid.slice(0, oid.length); |
| }; |
| |
| OID.prototype.toString = function() |
| { |
| var result = ""; |
| for (var i = 0; i < this.oid.length; ++i) { |
| if (i !== 0) |
| result += "."; |
| result += this.oid[i]; |
| } |
| |
| return result; |
| }; |
| |
| OID.prototype.equals = function(other) |
| { |
| if (!(other instanceof OID)) |
| return false; |
| if (this.oid.length !== other.oid.length) |
| return false; |
| |
| for (var i = 0; i < this.oid.length; ++i) { |
| if (this.oid[i] != other.oid[i]) |
| return false; |
| } |
| return true; |
| }; |
| /** |
| * Copyright (C) 2013-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * Create a WireFormat base class where the encode and decode methods throw an error. You should use a derived class like TlvWireFormat. |
| * @constructor |
| */ |
| var WireFormat = function WireFormat() { |
| }; |
| |
| exports.WireFormat = WireFormat; |
| |
| /** |
| * Encode name and return the encoding. Your derived class should override. |
| * @param {Name} name The Name to encode. |
| * @return {Blob} A Blob containing the encoding. |
| * @throws Error This always throws an "unimplemented" error. The derived class should override. |
| */ |
| WireFormat.prototype.encodeName = function(name) |
| { |
| throw new Error("encodeName is unimplemented in the base WireFormat class. You should use a derived class."); |
| }; |
| |
| /** |
| * Decode input as a name and set the fields of the Name object. |
| * Your derived class should override. |
| * @param {Name} name The Name object whose fields are updated. |
| * @param {Buffer} input The buffer with the bytes to decode. |
| * @param {boolean} copy (optional) If true, copy from the input when making new |
| * Blob values. If false, then Blob values share memory with the input, which |
| * must remain unchanged while the Blob values are used. If omitted, use true. |
| * @throws Error This always throws an "unimplemented" error. The derived class should override. |
| */ |
| WireFormat.prototype.decodeName = function(name, input, copy) |
| { |
| throw new Error("decodeName is unimplemented in the base WireFormat class. You should use a derived class."); |
| }; |
| |
| /** |
| * Encode interest and return the encoding. Your derived class should override. |
| * @param {Interest} interest The Interest to encode. |
| * @return {object} An associative array with fields |
| * (encoding, signedPortionBeginOffset, signedPortionEndOffset) where encoding |
| * is a Blob containing the encoding, signedPortionBeginOffset is the offset in |
| * the encoding of the beginning of the signed portion, and |
| * signedPortionEndOffset is the offset in the encoding of the end of the signed |
| * portion. The signed portion starts from the first name component and ends |
| * just before the final name component (which is assumed to be a signature for |
| * a signed interest). |
| * @throws Error This always throws an "unimplemented" error. The derived class should override. |
| */ |
| WireFormat.prototype.encodeInterest = function(interest) |
| { |
| throw new Error("encodeInterest is unimplemented in the base WireFormat class. You should use a derived class."); |
| }; |
| |
| /** |
| * Decode input as an interest and set the fields of the interest object. |
| * Your derived class should override. |
| * @param {Interest} interest The Interest object whose fields are updated. |
| * @param {Buffer} input The buffer with the bytes to decode. |
| * @param {boolean} copy (optional) If true, copy from the input when making new |
| * Blob values. If false, then Blob values share memory with the input, which |
| * must remain unchanged while the Blob values are used. If omitted, use true. |
| * @return {object} An associative array with fields |
| * (signedPortionBeginOffset, signedPortionEndOffset) where |
| * signedPortionBeginOffset is the offset in the encoding of the beginning of |
| * the signed portion, and signedPortionEndOffset is the offset in the encoding |
| * of the end of the signed portion. The signed portion starts from the first |
| * name component and ends just before the final name component (which is |
| * assumed to be a signature for a signed interest). |
| * @throws Error This always throws an "unimplemented" error. The derived class should override. |
| */ |
| WireFormat.prototype.decodeInterest = function(interest, input, copy) |
| { |
| throw new Error("decodeInterest is unimplemented in the base WireFormat class. You should use a derived class."); |
| }; |
| |
| /** |
| * Encode data and return the encoding and signed offsets. Your derived class |
| * should override. |
| * @param {Data} data The Data object to encode. |
| * @return {object} An associative array with fields |
| * (encoding, signedPortionBeginOffset, signedPortionEndOffset) where encoding |
| * is a Blob containing the encoding, signedPortionBeginOffset is the offset in |
| * the encoding of the beginning of the signed portion, and |
| * signedPortionEndOffset is the offset in the encoding of the end of the |
| * signed portion. |
| * @throws Error This always throws an "unimplemented" error. The derived class should override. |
| */ |
| WireFormat.prototype.encodeData = function(data) |
| { |
| throw new Error("encodeData is unimplemented in the base WireFormat class. You should use a derived class."); |
| }; |
| |
| /** |
| * Decode input as a data packet, set the fields in the data object, and return |
| * the signed offsets. Your derived class should override. |
| * @param {Data} data The Data object whose fields are updated. |
| * @param {Buffer} input The buffer with the bytes to decode. |
| * @param {boolean} copy (optional) If true, copy from the input when making new |
| * Blob values. If false, then Blob values share memory with the input, which |
| * must remain unchanged while the Blob values are used. If omitted, use true. |
| * @return {object} An associative array with fields |
| * (signedPortionBeginOffset, signedPortionEndOffset) where |
| * signedPortionBeginOffset is the offset in the encoding of the beginning of |
| * the signed portion, and signedPortionEndOffset is the offset in the encoding |
| * of the end of the signed portion. |
| * @throws Error This always throws an "unimplemented" error. The derived class should override. |
| */ |
| WireFormat.prototype.decodeData = function(data, input, copy) |
| { |
| throw new Error("decodeData is unimplemented in the base WireFormat class. You should use a derived class."); |
| }; |
| |
| /** |
| * Encode controlParameters and return the encoding. Your derived class should |
| * override. |
| * @param {ControlParameters} controlParameters The ControlParameters object to |
| * encode. |
| * @return {Blob} A Blob containing the encoding. |
| * @throws Error This always throws an "unimplemented" error. The derived class should override. |
| */ |
| WireFormat.prototype.encodeControlParameters = function(controlParameters) |
| { |
| throw new Error("encodeControlParameters is unimplemented in the base WireFormat class. You should use a derived class."); |
| }; |
| |
| /** |
| * Decode input as a controlParameters and set the fields of the |
| * controlParameters object. Your derived class should override. |
| * @param {ControlParameters} controlParameters The ControlParameters object |
| * whose fields are updated. |
| * @param {Buffer} input The buffer with the bytes to decode. |
| * @param {boolean} copy (optional) If true, copy from the input when making new |
| * Blob values. If false, then Blob values share memory with the input, which |
| * must remain unchanged while the Blob values are used. If omitted, use true. |
| * @throws Error This always throws an "unimplemented" error. The derived class should override. |
| */ |
| WireFormat.prototype.decodeControlParameters = function |
| (controlParameters, input, copy) |
| { |
| throw new Error("decodeControlParameters is unimplemented in the base WireFormat class. You should use a derived class."); |
| }; |
| |
| /** |
| * Encode controlResponse and return the encoding. Your derived class should |
| * override. |
| * @param {ControlResponse} controlResponse The ControlResponse object to |
| * encode. |
| * @return {Blob} A Blob containing the encoding. |
| * @throws Error This always throws an "unimplemented" error. The derived class should override. |
| */ |
| WireFormat.prototype.encodeControlResponse = function(controlResponse) |
| { |
| throw new Error("encodeControlResponse is unimplemented in the base WireFormat class. You should use a derived class."); |
| }; |
| |
| /** |
| * Decode input as a controlResponse and set the fields of the |
| * controlResponse object. Your derived class should override. |
| * @param {ControlResponse} controlResponse The ControlResponse object |
| * whose fields are updated. |
| * @param {Buffer} input The buffer with the bytes to decode. |
| * @param {boolean} copy (optional) If true, copy from the input when making new |
| * Blob values. If false, then Blob values share memory with the input, which |
| * must remain unchanged while the Blob values are used. If omitted, use true. |
| * @throws Error This always throws an "unimplemented" error. The derived class should override. |
| */ |
| WireFormat.prototype.decodeControlResponse = function |
| (controlResponse, input, copy) |
| { |
| throw new Error("decodeControlResponse is unimplemented in the base WireFormat class. You should use a derived class."); |
| }; |
| |
| /** |
| * Encode signature as a SignatureInfo and return the encoding. Your derived |
| * class should override. |
| * @param {Signature} signature An object of a subclass of Signature to encode. |
| * @return {Blob} A Blob containing the encoding. |
| * @throws Error This always throws an "unimplemented" error. The derived class should override. |
| */ |
| WireFormat.prototype.encodeSignatureInfo = function(signature) |
| { |
| throw new Error("encodeSignatureInfo is unimplemented in the base WireFormat class. You should use a derived class."); |
| }; |
| |
| /** |
| * Decode signatureInfo as a signature info and signatureValue as the related |
| * SignatureValue, and return a new object which is a subclass of Signature. |
| * Your derived class should override. |
| * @param {Buffer} signatureInfo The buffer with the signature info bytes to |
| * decode. |
| * @param {Buffer} signatureValue The buffer with the signature value to decode. |
| * @param {boolean} copy (optional) If true, copy from the input when making new |
| * Blob values. If false, then Blob values share memory with the input, which |
| * must remain unchanged while the Blob values are used. If omitted, use true. |
| * @return {Signature} A new object which is a subclass of Signature. |
| * @throws Error This always throws an "unimplemented" error. The derived class should override. |
| */ |
| WireFormat.prototype.decodeSignatureInfoAndValue = function |
| (signatureInfo, signatureValue, copy) |
| { |
| throw new Error("decodeSignatureInfoAndValue is unimplemented in the base WireFormat class. You should use a derived class."); |
| }; |
| |
| /** |
| * Encode the signatureValue in the Signature object as a SignatureValue (the |
| * signature bits) and return the encoding. Your derived class should override. |
| * @param {Signature} signature An object of a subclass of Signature with the |
| * signature value to encode. |
| * @return {Blob} A Blob containing the encoding. |
| * @throws Error This always throws an "unimplemented" error. The derived class should override. |
| */ |
| WireFormat.prototype.encodeSignatureValue = function(signature) |
| { |
| throw new Error("encodeSignatureValue is unimplemented in the base WireFormat class. You should use a derived class."); |
| }; |
| |
| /** |
| * Decode input as an NDN-TLV LpPacket and set the fields of the lpPacket object. |
| * Your derived class should override. |
| * @param {LpPacket} lpPacket The LpPacket object whose fields are updated. |
| * @param {Buffer} input The buffer with the bytes to decode. |
| * @param {boolean} copy (optional) If true, copy from the input when making new |
| * Blob values. If false, then Blob values share memory with the input, which |
| * must remain unchanged while the Blob values are used. If omitted, use true. |
| * @throws Error This always throws an "unimplemented" error. The derived class |
| * should override. |
| */ |
| WireFormat.prototype.decodeLpPacket = function(lpPacket, input, copy) |
| { |
| throw new Error |
| ("decodeLpPacket is unimplemented in the base WireFormat class. You should use a derived class."); |
| }; |
| |
| /** |
| * Encode the DelegationSet and return the encoding. Your derived class |
| * should override. |
| * @param {DelegationSet} delegationSet The DelegationSet object to |
| * encode. |
| * @return {Blob} A Blob containing the encoding. |
| * @throws Error This always throws an "unimplemented" error. The derived class |
| * should override. |
| */ |
| WireFormat.prototype.encodeDelegationSet = function(delegationSet) |
| { |
| throw new Error |
| ("encodeDelegationSet is unimplemented in the base WireFormat class. You should use a derived class."); |
| }; |
| |
| /** |
| * Decode input as an DelegationSet and set the fields of the |
| * delegationSet object. Your derived class should override. |
| * @param {DelegationSet} delegationSet The DelegationSet object |
| * whose fields are updated. |
| * @param {Buffer} input The buffer with the bytes to decode. |
| * @param {boolean} copy (optional) If true, copy from the input when making new |
| * Blob values. If false, then Blob values share memory with the input, which |
| * must remain unchanged while the Blob values are used. If omitted, use true. |
| * @throws Error This always throws an "unimplemented" error. The derived class |
| * should override. |
| */ |
| WireFormat.prototype.decodeDelegationSet = function(delegationSet, input, copy) |
| { |
| throw new Error |
| ("decodeDelegationSet is unimplemented in the base WireFormat class. You should use a derived class."); |
| }; |
| |
| /** |
| * Encode the EncryptedContent and return the encoding. Your derived class |
| * should override. |
| * @param {EncryptedContent} encryptedContent The EncryptedContent object to |
| * encode. |
| * @return {Blob} A Blob containing the encoding. |
| * @throws Error This always throws an "unimplemented" error. The derived class |
| * should override. |
| */ |
| WireFormat.prototype.encodeEncryptedContent = function(encryptedContent) |
| { |
| throw new Error |
| ("encodeEncryptedContent is unimplemented in the base WireFormat class. You should use a derived class."); |
| }; |
| |
| /** |
| * Decode input as an EncryptedContent and set the fields of the |
| * encryptedContent object. Your derived class should override. |
| * @param {EncryptedContent} encryptedContent The EncryptedContent object |
| * whose fields are updated. |
| * @param {Buffer} input The buffer with the bytes to decode. |
| * @param {boolean} copy (optional) If true, copy from the input when making new |
| * Blob values. If false, then Blob values share memory with the input, which |
| * must remain unchanged while the Blob values are used. If omitted, use true. |
| * @throws Error This always throws an "unimplemented" error. The derived class |
| * should override. |
| */ |
| WireFormat.prototype.decodeEncryptedContent = function |
| (encryptedContent, input, copy) |
| { |
| throw new Error |
| ("decodeEncryptedContent is unimplemented in the base WireFormat class. You should use a derived class."); |
| }; |
| |
| /** |
| * Set the static default WireFormat used by default encoding and decoding |
| * methods. |
| * @param {WireFormat} wireFormat An object of a subclass of WireFormat. |
| */ |
| WireFormat.setDefaultWireFormat = function(wireFormat) |
| { |
| WireFormat.defaultWireFormat = wireFormat; |
| }; |
| |
| /** |
| * Return the default WireFormat used by default encoding and decoding methods |
| * which was set with setDefaultWireFormat. |
| * @return {WireFormat} An object of a subclass of WireFormat. |
| */ |
| WireFormat.getDefaultWireFormat = function() |
| { |
| return WireFormat.defaultWireFormat; |
| }; |
| |
| // Invoke TlvWireFormat to set the default format. |
| // Since tlv-wire-format.js includes this file, put this at the bottom |
| // to avoid problems with cycles of require. |
| var TlvWireFormat = require('./tlv-wire-format.js').TlvWireFormat; |
| /** |
| * Copyright (C) 2013-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var DataUtils = require('./data-utils.js').DataUtils; /** @ignore */ |
| var Tlv = require('./tlv/tlv.js').Tlv; /** @ignore */ |
| var TlvStructureDecoder = require('./tlv/tlv-structure-decoder.js').TlvStructureDecoder; /** @ignore */ |
| var DecodingException = require('./decoding-exception.js').DecodingException; /** @ignore */ |
| var NdnCommon = require('../util/ndn-common.js').NdnCommon; /** @ignore */ |
| var LOG = require('../log.js').Log.LOG; |
| |
| /** |
| * A ElementReader lets you call onReceivedData multiple times which uses a |
| * TlvStructureDecoder to detect the end of a TLV element and calls |
| * elementListener.onReceivedElement(element) with the element. This handles |
| * the case where a single call to onReceivedData may contain multiple elements. |
| * @constructor |
| * @param {object} elementListener An object with an onReceivedElement method. |
| */ |
| var ElementReader = function ElementReader(elementListener) |
| { |
| this.elementListener_ = elementListener; |
| this.dataParts_ = []; |
| this.tlvStructureDecoder_ = new TlvStructureDecoder(); |
| }; |
| |
| exports.ElementReader = ElementReader; |
| |
| /** |
| * Continue to read data until the end of an element, then call |
| * this.elementListener_.onReceivedElement(element). The buffer passed to |
| * onReceivedElement is only valid during this call. If you need the data |
| * later, you must copy. |
| * @param {Buffer} data The Buffer with the incoming element's bytes. |
| */ |
| ElementReader.prototype.onReceivedData = function(data) |
| { |
| // Process multiple elements in the data. |
| while (true) { |
| var gotElementEnd; |
| var offset; |
| |
| try { |
| if (this.dataParts_.length === 0) { |
| // This is the beginning of an element. |
| if (data.length <= 0) |
| // Wait for more data. |
| return; |
| } |
| |
| // Scan the input to check if a whole TLV element has been read. |
| this.tlvStructureDecoder_.seek(0); |
| gotElementEnd = this.tlvStructureDecoder_.findElementEnd(data); |
| offset = this.tlvStructureDecoder_.getOffset(); |
| } catch (ex) { |
| // Reset to read a new element on the next call. |
| this.dataParts_ = []; |
| this.tlvStructureDecoder_ = new TlvStructureDecoder(); |
| |
| throw ex; |
| } |
| |
| if (gotElementEnd) { |
| // Got the remainder of an element. Report to the caller. |
| var element; |
| if (this.dataParts_.length === 0) |
| element = data.slice(0, offset); |
| else { |
| this.dataParts_.push(data.slice(0, offset)); |
| element = DataUtils.concatArrays(this.dataParts_); |
| this.dataParts_ = []; |
| } |
| |
| // Reset to read a new element. Do this before calling onReceivedElement |
| // in case it throws an exception. |
| data = data.slice(offset, data.length); |
| this.tlvStructureDecoder_ = new TlvStructureDecoder(); |
| |
| this.elementListener_.onReceivedElement(element); |
| if (data.length == 0) |
| // No more data in the packet. |
| return; |
| |
| // else loop back to decode. |
| } |
| else { |
| // Save a copy. We will call concatArrays later. |
| var totalLength = data.length; |
| for (var i = 0; i < this.dataParts_.length; ++i) |
| totalLength += this.dataParts_[i].length; |
| if (totalLength > NdnCommon.MAX_NDN_PACKET_SIZE) { |
| // Reset to read a new element on the next call. |
| this.dataParts_ = []; |
| this.tlvStructureDecoder_ = new TlvStructureDecoder(); |
| |
| throw new DecodingException(new Error |
| ("The incoming packet exceeds the maximum limit Face.getMaxNdnPacketSize()")); |
| } |
| |
| this.dataParts_.push(new Buffer(data)); |
| if (LOG > 3) console.log('Incomplete packet received. Length ' + data.length + '. Wait for more input.'); |
| return; |
| } |
| } |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * Create a new DerDecodingException wrapping the given error object. |
| * Call with: throw new DerDecodingException(new Error("message")). |
| * @constructor |
| * @param {Error} error The exception created with new Error. |
| */ |
| function DerDecodingException(error) |
| { |
| if (error) { |
| error.__proto__ = DerDecodingException.prototype; |
| return error; |
| } |
| } |
| DerDecodingException.prototype = new Error(); |
| DerDecodingException.prototype.name = "DerDecodingException"; |
| |
| exports.DerDecodingException = DerDecodingException; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * Create a new DerEncodingException wrapping the given error object. |
| * Call with: throw new DerEncodingException(new Error("message")). |
| * @constructor |
| * @param {Error} error The exception created with new Error. |
| */ |
| function DerEncodingException(error) |
| { |
| if (error) { |
| error.__proto__ = DerEncodingException.prototype; |
| return error; |
| } |
| } |
| DerEncodingException.prototype = new Error(); |
| DerEncodingException.prototype.name = "DerEncodingException"; |
| |
| exports.DerEncodingException = DerEncodingException; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From PyNDN der.py by Adeola Bannis <thecodemaiden@gmail.com>. |
| * @author: Originally from code in ndn-cxx by Yingdi Yu <yingdi@cs.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * The DerNodeType enum defines the known DER node types. |
| */ |
| var DerNodeType = function DerNodeType() |
| { |
| } |
| |
| exports.DerNodeType = DerNodeType; |
| |
| DerNodeType.Eoc = 0; |
| DerNodeType.Boolean = 1; |
| DerNodeType.Integer = 2; |
| DerNodeType.BitString = 3; |
| DerNodeType.OctetString = 4; |
| DerNodeType.Null = 5; |
| DerNodeType.ObjectIdentifier = 6; |
| DerNodeType.ObjectDescriptor = 7; |
| DerNodeType.External = 40; |
| DerNodeType.Real = 9; |
| DerNodeType.Enumerated = 10; |
| DerNodeType.EmbeddedPdv = 43; |
| DerNodeType.Utf8String = 12; |
| DerNodeType.RelativeOid = 13; |
| DerNodeType.Sequence = 48; |
| DerNodeType.Set = 49; |
| DerNodeType.NumericString = 18; |
| DerNodeType.PrintableString = 19; |
| DerNodeType.T61String = 20; |
| DerNodeType.VideoTexString = 21; |
| DerNodeType.Ia5String = 22; |
| DerNodeType.UtcTime = 23; |
| DerNodeType.GeneralizedTime = 24; |
| DerNodeType.GraphicString = 25; |
| DerNodeType.VisibleString = 26; |
| DerNodeType.GeneralString = 27; |
| DerNodeType.UniversalString = 28; |
| DerNodeType.CharacterString = 29; |
| DerNodeType.BmpString = 30; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From PyNDN der_node.py by Adeola Bannis <thecodemaiden@gmail.com>. |
| * @author: Originally from code in ndn-cxx by Yingdi Yu <yingdi@cs.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var DynamicBuffer = require('../../util/dynamic-buffer.js').DynamicBuffer; /** @ignore */ |
| var Blob = require('../../util/blob.js').Blob; /** @ignore */ |
| var DerDecodingException = require('./der-decoding-exception.js').DerDecodingException; /** @ignore */ |
| var DerEncodingException = require('./der-encoding-exception.js').DerEncodingException; /** @ignore */ |
| var DerNodeType = require('./der-node-type.js').DerNodeType; |
| |
| /** |
| * DerNode implements the DER node types used in encoding/decoding DER-formatted |
| * data. |
| * |
| * Create a generic DER node with the given nodeType. This is a private |
| * constructor used by one of the public DerNode subclasses defined below. |
| * @param {number} nodeType One of the defined DER DerNodeType constants. |
| * @constructor |
| */ |
| var DerNode = function DerNode(nodeType) |
| { |
| this.nodeType_ = nodeType; |
| this.parent_ = null; |
| this.header_ = new Buffer(0); |
| this.payload_ = new DynamicBuffer(0); |
| this.payloadPosition_ = 0; |
| }; |
| |
| exports.DerNode = DerNode; |
| |
| /** |
| * Return the number of bytes in DER |
| * @return {number} |
| */ |
| DerNode.prototype.getSize = function() |
| { |
| return this.header_.length + this.payloadPosition_; |
| }; |
| |
| /** |
| * Encode the given size and update the header. |
| * @param {number} size |
| */ |
| DerNode.prototype.encodeHeader = function(size) |
| { |
| var buffer = new DynamicBuffer(10); |
| var bufferPosition = 0; |
| buffer.array[bufferPosition++] = this.nodeType_; |
| if (size < 0) |
| // We don't expect this to happen since this is an internal method and |
| // always called with the non-negative size() of some buffer. |
| throw new Error("encodeHeader: DER object has negative length"); |
| else if (size <= 127) |
| buffer.array[bufferPosition++] = size & 0xff; |
| else { |
| var tempBuf = new DynamicBuffer(10); |
| // We encode backwards from the back. |
| |
| var val = size; |
| var n = 0; |
| while (val != 0) { |
| ++n; |
| tempBuf.ensureLengthFromBack(n); |
| tempBuf.array[tempBuf.array.length - n] = val & 0xff; |
| val >>= 8; |
| } |
| var nTempBufBytes = n + 1; |
| tempBuf.ensureLengthFromBack(nTempBufBytes); |
| tempBuf.array[tempBuf.array.length - nTempBufBytes] = ((1<<7) | n) & 0xff; |
| |
| buffer.copy(tempBuf.slice(tempBuf.array.length - nTempBufBytes), bufferPosition); |
| bufferPosition += nTempBufBytes; |
| } |
| |
| this.header_ = buffer.slice(0, bufferPosition); |
| }; |
| |
| /** |
| * Extract the header from an input buffer and return the size. |
| * @param {Buffer} inputBuf The input buffer to read from. |
| * @param {number} startIdx The offset into the buffer. |
| * @return {number} The parsed size in the header. |
| */ |
| DerNode.prototype.decodeHeader = function(inputBuf, startIdx) |
| { |
| var idx = startIdx; |
| |
| var nodeType = inputBuf[idx] & 0xff; |
| idx += 1; |
| |
| this.nodeType_ = nodeType; |
| |
| var sizeLen = inputBuf[idx] & 0xff; |
| idx += 1; |
| |
| var header = new DynamicBuffer(10); |
| var headerPosition = 0; |
| header.array[headerPosition++] = nodeType; |
| header.array[headerPosition++] = sizeLen; |
| |
| var size = sizeLen; |
| var isLongFormat = (sizeLen & (1 << 7)) != 0; |
| if (isLongFormat) { |
| var lenCount = sizeLen & ((1<<7) - 1); |
| size = 0; |
| while (lenCount > 0) { |
| var b = inputBuf[idx]; |
| idx += 1; |
| header.ensureLength(headerPosition + 1); |
| header.array[headerPosition++] = b; |
| size = 256 * size + (b & 0xff); |
| lenCount -= 1; |
| } |
| } |
| |
| this.header_ = header.slice(0, headerPosition); |
| return size; |
| }; |
| |
| /** |
| * Get the raw data encoding for this node. |
| * @return {Blob} The raw data encoding. |
| */ |
| DerNode.prototype.encode = function() |
| { |
| var buffer = new Buffer(this.getSize()); |
| |
| this.header_.copy(buffer); |
| this.payload_.slice(0, this.payloadPosition_).copy(buffer, this.header_.length); |
| |
| return new Blob(buffer, false); |
| }; |
| |
| /** |
| * Decode and store the data from an input buffer. |
| * @param {Buffer} inputBuf The input buffer to read from. This reads from |
| * startIdx (regardless of the buffer's position) and does not change the |
| * position. |
| * @param {number} startIdx The offset into the buffer. |
| */ |
| DerNode.prototype.decode = function(inputBuf, startIdx) |
| { |
| var idx = startIdx; |
| var payloadSize = this.decodeHeader(inputBuf, idx); |
| var skipBytes = this.header_.length; |
| if (payloadSize > 0) { |
| idx += skipBytes; |
| this.payloadAppend(inputBuf.slice(idx, idx + payloadSize)); |
| } |
| }; |
| |
| /** |
| * Copy buffer to this.payload_ at this.payloadPosition_ and update |
| * this.payloadPosition_. |
| * @param {Buffer} buffer The buffer to copy. |
| */ |
| DerNode.prototype.payloadAppend = function(buffer) |
| { |
| this.payloadPosition_ = this.payload_.copy(buffer, this.payloadPosition_); |
| } |
| |
| /** |
| * Parse the data from the input buffer recursively and return the root as an |
| * object of a subclass of DerNode. |
| * @param {Buffer} inputBuf The input buffer to read from. |
| * @param {number} startIdx (optional) The offset into the buffer. If omitted, |
| * use 0. |
| * @return {DerNode} An object of a subclass of DerNode. |
| */ |
| DerNode.parse = function(inputBuf, startIdx) |
| { |
| if (startIdx == undefined) |
| startIdx = 0; |
| |
| var nodeType = inputBuf[startIdx] & 0xff; |
| // Don't increment idx. We're just peeking. |
| |
| var newNode; |
| if (nodeType === DerNodeType.Boolean) |
| newNode = new DerNode.DerBoolean(); |
| else if (nodeType === DerNodeType.Integer) |
| newNode = new DerNode.DerInteger(); |
| else if (nodeType === DerNodeType.BitString) |
| newNode = new DerNode.DerBitString(); |
| else if (nodeType === DerNodeType.OctetString) |
| newNode = new DerNode.DerOctetString(); |
| else if (nodeType === DerNodeType.Null) |
| newNode = new DerNode.DerNull(); |
| else if (nodeType === DerNodeType.ObjectIdentifier) |
| newNode = new DerNode.DerOid(); |
| else if (nodeType === DerNodeType.Sequence) |
| newNode = new DerNode.DerSequence(); |
| else if (nodeType === DerNodeType.PrintableString) |
| newNode = new DerNode.DerPrintableString(); |
| else if (nodeType === DerNodeType.GeneralizedTime) |
| newNode = new DerNode.DerGeneralizedTime(); |
| else |
| throw new DerDecodingException(new Error("Unimplemented DER type " + nodeType)); |
| |
| newNode.decode(inputBuf, startIdx); |
| return newNode; |
| }; |
| |
| /** |
| * Convert the encoded data to a standard representation. Overridden by some |
| * subclasses (e.g. DerBoolean). |
| * @return {Blob} The encoded data as a Blob. |
| */ |
| DerNode.prototype.toVal = function() |
| { |
| return this.encode(); |
| }; |
| |
| /** |
| * Get a copy of the payload bytes. |
| * @return {Blob} A copy of the payload. |
| */ |
| DerNode.prototype.getPayload = function() |
| { |
| return new Blob(this.payload_.slice(0, this.payloadPosition_), true); |
| }; |
| |
| /** |
| * If this object is a DerNode.DerSequence, get the children of this node. |
| * Otherwise, throw an exception. (DerSequence overrides to implement this |
| * method.) |
| * @return {Array<DerNode>} The children as an array of DerNode. |
| * @throws DerDecodingException if this object is not a DerSequence. |
| */ |
| DerNode.prototype.getChildren = function() |
| { |
| throw new DerDecodingException(new Error |
| ("getChildren: This DerNode is not DerSequence")); |
| }; |
| |
| /** |
| * Check that index is in bounds for the children list, return children[index]. |
| * @param {Array<DerNode>} children The list of DerNode, usually returned by |
| * another call to getChildren. |
| * @param {number} index The index of the children. |
| * @return {DerNode.DerSequence} children[index]. |
| * @throws DerDecodingException if index is out of bounds or if children[index] |
| * is not a DerSequence. |
| */ |
| DerNode.getSequence = function(children, index) |
| { |
| if (index < 0 || index >= children.length) |
| throw new DerDecodingException(new Error |
| ("getSequence: Child index is out of bounds")); |
| |
| if (!(children[index] instanceof DerNode.DerSequence)) |
| throw new DerDecodingException(new Error |
| ("getSequence: Child DerNode is not a DerSequence")); |
| |
| return children[index]; |
| }; |
| |
| /** |
| * A DerStructure extends DerNode to hold other DerNodes. |
| * Create a DerStructure with the given nodeType. This is a private |
| * constructor. To create an object, use DerSequence. |
| * @param {number} nodeType One of the defined DER DerNodeType constants. |
| */ |
| DerNode.DerStructure = function DerStructure(nodeType) |
| { |
| // Call the base constructor. |
| DerNode.call(this, nodeType); |
| |
| this.childChanged_ = false; |
| this.nodeList_ = []; // Of DerNode. |
| this.size_ = 0; |
| }; |
| DerNode.DerStructure.prototype = new DerNode(); |
| DerNode.DerStructure.prototype.name = "DerStructure"; |
| |
| /** |
| * Get the total length of the encoding, including children. |
| * @return {number} The total (header + payload) length. |
| */ |
| DerNode.DerStructure.prototype.getSize = function() |
| { |
| if (this.childChanged_) { |
| this.updateSize(); |
| this.childChanged_ = false; |
| } |
| |
| this.encodeHeader(this.size_); |
| return this.size_ + this.header_.length; |
| }; |
| |
| /** |
| * Get the children of this node. |
| * @return {Array<DerNode>} The children as an array of DerNode. |
| */ |
| DerNode.DerStructure.prototype.getChildren = function() |
| { |
| return this.nodeList_; |
| }; |
| |
| DerNode.DerStructure.prototype.updateSize = function() |
| { |
| var newSize = 0; |
| |
| for (var i = 0; i < this.nodeList_.length; ++i) { |
| var n = this.nodeList_[i]; |
| newSize += n.getSize(); |
| } |
| |
| this.size_ = newSize; |
| this.childChanged_ = false; |
| }; |
| |
| /** |
| * Add a child to this node. |
| * @param {DerNode} node The child node to add. |
| * @param {boolean} (optional) notifyParent Set to true to cause any containing |
| * nodes to update their size. If omitted, use false. |
| */ |
| DerNode.DerStructure.prototype.addChild = function(node, notifyParent) |
| { |
| node.parent_ = this; |
| this.nodeList_.push(node); |
| |
| if (notifyParent) { |
| if (this.parent_ != null) |
| this.parent_.setChildChanged(); |
| } |
| |
| this.childChanged_ = true; |
| }; |
| |
| /** |
| * Mark the child list as dirty, so that we update size when necessary. |
| */ |
| DerNode.DerStructure.prototype.setChildChanged = function() |
| { |
| if (this.parent_ != null) |
| this.parent_.setChildChanged(); |
| this.childChanged_ = true; |
| }; |
| |
| /** |
| * Override the base encode to return raw data encoding for this node and its |
| * children. |
| * @return {Blob} The raw data encoding. |
| */ |
| DerNode.DerStructure.prototype.encode = function() |
| { |
| var buffer = new DynamicBuffer(10); |
| var bufferPosition = 0; |
| this.updateSize(); |
| this.encodeHeader(this.size_); |
| bufferPosition = buffer.copy(this.header_, bufferPosition); |
| |
| for (var i = 0; i < this.nodeList_.length; ++i) { |
| var n = this.nodeList_[i]; |
| var encodedChild = n.encode(); |
| bufferPosition = buffer.copy(encodedChild.buf(), bufferPosition); |
| } |
| |
| return new Blob(buffer.slice(0, bufferPosition), false); |
| }; |
| |
| /** |
| * Override the base decode to decode and store the data from an input |
| * buffer. Recursively populates child nodes. |
| * @param {Buffer} inputBuf The input buffer to read from. |
| * @param {number} startIdx The offset into the buffer. |
| */ |
| DerNode.DerStructure.prototype.decode = function(inputBuf, startIdx) |
| { |
| var idx = startIdx; |
| this.size_ = this.decodeHeader(inputBuf, idx); |
| idx += this.header_.length; |
| |
| var accSize = 0; |
| while (accSize < this.size_) { |
| var node = DerNode.parse(inputBuf, idx); |
| var size = node.getSize(); |
| idx += size; |
| accSize += size; |
| this.addChild(node, false); |
| } |
| }; |
| |
| //////// |
| // Now for all the node types... |
| //////// |
| |
| /** |
| * A DerByteString extends DerNode to handle byte strings. |
| * Create a DerByteString with the given inputData and nodeType. This is a |
| * private constructor used by one of the public subclasses such as |
| * DerOctetString or DerPrintableString. |
| * @param {Buffer} inputData An input buffer containing the string to encode. |
| * @param {number} nodeType One of the defined DER DerNodeType constants. |
| */ |
| DerNode.DerByteString = function DerByteString(inputData, nodeType) |
| { |
| // Call the base constructor. |
| DerNode.call(this, nodeType); |
| |
| if (inputData != null) { |
| this.payloadAppend(inputData); |
| this.encodeHeader(inputData.length); |
| } |
| }; |
| DerNode.DerByteString.prototype = new DerNode(); |
| DerNode.DerByteString.prototype.name = "DerByteString"; |
| |
| /** |
| * Override to return just the byte string. |
| * @return {Blob} The byte string as a copy of the payload buffer. |
| */ |
| DerNode.DerByteString.prototype.toVal = function() |
| { |
| return this.getPayload(); |
| }; |
| |
| /** |
| * DerBoolean extends DerNode to encode a boolean value. |
| * Create a new DerBoolean for the value. |
| * @param {boolean} value The value to encode. |
| */ |
| DerNode.DerBoolean = function DerBoolean(value) |
| { |
| // Call the base constructor. |
| DerNode.call(this, DerNodeType.Boolean); |
| |
| if (value != undefined) { |
| var val = value ? 0xff : 0x00; |
| this.payload_.ensureLength(this.payloadPosition_ + 1); |
| this.payload_.array[this.payloadPosition_++] = val; |
| this.encodeHeader(1); |
| } |
| }; |
| DerNode.DerBoolean.prototype = new DerNode(); |
| DerNode.DerBoolean.prototype.name = "DerBoolean"; |
| |
| DerNode.DerBoolean.prototype.toVal = function() |
| { |
| var val = this.payload_.array[0]; |
| return val != 0x00; |
| }; |
| |
| /** |
| * DerInteger extends DerNode to encode an integer value. |
| * Create a new DerInteger for the value. |
| * @param {number|Buffer} integer The value to encode. If integer is a Buffer |
| * byte array of a positive integer, you must ensure that the first byte is less |
| * than 0x80. |
| */ |
| DerNode.DerInteger = function DerInteger(integer) |
| { |
| // Call the base constructor. |
| DerNode.call(this, DerNodeType.Integer); |
| |
| if (integer != undefined) { |
| if (Buffer.isBuffer(integer)) { |
| if (integer.length > 0 && integer[0] >= 0x80) |
| throw new DerEncodingException(new Error |
| ("DerInteger: Negative integers are not currently supported")); |
| |
| if (integer.length == 0) |
| this.payloadAppend(new Buffer([0])); |
| else |
| this.payloadAppend(integer); |
| } |
| else { |
| // JavaScript doesn't distinguish int from float, so round. |
| integer = Math.round(integer); |
| |
| if (integer < 0) |
| throw new DerEncodingException(new Error |
| ("DerInteger: Negative integers are not currently supported")); |
| |
| // Convert the integer to bytes the easy/slow way. |
| var temp = new DynamicBuffer(10); |
| // We encode backwards from the back. |
| var length = 0; |
| while (true) { |
| ++length; |
| temp.ensureLengthFromBack(length); |
| temp.array[temp.array.length - length] = integer & 0xff; |
| integer >>= 8; |
| |
| if (integer <= 0) |
| // We check for 0 at the end so we encode one byte if it is 0. |
| break; |
| } |
| |
| if (temp.array[temp.array.length - length] >= 0x80) { |
| // Make it a non-negative integer. |
| ++length; |
| temp.ensureLengthFromBack(length); |
| temp.array[temp.array.length - length] = 0; |
| } |
| |
| this.payloadAppend(temp.slice(temp.array.length - length)); |
| } |
| |
| this.encodeHeader(this.payloadPosition_); |
| } |
| }; |
| DerNode.DerInteger.prototype = new DerNode(); |
| DerNode.DerInteger.prototype.name = "DerInteger"; |
| |
| DerNode.DerInteger.prototype.toVal = function() |
| { |
| if (this.payloadPosition_ > 0 && this.payload_.array[0] >= 0x80) |
| throw new DerDecodingException(new Error |
| ("DerInteger: Negative integers are not currently supported")); |
| |
| var result = 0; |
| for (var i = 0; i < this.payloadPosition_; ++i) { |
| result <<= 8; |
| result += this.payload_.array[i]; |
| } |
| |
| return result; |
| }; |
| |
| /** |
| * A DerBitString extends DerNode to handle a bit string. |
| * Create a DerBitString with the given padding and inputBuf. |
| * @param {Buffer} inputBuf An input buffer containing the bit octets to encode. |
| * @param {number} paddingLen The number of bits of padding at the end of the bit |
| * string. Should be less than 8. |
| */ |
| DerNode.DerBitString = function DerBitString(inputBuf, paddingLen) |
| { |
| // Call the base constructor. |
| DerNode.call(this, DerNodeType.BitString); |
| |
| if (inputBuf != undefined) { |
| this.payload_.ensureLength(this.payloadPosition_ + 1); |
| this.payload_.array[this.payloadPosition_++] = paddingLen & 0xff; |
| this.payloadAppend(inputBuf); |
| this.encodeHeader(this.payloadPosition_); |
| } |
| }; |
| DerNode.DerBitString.prototype = new DerNode(); |
| DerNode.DerBitString.prototype.name = "DerBitString"; |
| |
| /** |
| * DerOctetString extends DerByteString to encode a string of bytes. |
| * Create a new DerOctetString for the inputData. |
| * @param {Buffer} inputData An input buffer containing the string to encode. |
| */ |
| DerNode.DerOctetString = function DerOctetString(inputData) |
| { |
| // Call the base constructor. |
| DerNode.DerByteString.call(this, inputData, DerNodeType.OctetString); |
| }; |
| DerNode.DerOctetString.prototype = new DerNode.DerByteString(); |
| DerNode.DerOctetString.prototype.name = "DerOctetString"; |
| |
| /** |
| * A DerNull extends DerNode to encode a null value. |
| * Create a DerNull. |
| */ |
| DerNode.DerNull = function DerNull() |
| { |
| // Call the base constructor. |
| DerNode.call(this, DerNodeType.Null); |
| this.encodeHeader(0); |
| }; |
| DerNode.DerNull.prototype = new DerNode(); |
| DerNode.DerNull.prototype.name = "DerNull"; |
| |
| /** |
| * A DerOid extends DerNode to represent an object identifier. |
| * Create a DerOid with the given object identifier. The object identifier |
| * string must begin with 0,1, or 2 and must contain at least 2 digits. |
| * @param {string|OID} oid The OID string or OID object to encode. |
| */ |
| DerNode.DerOid = function DerOid(oid) |
| { |
| // Call the base constructor. |
| DerNode.call(this, DerNodeType.ObjectIdentifier); |
| |
| if (oid != undefined) { |
| if (typeof oid === 'string') { |
| var splitString = oid.split("."); |
| var parts = []; |
| for (var i = 0; i < splitString.length; ++i) |
| parts.push(parseInt(splitString[i])); |
| |
| this.prepareEncoding(parts); |
| } |
| else |
| // Assume oid is of type OID. |
| this.prepareEncoding(oid.getIntegerList()); |
| } |
| }; |
| DerNode.DerOid.prototype = new DerNode(); |
| DerNode.DerOid.prototype.name = "DerOid"; |
| |
| /** |
| * Encode a sequence of integers into an OID object and set the payload. |
| * @param {Array<number>} value The array of integers. |
| */ |
| DerNode.DerOid.prototype.prepareEncoding = function(value) |
| { |
| var firstNumber; |
| if (value.length == 0) |
| throw new DerEncodingException(new Error("No integer in OID")); |
| else { |
| if (value[0] >= 0 && value[0] <= 2) |
| firstNumber = value[0] * 40; |
| else |
| throw new DerEncodingException(new Error("First integer in OID is out of range")); |
| } |
| |
| if (value.length >= 2) { |
| if (value[1] >= 0 && value[1] <= 39) |
| firstNumber += value[1]; |
| else |
| throw new DerEncodingException(new Error("Second integer in OID is out of range")); |
| } |
| |
| var encodedBuffer = new DynamicBuffer(10); |
| var encodedBufferPosition = 0; |
| encodedBufferPosition = encodedBuffer.copy |
| (DerNode.DerOid.encode128(firstNumber), encodedBufferPosition); |
| |
| if (value.length > 2) { |
| for (var i = 2; i < value.length; ++i) |
| encodedBufferPosition = encodedBuffer.copy |
| (DerNode.DerOid.encode128(value[i]), encodedBufferPosition); |
| } |
| |
| this.encodeHeader(encodedBufferPosition); |
| this.payloadAppend(encodedBuffer.slice(0, encodedBufferPosition)); |
| }; |
| |
| /** |
| * Compute the encoding for one part of an OID, where values greater than 128 |
| * must be encoded as multiple bytes. |
| * @param {number} value A component of an OID. |
| * @return {Buffer} The encoded buffer. |
| */ |
| DerNode.DerOid.encode128 = function(value) |
| { |
| var mask = (1 << 7) - 1; |
| var outBytes = new DynamicBuffer(10); |
| var outBytesLength = 0; |
| // We encode backwards from the back. |
| |
| if (value < 128) { |
| ++outBytesLength; |
| outBytes.array[outBytes.array.length - outBytesLength] = value & mask; |
| } |
| else { |
| ++outBytesLength; |
| outBytes.array[outBytes.array.length - outBytesLength] = value & mask; |
| value >>= 7; |
| |
| while (value != 0) { |
| ++outBytesLength; |
| outBytes.ensureLengthFromBack(outBytesLength); |
| outBytes.array[outBytes.array.length - outBytesLength] = |
| (value & mask) | (1 << 7); |
| value >>= 7; |
| } |
| } |
| |
| return outBytes.slice(outBytes.array.length - outBytesLength); |
| }; |
| |
| /** |
| * Convert an encoded component of the encoded OID to the original integer. |
| * @param {number} offset The offset into this node's payload. |
| * @param {Array<number>} skip Set skip[0] to the number of payload bytes to skip. |
| * @return {number} The original integer. |
| */ |
| DerNode.DerOid.prototype.decode128 = function(offset, skip) |
| { |
| var flagMask = 0x80; |
| var result = 0; |
| var oldOffset = offset; |
| |
| while ((this.payload_.array[offset] & flagMask) != 0) { |
| result = 128 * result + (this.payload_.array[offset] & 0xff) - 128; |
| offset += 1; |
| } |
| |
| result = result * 128 + (this.payload_.array[offset] & 0xff); |
| |
| skip[0] = offset - oldOffset + 1; |
| return result; |
| }; |
| |
| /** |
| * Override to return the string representation of the OID. |
| * @return {string} The string representation of the OID. |
| */ |
| DerNode.DerOid.prototype.toVal = function() |
| { |
| var offset = 0; |
| var components = []; // of number. |
| |
| while (offset < this.payloadPosition_) { |
| var skip = [0]; |
| var nextVal = this.decode128(offset, skip); |
| offset += skip[0]; |
| components.push(nextVal); |
| } |
| |
| // For some odd reason, the first digits are represented in one byte. |
| var firstByte = components[0]; |
| var firstDigit = Math.floor(firstByte / 40); |
| var secondDigit = firstByte % 40; |
| |
| var result = firstDigit + "." + secondDigit; |
| for (var i = 1; i < components.length; ++i) |
| result += "." + components[i]; |
| |
| return result; |
| }; |
| |
| /** |
| * A DerSequence extends DerStructure to contains an ordered sequence of other |
| * nodes. |
| * Create a DerSequence. |
| */ |
| DerNode.DerSequence = function DerSequence() |
| { |
| // Call the base constructor. |
| DerNode.DerStructure.call(this, DerNodeType.Sequence); |
| }; |
| DerNode.DerSequence.prototype = new DerNode.DerStructure(); |
| DerNode.DerSequence.prototype.name = "DerSequence"; |
| |
| /** |
| * A DerPrintableString extends DerByteString to handle a a printable string. No |
| * escaping or other modification is done to the string. |
| * Create a DerPrintableString with the given inputData. |
| * @param {Buffer} inputData An input buffer containing the string to encode. |
| */ |
| DerNode.DerPrintableString = function DerPrintableString(inputData) |
| { |
| // Call the base constructor. |
| DerNode.DerByteString.call(this, inputData, DerNodeType.PrintableString); |
| }; |
| DerNode.DerPrintableString.prototype = new DerNode.DerByteString(); |
| DerNode.DerPrintableString.prototype.name = "DerPrintableString"; |
| |
| /** |
| * A DerGeneralizedTime extends DerNode to represent a date and time, with |
| * millisecond accuracy. |
| * Create a DerGeneralizedTime with the given milliseconds since 1970. |
| * @param {number} msSince1970 The timestamp as milliseconds since Jan 1, 1970. |
| */ |
| DerNode.DerGeneralizedTime = function DerGeneralizedTime(msSince1970) |
| { |
| // Call the base constructor. |
| DerNode.call(this, DerNodeType.GeneralizedTime); |
| |
| if (msSince1970 != undefined) { |
| var derTime = DerNode.DerGeneralizedTime.toDerTimeString(msSince1970); |
| // Use Blob to convert to a Buffer. |
| this.payloadAppend(new Blob(derTime).buf()); |
| this.encodeHeader(this.payloadPosition_); |
| } |
| }; |
| DerNode.DerGeneralizedTime.prototype = new DerNode(); |
| DerNode.DerGeneralizedTime.prototype.name = "DerGeneralizedTime"; |
| |
| /** |
| * Convert a UNIX timestamp to the internal string representation. |
| * @param {type} msSince1970 Timestamp as milliseconds since Jan 1, 1970. |
| * @return {string} The string representation. |
| */ |
| DerNode.DerGeneralizedTime.toDerTimeString = function(msSince1970) |
| { |
| var utcTime = new Date(Math.round(msSince1970)); |
| return utcTime.getUTCFullYear() + |
| DerNode.DerGeneralizedTime.to2DigitString(utcTime.getUTCMonth() + 1) + |
| DerNode.DerGeneralizedTime.to2DigitString(utcTime.getUTCDate()) + |
| DerNode.DerGeneralizedTime.to2DigitString(utcTime.getUTCHours()) + |
| DerNode.DerGeneralizedTime.to2DigitString(utcTime.getUTCMinutes()) + |
| DerNode.DerGeneralizedTime.to2DigitString(utcTime.getUTCSeconds()) + |
| "Z"; |
| }; |
| |
| /** |
| * A private method to zero pad an integer to 2 digits. |
| * @param {number} x The number to pad. Assume it is a non-negative integer. |
| * @return {string} The padded string. |
| */ |
| DerNode.DerGeneralizedTime.to2DigitString = function(x) |
| { |
| var result = x.toString(); |
| return result.length === 1 ? "0" + result : result; |
| }; |
| |
| /** |
| * Override to return the milliseconds since 1970. |
| * @return {number} The timestamp value as milliseconds since 1970. |
| */ |
| DerNode.DerGeneralizedTime.prototype.toVal = function() |
| { |
| var timeStr = this.payload_.slice(0, this.payloadPosition_).toString(); |
| return Date.UTC |
| (parseInt(timeStr.substr(0, 4)), |
| parseInt(timeStr.substr(4, 2) - 1), |
| parseInt(timeStr.substr(6, 2)), |
| parseInt(timeStr.substr(8, 2)), |
| parseInt(timeStr.substr(10, 2)), |
| parseInt(timeStr.substr(12, 2))); |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * From PyNDN boost_info_parser by Adeola Bannis. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var fs = require('fs'); |
| |
| /** |
| * BoostInfoTree is provided for compatibility with the Boost INFO property list |
| * format used in ndn-cxx. |
| * |
| * Each node in the tree may have a name and a value as well as associated |
| * sub-trees. The sub-tree names are not unique, and so sub-trees are stored as |
| * dictionaries where the key is a sub-tree name and the values are the |
| * sub-trees sharing the same name. |
| * |
| * Nodes can be accessed with a path syntax, as long as nodes in the path do not |
| * contain the path separator '/' in their names. |
| * @constructor |
| */ |
| var BoostInfoTree = function BoostInfoTree(value, parent) |
| { |
| // subtrees is an array of {key: treeName, value: subtreeList} where |
| // treeName is a string and subtreeList is an array of BoostInfoTree. |
| // We can't use a dictionary because we want the keys to be in order. |
| this.subtrees = []; |
| this.value = value; |
| this.parent = parent; |
| |
| this.lastChild = null; |
| }; |
| |
| /** |
| * Insert a BoostInfoTree as a sub-tree with the given name. |
| * @param {string} treeName The name of the new sub-tree. |
| * @param {BoostInfoTree} newTree The sub-tree to add. |
| */ |
| BoostInfoTree.prototype.addSubtree = function(treeName, newTree) |
| { |
| var subtreeList = this.find(treeName); |
| if (subtreeList !== null) |
| subtreeList.push(newTree); |
| else |
| this.subtrees.push({key: treeName, value: [newTree]}); |
| |
| newTree.parent = this; |
| this.lastChild = newTree; |
| }; |
| |
| /** |
| * Create a new BoostInfo and insert it as a sub-tree with the given name. |
| * @param {string} treeName The name of the new sub-tree. |
| * @param {string} value The value associated with the new sub-tree. |
| * @return {BoostInfoTree} The created sub-tree. |
| */ |
| BoostInfoTree.prototype.createSubtree = function(treeName, value) |
| { |
| var newTree = new BoostInfoTree(value, this); |
| this.addSubtree(treeName, newTree); |
| return newTree; |
| }; |
| |
| /** |
| * Look up using the key and return a list of the subtrees. |
| * @param {string} key The key which may be a path separated with '/'. |
| * @return {Array<BoostInfoTree>} A new array with pointers to the subtrees. |
| */ |
| BoostInfoTree.prototype.get = function(key) |
| { |
| // Strip beginning '/'. |
| key = key.replace(/^\/+/, ""); |
| if (key.length === 0) |
| return [this]; |
| var path = key.split('/'); |
| |
| var subtrees = this.find(path[0]); |
| if (subtrees === null) |
| return []; |
| if (path.length == 1) |
| return subtrees.slice(0); |
| |
| var newPath = path.slice(1).join('/'); |
| var foundVals = []; |
| for (var i = 0; i < subtrees.length; ++i) { |
| var t = subtrees[i]; |
| var partial = t.get(newPath); |
| foundVals = foundVals.concat(partial); |
| } |
| return foundVals; |
| }; |
| |
| /** |
| * Look up using the key and return string value of the first subtree. |
| * @param {string} key The key which may be a path separated with '/'. |
| * @return {string} The string value or null if not found. |
| */ |
| BoostInfoTree.prototype.getFirstValue = function(key) |
| { |
| var list = this.get(key); |
| if (list.length >= 1) |
| return list[0].value; |
| else |
| return null; |
| }; |
| |
| BoostInfoTree.prototype.getValue = function() { return this.value; }; |
| |
| BoostInfoTree.prototype.getParent = function() { return this.parent; }; |
| |
| BoostInfoTree.prototype.getLastChild = function() { return this.lastChild; }; |
| |
| BoostInfoTree.prototype.prettyPrint = function(indentLevel) |
| { |
| indentLevel = indentLevel || 1; |
| |
| var prefix = Array(indentLevel + 1).join(' '); |
| var s = ""; |
| |
| if (this.parent != null) { |
| if (this.value && this.value.length > 0) |
| s += "\"" + this.value + "\""; |
| s += "\n"; |
| } |
| |
| if (this.subtrees.length > 0) { |
| if (this.parent) |
| s += prefix + "{\n"; |
| var nextLevel = Array(indentLevel + 2 + 1).join(' '); |
| for (var i = 0; i < this.subtrees.length; ++i) { |
| for (var iSubTree = 0; iSubTree < this.subtrees[i].value.length; ++iSubTree) |
| s += nextLevel + this.subtrees[i].key + " " + |
| this.subtrees[i].value[iSubTree].prettyPrint(indentLevel + 2); |
| } |
| |
| if (this.parent) |
| s += prefix + "}\n"; |
| } |
| |
| return s; |
| }; |
| |
| BoostInfoTree.prototype.toString = function() |
| { |
| return this.prettyPrint(); |
| }; |
| |
| /** |
| * Use treeName to find the array of BoostInfoTree in this.subtrees. |
| * @param {string} treeName The key in this.subtrees to search for. This does a |
| * flat search in subtrees_. It does not split by '/' into a path. |
| * @return {Array<BoostInfoTree>} A array of BoostInfoTree, or null if not found. |
| */ |
| BoostInfoTree.prototype.find = function(treeName) |
| { |
| for (var i = 0; i < this.subtrees.length; ++i) { |
| if (this.subtrees[i].key == treeName) |
| return this.subtrees[i].value; |
| } |
| |
| return null; |
| }; |
| |
| /** |
| * A BoostInfoParser reads files in Boost's INFO format and constructs a |
| * BoostInfoTree. |
| * @constructor |
| */ |
| var BoostInfoParser = function BoostInfoParser() |
| { |
| this.root = new BoostInfoTree(); |
| }; |
| |
| exports.BoostInfoParser = BoostInfoParser; |
| exports.BoostInfoTree = BoostInfoTree; // debug |
| |
| /** |
| * Add the contents of the file or input string to the root BoostInfoTree. There |
| * are two forms: |
| * read(fileName) reads fileName from the file system. |
| * read(input, inputName) reads from the input, in which case inputName is used |
| * only for log messages, etc. |
| * @param {string} fileName The path to the INFO file. |
| * @param {string} input The contents of the INFO file, with lines separated by |
| * "\n" or "\r\n". |
| * @param {string} inputName Use with input for log messages, etc. |
| */ |
| BoostInfoParser.prototype.read = function(fileNameOrInput, inputName) |
| { |
| var input; |
| if (typeof inputName == 'string') |
| input = fileNameOrInput; |
| else { |
| // No inputName, so assume the first arg is the file name. |
| var fileName = fileNameOrInput; |
| inputName = fileName; |
| input = fs.readFileSync(fileName).toString(); |
| } |
| |
| var ctx = this.root; |
| var thisParser = this; |
| input.split(/\r?\n/).forEach(function(line) { |
| ctx = thisParser.parseLine(line.trim(), ctx); |
| }); |
| }; |
| |
| /** |
| * Write the root tree of this BoostInfoParser as file in Boost's INFO format. |
| * @param {string} fileName The output path. |
| */ |
| BoostInfoParser.prototype.write = function(fileName) |
| { |
| fs.writeFileSync(fileName, "" + this.root); |
| }; |
| |
| /** |
| * Get the root tree of this parser. |
| * @return {BoostInfoTree} The root BoostInfoTree. |
| */ |
| BoostInfoParser.prototype.getRoot = function() { return this.root; }; |
| |
| /** |
| * Similar to Python's shlex.split, split s into an array of strings which are |
| * separated by whitespace, treating a string within quotes as a single entity |
| * regardless of whitespace between the quotes. Also allow a backslash to escape |
| * the next character. |
| * @param {string} s The input string to split. |
| * @return {Array<string>} An array of strings. |
| */ |
| BoostInfoParser.shlex_split = function(s) |
| { |
| var result = []; |
| if (s == "") |
| return result; |
| var whiteSpace = " \t\n\r"; |
| var iStart = 0; |
| |
| while (true) { |
| // Move iStart past whitespace. |
| while (whiteSpace.indexOf(s[iStart]) >= 0) { |
| iStart += 1; |
| if (iStart >= s.length) |
| // Done. |
| return result; |
| } |
| |
| // Move iEnd to the end of the token. |
| var iEnd = iStart; |
| var inQuotation = false; |
| var token = ""; |
| while (true) { |
| if (s[iEnd] == '\\') { |
| // Append characters up to the backslash, skip the backslash and |
| // move iEnd past the escaped character. |
| token += s.substring(iStart, iEnd); |
| iStart = iEnd + 1; |
| iEnd = iStart; |
| if (iEnd >= s.length) |
| // An unusual case: A backslash at the end of the string. |
| break; |
| } |
| else { |
| if (inQuotation) { |
| if (s[iEnd] == '\"') { |
| // Append characters up to the end quote and skip. |
| token += s.substring(iStart, iEnd); |
| iStart = iEnd + 1; |
| inQuotation = false; |
| } |
| } |
| else { |
| if (s[iEnd] == '\"') { |
| // Append characters up to the start quote and skip. |
| token += s.substring(iStart, iEnd); |
| iStart = iEnd + 1; |
| inQuotation = true; |
| } |
| else |
| if (whiteSpace.indexOf(s[iEnd]) >= 0) |
| break; |
| } |
| } |
| |
| iEnd += 1; |
| if (iEnd >= s.length) |
| break; |
| } |
| |
| token += s.substring(iStart, iEnd); |
| result.push(token); |
| if (iEnd >= s.length) |
| // Done. |
| return result; |
| |
| iStart = iEnd; |
| } |
| }; |
| |
| BoostInfoParser.prototype.parseLine = function(line, context) |
| { |
| // Skip blank lines and comments. |
| var commentStart = line.indexOf(';'); |
| if (commentStart >= 0) |
| line = line.substring(0, commentStart).trim(); |
| if (line.length == 0) |
| return context; |
| |
| // Usually we are expecting key and optional value. |
| var strings = BoostInfoParser.shlex_split(line); |
| var isSectionStart = false; |
| var isSectionEnd = false; |
| for (var i = 0; i < strings.length; ++i) { |
| isSectionStart = (isSectionStart || strings[i] == "{"); |
| isSectionEnd = (isSectionEnd || strings[i] == "}"); |
| } |
| |
| if (!isSectionStart && !isSectionEnd) { |
| var key = strings[0]; |
| var val; |
| if (strings.length > 1) |
| val = strings[1]; |
| context.createSubtree(key, val); |
| |
| return context; |
| } |
| |
| // OK, who is the joker who put a { on the same line as the key name?! |
| var sectionStart = line.indexOf('{'); |
| if (sectionStart > 0) { |
| var firstPart = line.substring(0, sectionStart); |
| var secondPart = line.substring(sectionStart); |
| |
| var ctx = this.parseLine(firstPart, context); |
| return this.parseLine(secondPart, ctx); |
| } |
| |
| // If we encounter a {, we are beginning a new context. |
| // TODO: Error if there was already a subcontext here. |
| if (line[0] == '{') { |
| context = context.getLastChild(); |
| return context; |
| } |
| |
| // If we encounter a }, we are ending a list context. |
| if (line[0] == '}') { |
| context = context.getParent(); |
| return context; |
| } |
| |
| throw runtime_error("BoostInfoParser: input line is malformed"); |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Name = require('../name.js').Name; /** @ignore */ |
| var InterestFilter = require('../interest-filter.js').InterestFilter; /** @ignore */ |
| var ForwardingFlags = require('../forwarding-flags.js').ForwardingFlags; /** @ignore */ |
| var WireFormat = require('../encoding/wire-format.js').WireFormat; /** @ignore */ |
| var LOG = require('../log.js').Log.LOG; |
| |
| /** |
| * A MemoryContentCache holds a set of Data packets and answers an Interest to |
| * return the correct Data packet. The cache is periodically cleaned up to |
| * remove each stale Data packet based on its FreshnessPeriod (if it has one). |
| * @note This class is an experimental feature. See the API docs for more detail at |
| * http://named-data.net/doc/ndn-ccl-api/memory-content-cache.html . |
| * |
| * Create a new MemoryContentCache to use the given Face. |
| * |
| * @param {Face} face The Face to use to call registerPrefix and |
| * setInterestFilter, and which will call this object's OnInterest callback. |
| * @param {number} cleanupIntervalMilliseconds (optional) The interval |
| * in milliseconds between each check to clean up stale content in the cache. If |
| * omitted, use a default of 1000 milliseconds. If this is a large number, then |
| * effectively the stale content will not be removed from the cache. |
| * @constructor |
| */ |
| var MemoryContentCache = function MemoryContentCache |
| (face, cleanupIntervalMilliseconds) |
| { |
| cleanupIntervalMilliseconds = (cleanupIntervalMilliseconds || 1000.0); |
| |
| this.face = face; |
| this.cleanupIntervalMilliseconds = cleanupIntervalMilliseconds; |
| this.nextCleanupTime = new Date().getTime() + cleanupIntervalMilliseconds; |
| |
| this.onDataNotFoundForPrefix = {}; /**< The map key is the prefix.toUri(). |
| The value is an OnInterest function. */ |
| this.interestFilterIdList = []; /**< elements are number */ |
| this.registeredPrefixIdList = []; /**< elements are number */ |
| this.noStaleTimeCache = []; /**< elements are MemoryContentCache.Content */ |
| this.staleTimeCache = []; /**< elements are MemoryContentCache.StaleTimeContent */ |
| //StaleTimeContent::Compare contentCompare_; |
| this.emptyComponent = new Name.Component(); |
| this.pendingInterestTable = []; |
| |
| var thisMemoryContentCache = this; |
| this.storePendingInterestCallback = function |
| (localPrefix, localInterest, localFace, localInterestFilterId, localFilter) { |
| thisMemoryContentCache.storePendingInterest(localInterest, localFace); |
| }; |
| }; |
| |
| exports.MemoryContentCache = MemoryContentCache; |
| |
| /** |
| * Call registerPrefix on the Face given to the constructor so that this |
| * MemoryContentCache will answer interests whose name has the prefix. |
| * Alternatively, if the Face's registerPrefix has already been called, |
| * then you can call this object's setInterestFilter. |
| * @param {Name} prefix The Name for the prefix to register. This copies the Name. |
| * @param {function} onRegisterFailed If this fails to register the prefix for |
| * any reason, this calls onRegisterFailed(prefix) where prefix is the prefix |
| * given to registerPrefix. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onRegisterSuccess (optional) When this receives a success |
| * message, this calls onRegisterSuccess[0](prefix, registeredPrefixId). If |
| * onRegisterSuccess is [null] or omitted, this does not use it. (As a special |
| * case, this optional parameter is supplied as an array of one function, |
| * instead of just a function, in order to detect when it is used instead of the |
| * following optional onDataNotFound function.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onDataNotFound (optional) If a data packet for an interest |
| * is not found in the cache, this forwards the interest by calling |
| * onDataNotFound(prefix, interest, face, interestFilterId, filter). Your |
| * callback can find the Data packet for the interest and call |
| * face.putData(data). If your callback cannot find the Data packet, it can |
| * optionally call storePendingInterest(interest, face) to store the pending |
| * interest in this object to be satisfied by a later call to add(data). If you |
| * want to automatically store all pending interests, you can simply use |
| * getStorePendingInterest() for onDataNotFound. If onDataNotFound is omitted or |
| * null, this does not use it. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {ForwardingFlags} flags (optional) See Face.registerPrefix. |
| * @param {WireFormat} wireFormat (optional) See Face.registerPrefix. |
| */ |
| MemoryContentCache.prototype.registerPrefix = function |
| (prefix, onRegisterFailed, onRegisterSuccess, onDataNotFound, flags, wireFormat) |
| { |
| var arg3 = onRegisterSuccess; |
| var arg4 = onDataNotFound; |
| var arg5 = flags; |
| var arg6 = wireFormat; |
| // arg3, arg4, arg5, arg6 may be: |
| // [OnRegisterSuccess], OnDataNotFound, ForwardingFlags, WireFormat |
| // [OnRegisterSuccess], OnDataNotFound, ForwardingFlags, null |
| // [OnRegisterSuccess], OnDataNotFound, WireFormat, null |
| // [OnRegisterSuccess], OnDataNotFound, null, null |
| // [OnRegisterSuccess], ForwardingFlags, WireFormat, null |
| // [OnRegisterSuccess], ForwardingFlags, null, null |
| // [OnRegisterSuccess], WireFormat, null, null |
| // [OnRegisterSuccess], null, null, null |
| // OnDataNotFound, ForwardingFlags, WireFormat, null |
| // OnDataNotFound, ForwardingFlags, null, null |
| // OnDataNotFound, WireFormat, null, null |
| // OnDataNotFound, null, null, null |
| // ForwardingFlags, WireFormat, null, null |
| // ForwardingFlags, null, null, null |
| // WireFormat, null, null, null |
| // null, null, null, null |
| if (typeof arg3 === "object" && arg3.length === 1 && |
| typeof arg3[0] === "function") |
| onRegisterSuccess = arg3[0]; |
| else |
| onRegisterSuccess = null; |
| |
| if (typeof arg3 === "function") |
| onDataNotFound = arg3; |
| else if (typeof arg4 === "function") |
| onDataNotFound = arg4; |
| else |
| onDataNotFound = null; |
| |
| if (arg3 instanceof ForwardingFlags) |
| flags = arg3; |
| else if (arg4 instanceof ForwardingFlags) |
| flags = arg4; |
| else if (arg5 instanceof ForwardingFlags) |
| flags = arg5; |
| else |
| flags = new ForwardingFlags(); |
| |
| if (arg3 instanceof WireFormat) |
| wireFormat = arg3; |
| else if (arg4 instanceof WireFormat) |
| wireFormat = arg4; |
| else if (arg5 instanceof WireFormat) |
| wireFormat = arg5; |
| else if (arg6 instanceof WireFormat) |
| wireFormat = arg6; |
| else |
| wireFormat = WireFormat.getDefaultWireFormat(); |
| |
| if (onDataNotFound) |
| this.onDataNotFoundForPrefix[prefix.toUri()] = onDataNotFound; |
| var registeredPrefixId = this.face.registerPrefix |
| (prefix, this.onInterest.bind(this), onRegisterFailed, onRegisterSuccess, |
| flags, wireFormat); |
| this.registeredPrefixIdList.push(registeredPrefixId); |
| }; |
| |
| /** |
| * Call setInterestFilter on the Face given to the constructor so that this |
| * MemoryContentCache will answer interests whose name matches the filter. |
| * There are two forms of setInterestFilter. |
| * The first form uses the exact given InterestFilter: |
| * setInterestFilter(filter, [onDataNotFound]). |
| * The second form creates an InterestFilter from the given prefix Name: |
| * setInterestFilter(prefix, [onDataNotFound]). |
| * @param {InterestFilter} filter The InterestFilter with a prefix and optional |
| * regex filter used to match the name of an incoming Interest. This makes a |
| * copy of filter. |
| * @param {Name} prefix The Name prefix used to match the name of an incoming |
| * Interest. |
| * @param {function} onDataNotFound (optional) If a data packet for an interest |
| * is not found in the cache, this forwards the interest by calling |
| * onDataNotFound(prefix, interest, face, interestFilterId, filter). Your |
| * callback can find the Data packet for the interest and call |
| * face.putData(data). If your callback cannot find the Data packet, it can |
| * optionally call storePendingInterest(interest, face) to store the pending |
| * interest in this object to be satisfied by a later call to add(data). If you |
| * want to automatically store all pending interests, you can simply use |
| * getStorePendingInterest() for onDataNotFound. If onDataNotFound is omitted or |
| * null, this does not use it. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| */ |
| MemoryContentCache.prototype.setInterestFilter = function |
| (filterOrPrefix, onDataNotFound) |
| { |
| if (onDataNotFound) { |
| var prefix; |
| if (typeof filterOrPrefix === 'object' && filterOrPrefix instanceof InterestFilter) |
| prefix = filterOrPrefix.getPrefix(); |
| else |
| prefix = filterOrPrefix; |
| this.onDataNotFoundForPrefix[prefix.toUri()] = onDataNotFound; |
| } |
| var interestFilterId = this.face.setInterestFilter |
| (filterOrPrefix, this.onInterest.bind(this)); |
| this.interestFilterIdList.push(interestFilterId); |
| }; |
| |
| /** |
| * Call Face.unsetInterestFilter and Face.removeRegisteredPrefix for all the |
| * prefixes given to the setInterestFilter and registerPrefix method on this |
| * MemoryContentCache object so that it will not receive interests any more. You |
| * can call this if you want to "shut down" this MemoryContentCache while your |
| * application is still running. |
| */ |
| MemoryContentCache.prototype.unregisterAll = function() |
| { |
| for (var i = 0; i < this.interestFilterIdList.length; ++i) |
| this.face.unsetInterestFilter(this.interestFilterIdList[i]); |
| this.interestFilterIdList = []; |
| |
| for (var i = 0; i < this.registeredPrefixIdList.length; ++i) |
| this.face.removeRegisteredPrefix(this.registeredPrefixIdList[i]); |
| this.registeredPrefixIdList = []; |
| |
| // Also clear each onDataNotFoundForPrefix given to registerPrefix. |
| this.onDataNotFoundForPrefix = {}; |
| }; |
| |
| /** |
| * Add the Data packet to the cache so that it is available to use to answer |
| * interests. If data.getMetaInfo().getFreshnessPeriod() is not null, set the |
| * staleness time to now plus data.getMetaInfo().getFreshnessPeriod(), which is |
| * checked during cleanup to remove stale content. This also checks if |
| * cleanupIntervalMilliseconds milliseconds have passed and removes stale |
| * content from the cache. After removing stale content, remove timed-out |
| * pending interests from storePendingInterest(), then if the added Data packet |
| * satisfies any interest, send it through the face and remove the interest |
| * from the pending interest table. |
| * @param {Data} data The Data packet object to put in the cache. This copies |
| * the fields from the object. |
| */ |
| MemoryContentCache.prototype.add = function(data) |
| { |
| this.doCleanup(); |
| |
| if (data.getMetaInfo().getFreshnessPeriod() != null && |
| data.getMetaInfo().getFreshnessPeriod() >= 0.0) { |
| // The content will go stale, so use staleTimeCache. |
| var content = new MemoryContentCache.StaleTimeContent(data); |
| // Insert into staleTimeCache, sorted on content.staleTimeMilliseconds. |
| // Search from the back since we expect it to go there. |
| var i = this.staleTimeCache.length - 1; |
| while (i >= 0) { |
| if (this.staleTimeCache[i].staleTimeMilliseconds <= content.staleTimeMilliseconds) |
| break; |
| --i; |
| } |
| // Element i is the greatest less than or equal to |
| // content.staleTimeMilliseconds, so insert after it. |
| this.staleTimeCache.splice(i + 1, 0, content); |
| } |
| else |
| // The data does not go stale, so use noStaleTimeCache. |
| this.noStaleTimeCache.push(new MemoryContentCache.Content(data)); |
| |
| // Remove timed-out interests and check if the data packet matches any pending |
| // interest. |
| // Go backwards through the list so we can erase entries. |
| var nowMilliseconds = new Date().getTime(); |
| for (var i = this.pendingInterestTable.length - 1; i >= 0; --i) { |
| if (this.pendingInterestTable[i].isTimedOut(nowMilliseconds)) { |
| this.pendingInterestTable.splice(i, 1); |
| continue; |
| } |
| if (this.pendingInterestTable[i].getInterest().matchesName(data.getName())) { |
| try { |
| // Send to the same face from the original call to onInterest. |
| // wireEncode returns the cached encoding if available. |
| this.pendingInterestTable[i].getFace().send(data.wireEncode().buf()); |
| } |
| catch (ex) { |
| if (LOG > 0) |
| console.log("" + ex); |
| return; |
| } |
| |
| // The pending interest is satisfied, so remove it. |
| this.pendingInterestTable.splice(i, 1); |
| } |
| } |
| }; |
| |
| /** |
| * Store an interest from an OnInterest callback in the internal pending |
| * interest table (normally because there is no Data packet available yet to |
| * satisfy the interest). add(data) will check if the added Data packet |
| * satisfies any pending interest and send it through the face. |
| * @param {Interest} interest The Interest for which we don't have a Data packet |
| * yet. You should not modify the interest after calling this. |
| * @param {Face} face The Face with the connection which received |
| * the interest. This comes from the OnInterest callback. |
| */ |
| MemoryContentCache.prototype.storePendingInterest = function(interest, face) |
| { |
| this.pendingInterestTable.push |
| (new MemoryContentCache.PendingInterest(interest, face)); |
| }; |
| |
| /** |
| * Return a callback to use for onDataNotFound in registerPrefix which simply |
| * calls storePendingInterest() to store the interest that doesn't match a |
| * Data packet. add(data) will check if the added Data packet satisfies any |
| * pending interest and send it. |
| * @return {function} A callback to use for onDataNotFound in registerPrefix(). |
| */ |
| MemoryContentCache.prototype.getStorePendingInterest = function() |
| { |
| return this.storePendingInterestCallback; |
| }; |
| |
| /** |
| * This is the OnInterest callback which is called when the library receives |
| * an interest whose name has the prefix given to registerPrefix. First check |
| * if cleanupIntervalMilliseconds milliseconds have passed and remove stale |
| * content from the cache. Then search the cache for the Data packet, matching |
| * any interest selectors including ChildSelector, and send the Data packet |
| * to the face. If no matching Data packet is in the cache, call |
| * the callback in onDataNotFoundForPrefix (if defined). |
| */ |
| MemoryContentCache.prototype.onInterest = function |
| (prefix, interest, face, interestFilterId, filter) |
| { |
| this.doCleanup(); |
| |
| var selectedComponent = 0; |
| var selectedEncoding = null; |
| // We need to iterate over both arrays. |
| var totalSize = this.staleTimeCache.length + this.noStaleTimeCache.length; |
| for (var i = 0; i < totalSize; ++i) { |
| var content; |
| if (i < this.staleTimeCache.length) |
| content = this.staleTimeCache[i]; |
| else |
| // We have iterated over the first array. Get from the second. |
| content = this.noStaleTimeCache[i - this.staleTimeCache.length]; |
| |
| if (interest.matchesName(content.getName())) { |
| if (interest.getChildSelector() < 0) { |
| // No child selector, so send the first match that we have found. |
| face.send(content.getDataEncoding()); |
| return; |
| } |
| else { |
| // Update selectedEncoding based on the child selector. |
| var component; |
| if (content.getName().size() > interest.getName().size()) |
| component = content.getName().get(interest.getName().size()); |
| else |
| component = this.emptyComponent; |
| |
| var gotBetterMatch = false; |
| if (selectedEncoding === null) |
| // Save the first match. |
| gotBetterMatch = true; |
| else { |
| if (interest.getChildSelector() == 0) { |
| // Leftmost child. |
| if (component.compare(selectedComponent) < 0) |
| gotBetterMatch = true; |
| } |
| else { |
| // Rightmost child. |
| if (component.compare(selectedComponent) > 0) |
| gotBetterMatch = true; |
| } |
| } |
| |
| if (gotBetterMatch) { |
| selectedComponent = component; |
| selectedEncoding = content.getDataEncoding(); |
| } |
| } |
| } |
| } |
| |
| if (selectedEncoding !== null) |
| // We found the leftmost or rightmost child. |
| face.send(selectedEncoding); |
| else { |
| // Call the onDataNotFound callback (if defined). |
| var onDataNotFound = this.onDataNotFoundForPrefix[prefix.toUri()]; |
| if (onDataNotFound) |
| onDataNotFound(prefix, interest, face, interestFilterId, filter); |
| } |
| }; |
| |
| /** |
| * Check if now is greater than nextCleanupTime and, if so, remove stale |
| * content from staleTimeCache and reset nextCleanupTime based on |
| * cleanupIntervalMilliseconds. Since add(Data) does a sorted insert into |
| * staleTimeCache, the check for stale data is quick and does not require |
| * searching the entire staleTimeCache. |
| */ |
| MemoryContentCache.prototype.doCleanup = function() |
| { |
| var now = new Date().getTime(); |
| if (now >= this.nextCleanupTime) { |
| // staleTimeCache is sorted on staleTimeMilliseconds, so we only need to |
| // erase the stale entries at the front, then quit. |
| while (this.staleTimeCache.length > 0 && this.staleTimeCache[0].isStale(now)) |
| this.staleTimeCache.shift(); |
| |
| this.nextCleanupTime = now + this.cleanupIntervalMilliseconds; |
| } |
| }; |
| |
| /** |
| * Content is a private class to hold the name and encoding for each entry |
| * in the cache. This base class is for a Data packet without a FreshnessPeriod. |
| * |
| * Create a new Content entry to hold data's name and wire encoding. |
| * @param {Data} data The Data packet whose name and wire encoding are copied. |
| */ |
| MemoryContentCache.Content = function MemoryContentCacheContent(data) |
| { |
| // Allow an undefined data so that StaleTimeContent can set the prototype. |
| if (data) { |
| // Copy the name. |
| this.name = new Name(data.getName()); |
| // wireEncode returns the cached encoding if available. |
| this.dataEncoding = data.wireEncode().buf(); |
| } |
| }; |
| |
| MemoryContentCache.Content.prototype.getName = function() { return this.name; }; |
| |
| MemoryContentCache.Content.prototype.getDataEncoding = function() { return this.dataEncoding; }; |
| |
| /** |
| * StaleTimeContent extends Content to include the staleTimeMilliseconds for |
| * when this entry should be cleaned up from the cache. |
| * |
| * Create a new StaleTimeContent to hold data's name and wire encoding as well |
| * as the staleTimeMilliseconds which is now plus |
| * data.getMetaInfo().getFreshnessPeriod(). |
| * @param {Data} data The Data packet whose name and wire encoding are copied. |
| */ |
| MemoryContentCache.StaleTimeContent = function MemoryContentCacheStaleTimeContent |
| (data) |
| { |
| // Call the base constructor. |
| MemoryContentCache.Content.call(this, data); |
| |
| // Set up staleTimeMilliseconds which is The time when the content becomse |
| // stale in milliseconds according to new Date().getTime(). |
| this.staleTimeMilliseconds = new Date().getTime() + |
| data.getMetaInfo().getFreshnessPeriod(); |
| }; |
| |
| MemoryContentCache.StaleTimeContent.prototype = new MemoryContentCache.Content(); |
| MemoryContentCache.StaleTimeContent.prototype.name = "StaleTimeContent"; |
| |
| /** |
| * Check if this content is stale. |
| * @param {number} nowMilliseconds The current time in milliseconds from |
| * new Date().getTime(). |
| * @return {boolean} True if this content is stale, otherwise false. |
| */ |
| MemoryContentCache.StaleTimeContent.prototype.isStale = function(nowMilliseconds) |
| { |
| return this.staleTimeMilliseconds <= nowMilliseconds; |
| }; |
| |
| /** |
| * A PendingInterest holds an interest which onInterest received but could |
| * not satisfy. When we add a new data packet to the cache, we will also check |
| * if it satisfies a pending interest. |
| */ |
| MemoryContentCache.PendingInterest = function MemoryContentCachePendingInterest |
| (interest, face) |
| { |
| this.interest = interest; |
| this.face = face; |
| |
| if (this.interest.getInterestLifetimeMilliseconds() >= 0.0) |
| this.timeoutMilliseconds = (new Date()).getTime() + |
| this.interest.getInterestLifetimeMilliseconds(); |
| else |
| this.timeoutMilliseconds = -1.0; |
| }; |
| |
| /** |
| * Return the interest given to the constructor. |
| */ |
| MemoryContentCache.PendingInterest.prototype.getInterest = function() |
| { |
| return this.interest; |
| }; |
| |
| /** |
| * Return the face given to the constructor. |
| */ |
| MemoryContentCache.PendingInterest.prototype.getFace = function() |
| { |
| return this.face; |
| }; |
| |
| /** |
| * Check if this interest is timed out. |
| * @param {number} nowMilliseconds The current time in milliseconds from |
| * new Date().getTime(). |
| * @return {boolean} True if this interest timed out, otherwise false. |
| */ |
| MemoryContentCache.PendingInterest.prototype.isTimedOut = function(nowMilliseconds) |
| { |
| return this.timeoutTimeMilliseconds >= 0.0 && |
| nowMilliseconds >= this.timeoutTimeMilliseconds; |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * From PyNDN ndn_regex.py by Adeola Bannis. |
| * Originally from Yingdi Yu <http://irl.cs.ucla.edu/~yingdi/>. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Name = require('../name.js').Name; |
| |
| /** |
| * An NdnRegexMatcher has static methods to convert an NDN regex |
| * (http://redmine.named-data.net/projects/ndn-cxx/wiki/Regex) to a JavaScript |
| * RegExp that can match against URIs. |
| * @constructor |
| */ |
| var NdnRegexMatcher = function NdnRegexMatcher() |
| { |
| }; |
| |
| exports.NdnRegexMatcher = NdnRegexMatcher; |
| |
| /** |
| * Determine if the provided NDN regex matches the given Name. |
| * @param {string} pattern The NDN regex. |
| * @param {Name} name The Name to match against the regex. |
| * @return {Object} The match object from String.match, or null if the pattern |
| * does not match. |
| */ |
| NdnRegexMatcher.match = function(pattern, name) |
| { |
| var nameUri = name.toUri(); |
| |
| pattern = NdnRegexMatcher.sanitizeSets(pattern); |
| |
| pattern = pattern.replace(/<>/g, "(?:<.+?>)"); |
| pattern = pattern.replace(/>/g, ""); |
| pattern = pattern.replace(/<(?!!)/g, "/"); |
| |
| return nameUri.match(new RegExp(pattern)); |
| }; |
| |
| NdnRegexMatcher.sanitizeSets = function(pattern) |
| { |
| var newPattern = pattern; |
| |
| // Positive sets can be changed to (comp1|comp2). |
| // Negative sets must be changed to negative lookahead assertions. |
| |
| var regex1 = /\[(\^?)(.*?)\]/g; |
| var match; |
| while ((match = regex1.exec(pattern)) !== null) { |
| // Insert | between components. |
| // Match 2 is the last match, so we use the hack of working backwards from |
| // lastIndex. If possible, this should be changed to a more direct solution. |
| var start = regex1.lastIndex - "]".length - match[2].length; |
| var end = start + match[2].length; |
| if (start - end === 0) |
| continue; |
| var oldStr = match[2]; |
| var newStr = oldStr.replace(/></g, ">|<"); |
| newPattern = newPattern.substr(0, start) + newStr + newPattern.substr(end); |
| } |
| |
| // Replace [] with (), or (?! ) for negative lookahead. |
| // If we use negative lookahead, we also have to consume one component. |
| var isNegative = newPattern.indexOf("[^") >= 0; |
| if (isNegative) { |
| newPattern = newPattern.replace(/\[\^/g, "(?:(?!"); |
| newPattern = newPattern.replace(/\]/g, ")(?:/.*)*)"); |
| } |
| else { |
| newPattern = newPattern.replace(/\[/g, "("); |
| newPattern = newPattern.replace(/\]/g, ")"); |
| } |
| |
| return newPattern; |
| }; |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From ndn-cxx util/segment-fetcher https://github.com/named-data/ndn-cxx |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Interest = require('../interest.js').Interest; /** @ignore */ |
| var Blob = require('./blob.js').Blob; /** @ignore */ |
| var KeyChain = require('../security/key-chain.js').KeyChain; /** @ignore */ |
| var NdnCommon = require('./ndn-common.js').NdnCommon; |
| |
| /** |
| * SegmentFetcher is a utility class to fetch the latest version of segmented data. |
| * |
| * SegmentFetcher assumes that the data is named /<prefix>/<version>/<segment>, |
| * where: |
| * - <prefix> is the specified name prefix, |
| * - <version> is an unknown version that needs to be discovered, and |
| * - <segment> is a segment number. (The number of segments is unknown and is |
| * controlled by the `FinalBlockId` field in at least the last Data packet. |
| * |
| * The following logic is implemented in SegmentFetcher: |
| * |
| * 1. Express the first Interest to discover the version: |
| * |
| * >> Interest: /<prefix>?ChildSelector=1&MustBeFresh=true |
| * |
| * 2. Infer the latest version of the Data: <version> = Data.getName().get(-2) |
| * |
| * 3. If the segment number in the retrieved packet == 0, go to step 5. |
| * |
| * 4. Send an Interest for segment 0: |
| * |
| * >> Interest: /<prefix>/<version>/<segment=0> |
| * |
| * 5. Keep sending Interests for the next segment while the retrieved Data does |
| * not have a FinalBlockId or the FinalBlockId != Data.getName().get(-1). |
| * |
| * >> Interest: /<prefix>/<version>/<segment=(N+1))> |
| * |
| * 6. Call the onComplete callback with a Blob that concatenates the content |
| * from all the segmented objects. |
| * |
| * If an error occurs during the fetching process, the onError callback is called |
| * with a proper error code. The following errors are possible: |
| * |
| * - `INTEREST_TIMEOUT`: if any of the Interests times out |
| * - `DATA_HAS_NO_SEGMENT`: if any of the retrieved Data packets don't have a segment |
| * as the last component of the name (not counting the implicit digest) |
| * - `SEGMENT_VERIFICATION_FAILED`: if any retrieved segment fails |
| * the user-provided VerifySegment callback or KeyChain verifyData. |
| * - `IO_ERROR`: for I/O errors when sending an Interest. |
| * |
| * In order to validate individual segments, a KeyChain needs to be supplied. |
| * If verifyData fails, the fetching process is aborted with |
| * SEGMENT_VERIFICATION_FAILED. If data validation is not required, pass null. |
| * |
| * Example: |
| * var onComplete = function(content) { ... } |
| * |
| * var onError = function(errorCode, message) { ... } |
| * |
| * var interest = new Interest(new Name("/data/prefix")); |
| * interest.setInterestLifetimeMilliseconds(1000); |
| * |
| * SegmentFetcher.fetch(face, interest, null, onComplete, onError); |
| * |
| * This is a private constructor to create a new SegmentFetcher to use the Face. |
| * An application should use SegmentFetcher.fetch. If validatorKeyChain is not |
| * null, use it and ignore verifySegment. After creating the SegmentFetcher, |
| * call fetchFirstSegment. |
| * @param {Face} face This calls face.expressInterest to fetch more segments. |
| * @param validatorKeyChain {KeyChain} If this is not null, use its verifyData |
| * instead of the verifySegment callback. |
| * @param {function} verifySegment When a Data packet is received this calls |
| * verifySegment(data) where data is a Data object. If it returns False then |
| * abort fetching and call onError with |
| * SegmentFetcher.ErrorCode.SEGMENT_VERIFICATION_FAILED. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onComplete When all segments are received, call |
| * onComplete(content) where content is a Blob which has the concatenation of |
| * the content of all the segments. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onError Call onError.onError(errorCode, message) for |
| * timeout or an error processing segments. errorCode is a value from |
| * SegmentFetcher.ErrorCode and message is a related string. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @constructor |
| */ |
| var SegmentFetcher = function SegmentFetcher |
| (face, validatorKeyChain, verifySegment, onComplete, onError) |
| { |
| this.face = face; |
| this.validatorKeyChain = validatorKeyChain; |
| this.verifySegment = verifySegment; |
| this.onComplete = onComplete; |
| this.onError = onError; |
| |
| this.contentParts = []; // of Buffer |
| }; |
| |
| exports.SegmentFetcher = SegmentFetcher; |
| |
| /** |
| * An ErrorCode value is passed in the onError callback. |
| */ |
| SegmentFetcher.ErrorCode = { |
| INTEREST_TIMEOUT: 1, |
| DATA_HAS_NO_SEGMENT: 2, |
| SEGMENT_VERIFICATION_FAILED: 3 |
| }; |
| |
| /** |
| * DontVerifySegment may be used in fetch to skip validation of Data packets. |
| */ |
| SegmentFetcher.DontVerifySegment = function(data) |
| { |
| return true; |
| }; |
| |
| /** |
| * Initiate segment fetching. For more details, see the documentation for the |
| * class. There are two forms of fetch: |
| * fetch(face, baseInterest, validatorKeyChain, onComplete, onError) |
| * and |
| * fetch(face, baseInterest, verifySegment, onComplete, onError) |
| * @param {Face} face This calls face.expressInterest to fetch more segments. |
| * @param {Interest} baseInterest An Interest for the initial segment of the |
| * requested data, where baseInterest.getName() has the name prefix. This |
| * interest may include a custom InterestLifetime and selectors that will |
| * propagate to all subsequent Interests. The only exception is that the initial |
| * Interest will be forced to include selectors "ChildSelector=1" and |
| * "MustBeFresh=true" which will be turned off in subsequent Interests. |
| * @param validatorKeyChain {KeyChain} When a Data packet is received this calls |
| * validatorKeyChain.verifyData(data). If validation fails then abortfetching |
| * and call onError with SEGMENT_VERIFICATION_FAILED. This does not make a copy |
| * of the KeyChain; the object must remain valid while fetching. |
| * If validatorKeyChain is null, this does not validate the data packet. |
| * @param {function} verifySegment When a Data packet is received this calls |
| * verifySegment(data) where data is a Data object. If it returns False then |
| * abort fetching and call onError with |
| * SegmentFetcher.ErrorCode.SEGMENT_VERIFICATION_FAILED. If data validation is |
| * not required, use SegmentFetcher.DontVerifySegment. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onComplete When all segments are received, call |
| * onComplete(content) where content is a Blob which has the concatenation of |
| * the content of all the segments. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onError Call onError.onError(errorCode, message) for |
| * timeout or an error processing segments. errorCode is a value from |
| * SegmentFetcher.ErrorCode and message is a related string. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| */ |
| SegmentFetcher.fetch = function |
| (face, baseInterest, validatorKeyChainOrVerifySegment, onComplete, onError) |
| { |
| if (validatorKeyChainOrVerifySegment == null || |
| validatorKeyChainOrVerifySegment instanceof KeyChain) |
| new SegmentFetcher |
| (face, validatorKeyChainOrVerifySegment, SegmentFetcher.DontVerifySegment, |
| onComplete, onError) |
| .fetchFirstSegment(baseInterest); |
| else |
| new SegmentFetcher |
| (face, null, validatorKeyChainOrVerifySegment, onComplete, onError) |
| .fetchFirstSegment(baseInterest); |
| }; |
| |
| SegmentFetcher.prototype.fetchFirstSegment = function(baseInterest) |
| { |
| var interest = new Interest(baseInterest); |
| interest.setChildSelector(1); |
| interest.setMustBeFresh(true); |
| var thisSegmentFetcher = this; |
| this.face.expressInterest |
| (interest, |
| function(originalInterest, data) |
| { thisSegmentFetcher.onData(originalInterest, data); }, |
| function(interest) { thisSegmentFetcher.onTimeout(interest); }); |
| }; |
| |
| SegmentFetcher.prototype.fetchNextSegment = function |
| (originalInterest, dataName, segment) |
| { |
| // Start with the original Interest to preserve any special selectors. |
| var interest = new Interest(originalInterest); |
| // Changing a field clears the nonce so that the library will generate a new |
| // one. |
| interest.setChildSelector(0); |
| interest.setMustBeFresh(false); |
| interest.setName(dataName.getPrefix(-1).appendSegment(segment)); |
| var thisSegmentFetcher = this; |
| this.face.expressInterest |
| (interest, function(originalInterest, data) |
| { thisSegmentFetcher.onData(originalInterest, data); }, |
| function(interest) { thisSegmentFetcher.onTimeout(interest); }); |
| }; |
| |
| SegmentFetcher.prototype.onData = function(originalInterest, data) |
| { |
| if (this.validatorKeyChain != null) { |
| try { |
| var thisSegmentFetcher = this; |
| this.validatorKeyChain.verifyData |
| (data, |
| function(localData) { |
| thisSegmentFetcher.onVerified(localData, originalInterest); |
| }, |
| this.onValidationFailed.bind(this)); |
| } catch (ex) { |
| console.log("Error in KeyChain.verifyData: " + ex); |
| } |
| } |
| else { |
| if (!this.verifySegment(data)) { |
| try { |
| this.onError |
| (SegmentFetcher.ErrorCode.SEGMENT_VERIFICATION_FAILED, |
| "Segment verification failed"); |
| } catch (ex) { |
| console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| return; |
| } |
| |
| this.onVerified(data, originalInterest); |
| } |
| }; |
| |
| SegmentFetcher.prototype.onVerified = function(data, originalInterest) |
| { |
| if (!SegmentFetcher.endsWithSegmentNumber(data.getName())) { |
| // We don't expect a name without a segment number. Treat it as a bad packet. |
| try { |
| this.onError |
| (SegmentFetcher.ErrorCode.DATA_HAS_NO_SEGMENT, |
| "Got an unexpected packet without a segment number: " + |
| data.getName().toUri()); |
| } catch (ex) { |
| console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| else { |
| var currentSegment = 0; |
| try { |
| currentSegment = data.getName().get(-1).toSegment(); |
| } |
| catch (ex) { |
| try { |
| this.onError |
| (SegmentFetcher.ErrorCode.DATA_HAS_NO_SEGMENT, |
| "Error decoding the name segment number " + |
| data.getName().get(-1).toEscapedString() + ": " + ex); |
| } catch (ex) { |
| console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| return; |
| } |
| |
| var expectedSegmentNumber = this.contentParts.length; |
| if (currentSegment != expectedSegmentNumber) |
| // Try again to get the expected segment. This also includes the case |
| // where the first segment is not segment 0. |
| this.fetchNextSegment |
| (originalInterest, data.getName(), expectedSegmentNumber); |
| else { |
| // Save the content and check if we are finished. |
| this.contentParts.push(data.getContent().buf()); |
| |
| if (data.getMetaInfo().getFinalBlockId().getValue().size() > 0) { |
| var finalSegmentNumber = 0; |
| try { |
| finalSegmentNumber = (data.getMetaInfo().getFinalBlockId().toSegment()); |
| } |
| catch (ex) { |
| try { |
| this.onError |
| (SegmentFetcher.ErrorCode.DATA_HAS_NO_SEGMENT, |
| "Error decoding the FinalBlockId segment number " + |
| data.getMetaInfo().getFinalBlockId().toEscapedString() + |
| ": " + ex); |
| } catch (ex) { |
| console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| return; |
| } |
| |
| if (currentSegment == finalSegmentNumber) { |
| // We are finished. |
| |
| // Concatenate to get content. |
| var content = Buffer.concat(this.contentParts); |
| try { |
| this.onComplete(new Blob(content, false)); |
| } catch (ex) { |
| console.log("Error in onComplete: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| return; |
| } |
| } |
| |
| // Fetch the next segment. |
| this.fetchNextSegment |
| (originalInterest, data.getName(), expectedSegmentNumber + 1); |
| } |
| } |
| } |
| |
| SegmentFetcher.prototype.onValidationFailed = function(data, reason) |
| { |
| try { |
| this.onError |
| (SegmentFetcher.ErrorCode.SEGMENT_VERIFICATION_FAILED, |
| "Segment verification failed for " + data.getName().toUri() + |
| " . Reason: " + reason); |
| } catch (ex) { |
| console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| }; |
| |
| SegmentFetcher.prototype.onTimeout = function(interest) |
| { |
| try { |
| this.onError |
| (SegmentFetcher.ErrorCode.INTEREST_TIMEOUT, |
| "Time out for interest " + interest.getName().toUri()); |
| } catch (ex) { |
| console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| }; |
| |
| /** |
| * Check if the last component in the name is a segment number. |
| * @param {Name} name The name to check. |
| * @return {boolean} True if the name ends with a segment number, otherwise false. |
| */ |
| SegmentFetcher.endsWithSegmentNumber = function(name) |
| { |
| return name.size() >= 1 && name.get(-1).isSegment(); |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * Transport is a base class for specific transport classes such as TcpTransport. |
| * @constructor |
| */ |
| var Transport = function Transport() |
| { |
| }; |
| |
| exports.Transport = Transport; |
| |
| /** |
| * Transport.ConnectionInfo is a base class for connection information used by |
| * subclasses of Transport. |
| */ |
| Transport.ConnectionInfo = function TransportConnectionInfo() |
| { |
| }; |
| |
| /** |
| * Determine whether this transport connecting according to connectionInfo is to |
| * a node on the current machine. This affects the processing of |
| * Face.registerPrefix(): if the NFD is local, registration occurs with the |
| * '/localhost/nfd...' prefix; if non-local, the library will attempt to use |
| * remote prefix registration using '/localhop/nfd...' |
| * @param {Transport.ConnectionInfo} connectionInfo A ConnectionInfo with the |
| * host to check. |
| * @param {function} onResult On success, this calls onResult(isLocal) where |
| * isLocal is true if the host is local, false if not. We use callbacks because |
| * this may need to do an asynchronous DNS lookup. |
| * @param {function} onError On failure for DNS lookup or other error, this |
| * calls onError(message) where message is an error string. |
| */ |
| Transport.prototype.isLocal = function(connectionInfo, onResult, onError) |
| { |
| onError("Transport.isLocal is not implemented"); |
| }; |
| /** |
| * Copyright (C) 2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: Wentao Shang |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * A MicroForwarderTransport extends Transport to connect to the browser's |
| * micro forwarder service. This assumes that the MicroForwarder extensions has |
| * been installed. |
| * @constructor |
| */ |
| var MicroForwarderTransport = function MicroForwarderTransport() |
| { |
| // Call the base constructor. |
| Transport.call(this); |
| |
| this.elementReader = null; |
| this.connectionInfo = null; // Read by Face. |
| this.onReceivedObject = null; |
| |
| var thisTransport = this; |
| window.addEventListener("message", function(event) { |
| // We only accept messages from ourselves |
| if (event.source != window) |
| return; |
| |
| if (event.data.type && (event.data.type == "FromMicroForwarderStub")) { |
| var obj = event.data.object; |
| if (obj.type && obj.type == "Buffer") { |
| if (thisTransport.elementReader != null) |
| thisTransport.elementReader.onReceivedData(new Buffer(obj.data)); |
| } |
| else { |
| if (thisTransport.onReceivedObject) |
| thisTransport.onReceivedObject(obj); |
| } |
| } |
| }, false); |
| }; |
| |
| MicroForwarderTransport.prototype = new Transport(); |
| MicroForwarderTransport.prototype.name = "MicroForwarderTransport"; |
| |
| /** |
| * Create a new MicroForwarderTransport.ConnectionInfo which extends |
| * Transport.ConnectionInfo to hold info for the micro forwarer connection. |
| */ |
| MicroForwarderTransport.ConnectionInfo = function MicroForwarderTransportConnectionInfo() |
| { |
| // Call the base constructor. |
| Transport.ConnectionInfo .call(this); |
| }; |
| |
| MicroForwarderTransport.ConnectionInfo.prototype = new Transport.ConnectionInfo(); |
| MicroForwarderTransport.ConnectionInfo.prototype.name = "MicroForwarderTransport.ConnectionInfo"; |
| |
| /** |
| * Check if the fields of this MicroForwarderTransport.ConnectionInfo equal the other |
| * MicroForwarderTransport.ConnectionInfo. |
| * @param {MicroForwarderTransport.ConnectionInfo} The other object to check. |
| * @return {boolean} True if the objects have equal fields, false if not. |
| */ |
| MicroForwarderTransport.ConnectionInfo.prototype.equals = function(other) |
| { |
| if (other == null) |
| return false; |
| return true; |
| }; |
| |
| MicroForwarderTransport.ConnectionInfo.prototype.toString = function() |
| { |
| return "{}"; |
| }; |
| |
| /** |
| * Set the onReceivedObject callback, replacing any previous callback. |
| * @param {function} onReceivedObject (optional) If supplied and the received |
| * object type field is not "Buffer" then just call this.onReceivedObject(obj). |
| * If this is null, then don't call it. |
| */ |
| MicroForwarderTransport.prototype.setOnReceivedObject = function(onReceivedObject) |
| { |
| this.onReceivedObject = onReceivedObject; |
| } |
| |
| /** |
| * Determine whether this transport connecting according to connectionInfo is to |
| * a node on the current machine. Unix transports are always local. |
| * @param {MicroForwarderTransport.ConnectionInfo} connectionInfo This is ignored. |
| * @param {function} onResult This calls onResult(true) because micro forwarder |
| * transports are always local. |
| * @param {function} onError This is ignored. |
| */ |
| MicroForwarderTransport.prototype.isLocal = function(connectionInfo, onResult, onError) |
| { |
| onResult(true); |
| }; |
| |
| /** |
| * Connect to the micro forwarder according to the info in connectionInfo. |
| * Listen on the connection to read an entire packet element and call |
| * elementListener.onReceivedElement(element). However, if the received object |
| * type field is not "Buffer" then just call this.onReceivedObject(obj). |
| * @param {MicroForwarderTransport.ConnectionInfo} connectionInfo |
| * @param {object} elementListener The elementListener with function |
| * onReceivedElement which must remain valid during the life of this object. |
| * @param {function} onopenCallback Once connected, call onopenCallback(). |
| * @param {function} onclosedCallback (optional) If the connection is closed by |
| * the remote host, call onclosedCallback(). If omitted or null, don't call it. |
| */ |
| MicroForwarderTransport.prototype.connect = function |
| (connectionInfo, elementListener, onopenCallback, onclosedCallback) |
| { |
| // The window listener is already set up. |
| this.elementReader = new ElementReader(elementListener); |
| this.connectionInfo = connectionInfo; |
| onopenCallback(); |
| }; |
| |
| /** |
| * Send the JavaScript over the connection created by connect. |
| * @param {object} obj The object to send. It should have a field "type". If |
| * "type" is "Buffer" then it is processed like an NDN packet. |
| */ |
| MicroForwarderTransport.prototype.sendObject = function(obj) |
| { |
| window.postMessage({ |
| type: "FromMicroForwarderTransport", |
| object: obj |
| }, "*"); |
| }; |
| |
| /** |
| * Send the buffer over the connection created by connect. |
| * @param {Buffer} buffer The bytes to send. |
| */ |
| MicroForwarderTransport.prototype.send = function(buffer) |
| { |
| if (this.connectionInfo == null) { |
| console.log("MicroForwarderTransport connection is not established."); |
| return; |
| } |
| |
| this.sendObject(buffer.toJSON()); |
| }; |
| /** |
| * Copyright (C) 2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: Wentao Shang |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * A RuntimePortTransport extends Transport to connect to a WebExtensions |
| * runtime.port. |
| * @param {function} onReceivedObject (optional) If supplied and the received |
| * object type field is not "Buffer" then just call this.onReceivedObject(obj). |
| * If this is null, then don't call it. |
| * @constructor |
| */ |
| var RuntimePortTransport = function RuntimePortTransport(onReceivedObject) |
| { |
| // Call the base constructor. |
| Transport.call(this); |
| |
| this.elementReader = null; |
| this.connectionInfo = null; // Read by Face. |
| this.onReceivedObject = onReceivedObject; |
| this.port = null; |
| }; |
| |
| RuntimePortTransport.prototype = new Transport(); |
| RuntimePortTransport.prototype.name = "RuntimePortTransport"; |
| |
| /** |
| * Create a new RuntimePortTransport.ConnectionInfo which extends |
| * Transport.ConnectionInfo to hold the runtime.port used to connect. |
| * @param {runtime.port} port The runtime.port object. |
| */ |
| RuntimePortTransport.ConnectionInfo = function RuntimePortTransportConnectionInfo |
| (port) |
| { |
| // Call the base constructor. |
| Transport.ConnectionInfo .call(this); |
| |
| this.port = port; |
| }; |
| |
| RuntimePortTransport.ConnectionInfo.prototype = new Transport.ConnectionInfo(); |
| RuntimePortTransport.ConnectionInfo.prototype.name = "RuntimePortTransport.ConnectionInfo"; |
| |
| /** |
| * Check if the fields of this RuntimePortTransport.ConnectionInfo equal the other |
| * RuntimePortTransport.ConnectionInfo. |
| * @param {RuntimePortTransport.ConnectionInfo} The other object to check. |
| * @return {boolean} True if the objects have equal fields, false if not. |
| */ |
| RuntimePortTransport.ConnectionInfo.prototype.equals = function(other) |
| { |
| if (other == null || other.port == undefined) |
| return false; |
| return this.port == other.port; |
| }; |
| |
| RuntimePortTransport.ConnectionInfo.prototype.toString = function() |
| { |
| return "{}"; |
| }; |
| |
| /** |
| * Set the onReceivedObject callback, replacing any previous callback. |
| * @param {function} onReceivedObject (optional) If supplied and the received |
| * object type field is not "Buffer" then just call this.onReceivedObject(obj). |
| * If this is null, then don't call it. |
| */ |
| RuntimePortTransport.prototype.setOnReceivedObject = function(onReceivedObject) |
| { |
| this.onReceivedObject = onReceivedObject; |
| } |
| |
| /** |
| * Determine whether this transport connecting according to connectionInfo is to |
| * a node on the current machine. RuntimePortTransport is always local. |
| * @param {RuntimePortTransport.ConnectionInfo} connectionInfo This is ignored. |
| * @param {function} onResult This calls onResult(true) because a runtime.port |
| * is always local. |
| * @param {function} onError This is ignored. |
| */ |
| RuntimePortTransport.prototype.isLocal = function(connectionInfo, onResult, onError) |
| { |
| onResult(true); |
| }; |
| |
| /** |
| * Connect to the runtime.port in connectionInfo. For a received object obj, if |
| * obj.type is "Buffer", read an entire packet element from obj.data and call |
| * elementListener.onReceivedElement(element). Otherwise just call |
| * onReceivedObject(obj) using the callback given to the constructor. |
| * @param {RuntimePortTransport.ConnectionInfo} connectionInfo The |
| * ConnectionInfo with the runtime.port. |
| * @param {object} elementListener The elementListener with function |
| * onReceivedElement which must remain valid during the life of this object. |
| * @param {function} onOpenCallback Once connected, call onOpenCallback(). |
| * @param {function} onClosedCallback (optional) If the connection is closed by |
| * the remote host, call onClosedCallback(). If omitted or null, don't call it. |
| */ |
| RuntimePortTransport.prototype.connect = function |
| (connectionInfo, elementListener, onOpenCallback, onClosedCallback) |
| { |
| // The window listener is already set up. |
| this.elementReader = new ElementReader(elementListener); |
| this.connectionInfo = connectionInfo; |
| this.port = this.connectionInfo.port; |
| |
| // Add a listener to wait for a message object from the tab |
| var thisTransport = this; |
| this.port.onMessage.addListener(function(obj) { |
| if (obj.type == "Buffer") |
| thisTransport.elementReader.onReceivedData |
| (Buffer.isBuffer(obj.data) ? obj.data : new Buffer(obj.data)); |
| else { |
| if (thisTransport.onReceivedObject != null) |
| thisTransport.onReceivedObject(obj); |
| } |
| }); |
| |
| this.port.onDisconnect.addListener(function() { |
| thisTransport.port = null; |
| if (onClosedCallback != null) |
| onClosedCallback(); |
| }); |
| |
| onOpenCallback(); |
| }; |
| |
| /** |
| * Send the JavaScript object over the connection created by connect. |
| * @param {object} obj The object to send. If it is a JSON Buffer then it is |
| * processed like an NDN packet. |
| */ |
| RuntimePortTransport.prototype.sendObject = function(obj) |
| { |
| if (this.port == null) { |
| console.log("RuntimePortTransport connection is not established."); |
| return; |
| } |
| |
| this.port.postMessage(obj); |
| }; |
| |
| /** |
| * Send the buffer over the connection created by connect. |
| * @param {Buffer} buffer The bytes to send. |
| */ |
| RuntimePortTransport.prototype.send = function(buffer) |
| { |
| this.sendObject(buffer.toJSON()); |
| }; |
| /** |
| * Copyright (C) 2013-2016 Regents of the University of California. |
| * @author: Wentao Shang |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var ElementReader = require('../encoding/element-reader.js').ElementReader; /** @ignore */ |
| var LOG = require('../log.js').Log.LOG; /** @ignore */ |
| var Transport = require('./transport.js').Transport; /** @ignore */ |
| var Face; |
| |
| /** |
| * @constructor |
| */ |
| var WebSocketTransport = function WebSocketTransport() |
| { |
| // Call the base constructor. |
| Transport.call(this); |
| |
| if (!WebSocket) |
| throw new Error("WebSocket support is not available on this platform."); |
| |
| this.ws = null; |
| this.connectionInfo = null; // Read by Face. |
| this.elementReader = null; |
| this.defaultGetConnectionInfo = Face.makeShuffledHostGetConnectionInfo |
| (["A.ws.ndn.ucla.edu", "B.ws.ndn.ucla.edu", "C.ws.ndn.ucla.edu", "D.ws.ndn.ucla.edu", |
| "E.ws.ndn.ucla.edu", "F.ws.ndn.ucla.edu", "G.ws.ndn.ucla.edu", "H.ws.ndn.ucla.edu", |
| "I.ws.ndn.ucla.edu", "J.ws.ndn.ucla.edu", "K.ws.ndn.ucla.edu", "L.ws.ndn.ucla.edu", |
| "M.ws.ndn.ucla.edu", "N.ws.ndn.ucla.edu"], |
| 9696, |
| function(host, port) { return new WebSocketTransport.ConnectionInfo(host, port); }); |
| }; |
| |
| WebSocketTransport.prototype = new Transport(); |
| WebSocketTransport.prototype.name = "WebSocketTransport"; |
| |
| WebSocketTransport.importFace = function(face){ |
| Face = face; |
| }; |
| |
| exports.WebSocketTransport = WebSocketTransport; |
| |
| /** |
| * Create a new WebSocketTransport.ConnectionInfo which extends |
| * Transport.ConnectionInfo to hold the host and port info for the WebSocket |
| * connection. |
| * @param {string} host The host for the connection. However, if the host string |
| * begins with "ws:" or "wss:", then ignore port and use the string as the full |
| * endpoint URI. |
| * @param {number} port (optional) The port number for the connection. If |
| * omitted, use 9696. |
| */ |
| WebSocketTransport.ConnectionInfo = function WebSocketTransportConnectionInfo |
| (host, port) |
| { |
| // Call the base constructor. |
| Transport.ConnectionInfo .call(this); |
| |
| port = (port !== undefined ? port : 9696); |
| |
| this.host = host; |
| this.port = port; |
| }; |
| |
| WebSocketTransport.ConnectionInfo.prototype = new Transport.ConnectionInfo(); |
| WebSocketTransport.ConnectionInfo.prototype.name = "WebSocketTransport.ConnectionInfo"; |
| |
| /** |
| * Check if the fields of this WebSocketTransport.ConnectionInfo equal the other |
| * WebSocketTransport.ConnectionInfo. |
| * @param {WebSocketTransport.ConnectionInfo} The other object to check. |
| * @return {boolean} True if the objects have equal fields, false if not. |
| */ |
| WebSocketTransport.ConnectionInfo.prototype.equals = function(other) |
| { |
| if (other == null || other.host == undefined || other.port == undefined) |
| return false; |
| return this.host == other.host && this.port == other.port; |
| }; |
| |
| WebSocketTransport.ConnectionInfo.prototype.toString = function() |
| { |
| if (this.hostIsUri()) |
| return "{ uri: " + this.host + " }"; |
| else |
| return "{ host: " + this.host + ", port: " + this.port + " }"; |
| }; |
| |
| WebSocketTransport.ConnectionInfo.prototype.hostIsUri = function() |
| { |
| return this.host.substr(0, 3) == "ws:" || |
| this.host.substr(0, 4) == "wss:"; |
| } |
| |
| /** |
| * Determine whether this transport connecting according to connectionInfo is to |
| * a node on the current machine. WebSocket transports are always non-local. |
| * @param {WebSocketTransport.ConnectionInfo} connectionInfo This is ignored. |
| * @param {function} onResult This calls onResult(false) because WebSocket |
| * transports are always non-local. |
| * @param {function} onError This is ignored. |
| */ |
| WebSocketTransport.prototype.isLocal = function(connectionInfo, onResult, onError) |
| { |
| onResult(false); |
| }; |
| |
| /** |
| * Connect to a WebSocket according to the info in connectionInfo. Listen on |
| * the port to read an entire packet element and call |
| * elementListener.onReceivedElement(element). Note: this connect method |
| * previously took a Face object which is deprecated and renamed as the method |
| * connectByFace. |
| * @param {WebSocketTransport.ConnectionInfo} connectionInfo A |
| * WebSocketTransport.ConnectionInfo. |
| * @param {object} elementListener The elementListener with function |
| * onReceivedElement which must remain valid during the life of this object. |
| * @param {function} onopenCallback Once connected, call onopenCallback(). |
| * @param {function} onclosedCallback (optional) If the connection is closed by |
| * the remote host, call onclosedCallback(). If omitted or null, don't call it. |
| */ |
| WebSocketTransport.prototype.connect = function |
| (connectionInfo, elementListener, onopenCallback, onclosedCallback) |
| { |
| this.close(); |
| |
| var uri = connectionInfo.hostIsUri() ? |
| connectionInfo.host : 'ws://' + connectionInfo.host + ':' + connectionInfo.port; |
| this.ws = new WebSocket(uri); |
| if (LOG > 0) console.log('ws connection created.'); |
| this.connectionInfo = connectionInfo; |
| |
| this.ws.binaryType = "arraybuffer"; |
| |
| this.elementReader = new ElementReader(elementListener); |
| var self = this; |
| this.ws.onmessage = function(ev) { |
| var result = ev.data; |
| //console.log('RecvHandle called.'); |
| |
| if (result == null || result == undefined || result == "") { |
| console.log('INVALID ANSWER'); |
| } |
| else if (result instanceof ArrayBuffer) { |
| // The Buffer constructor expects an instantiated array. |
| var bytearray = new Buffer(new Uint8Array(result)); |
| |
| if (LOG > 3) console.log('BINARY RESPONSE IS ' + bytearray.toString('hex')); |
| |
| try { |
| // Find the end of the element and call onReceivedElement. |
| self.elementReader.onReceivedData(bytearray); |
| } catch (ex) { |
| console.log("NDN.ws.onmessage exception: " + ex); |
| return; |
| } |
| } |
| } |
| |
| this.ws.onopen = function(ev) { |
| if (LOG > 3) console.log(ev); |
| if (LOG > 3) console.log('ws.onopen: WebSocket connection opened.'); |
| if (LOG > 3) console.log('ws.onopen: ReadyState: ' + this.readyState); |
| // Face.registerPrefix will fetch the ndndid when needed. |
| |
| onopenCallback(); |
| } |
| |
| this.ws.onerror = function(ev) { |
| console.log('ws.onerror: ReadyState: ' + this.readyState); |
| console.log(ev); |
| console.log('ws.onerror: WebSocket error: ' + ev.data); |
| } |
| |
| this.ws.onclose = function(ev) { |
| console.log('ws.onclose: WebSocket connection closed.'); |
| self.ws = null; |
| |
| if (onclosedCallback != null) |
| onclosedCallback(); |
| } |
| }; |
| |
| /** |
| * @deprecated This is deprecated. You should not call Transport.connect |
| * directly, since it is called by Face methods. |
| */ |
| WebSocketTransport.prototype.connectByFace = function(face, onopenCallback) |
| { |
| this.connect |
| (face.connectionInfo, face, onopenCallback, |
| function() { face.closeByTransport(); }); |
| }; |
| |
| /** |
| * Send the Uint8Array data. |
| */ |
| WebSocketTransport.prototype.send = function(data) |
| { |
| if (this.ws != null) { |
| // If we directly use data.buffer to feed ws.send(), |
| // WebSocket may end up sending a packet with 10000 bytes of data. |
| // That is, WebSocket will flush the entire buffer |
| // regardless of the offset of the Uint8Array. So we have to create |
| // a new Uint8Array buffer with just the right size and copy the |
| // content from binaryInterest to the new buffer. |
| // ---Wentao |
| var bytearray = new Uint8Array(data.length); |
| bytearray.set(data); |
| this.ws.send(bytearray.buffer); |
| if (LOG > 3) console.log('ws.send() returned.'); |
| } |
| else |
| console.log('WebSocket connection is not established.'); |
| }; |
| |
| /** |
| * Close the connection. |
| */ |
| WebSocketTransport.prototype.close = function() |
| { |
| if (this.ws != null) |
| delete this.ws; |
| } |
| |
| /** |
| * Copyright (C) 2013-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| // The Face constructor uses TcpTransport by default which is not available in the browser, so override to WebSocketTransport. |
| exports.TcpTransport = require("./transport/web-socket-transport").WebSocketTransport; |
| /** |
| * This class represents a Name as an array of components where each is a byte array. |
| * Copyright (C) 2013-2016 Regents of the University of California. |
| * @author: Meki Cheraoui, Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Blob = require('./util/blob.js').Blob; /** @ignore */ |
| var DataUtils = require('./encoding/data-utils.js').DataUtils; /** @ignore */ |
| var LOG = require('./log.js').Log.LOG; |
| var DecodingException = require('./encoding/decoding-exception.js').DecodingException; |
| |
| /** |
| * Create a new Name from components. |
| * |
| * @constructor |
| * @param {string|Name|Array<string|Array<number>|ArrayBuffer|Buffer|Name>} components if a string, parse it as a URI. If a Name, add a deep copy of its components. |
| * Otherwise it is an array of components which are appended according to Name.append, so |
| * convert each and store it as an array of Buffer. If a component is a string, encode as utf8. |
| */ |
| var Name = function Name(components) |
| { |
| if (typeof components == 'string') { |
| if (LOG > 3) console.log('Content Name String ' + components); |
| this.components = Name.createNameArray(components); |
| } |
| else if (typeof components === 'object') { |
| this.components = []; |
| if (components instanceof Name) |
| this.append(components); |
| else { |
| for (var i = 0; i < components.length; ++i) |
| this.append(components[i]); |
| } |
| } |
| else if (components == null) |
| this.components = []; |
| else |
| if (LOG > 1) console.log("NO CONTENT NAME GIVEN"); |
| |
| this.changeCount = 0; |
| }; |
| |
| exports.Name = Name; |
| |
| /** |
| * Create a new GENERIC Name.Component with a copy of the given value. |
| * (To create an ImplicitSha256Digest component, use fromImplicitSha256Digest.) |
| * @param {Name.Component|String|Array<number>|ArrayBuffer|Buffer} value If the value is a string, encode it as utf8 (but don't unescape). |
| * @constructor |
| */ |
| Name.Component = function NameComponent(value) |
| { |
| if (typeof value === 'object' && value instanceof Name.Component) { |
| // The copy constructor. |
| this.value_ = value.value_; |
| this.type_ = value.type_; |
| return; |
| } |
| |
| if (!value) |
| this.value_ = new Blob([]); |
| else if (typeof value === 'object' && typeof ArrayBuffer !== 'undefined' && |
| value instanceof ArrayBuffer) |
| // Make a copy. Turn the value into a Uint8Array since the Buffer |
| // constructor doesn't take an ArrayBuffer. |
| this.value_ = new Blob(new Buffer(new Uint8Array(value)), false); |
| else if (typeof value === 'object' && value instanceof Blob) |
| this.value_ = value; |
| else |
| // Blob will make a copy if needed. |
| this.value_ = new Blob(value); |
| |
| this.type_ = Name.Component.ComponentType.GENERIC; |
| }; |
| |
| /** |
| * A Name.Component.ComponentType specifies the recognized types of a name |
| * component. |
| */ |
| Name.Component.ComponentType = { |
| IMPLICIT_SHA256_DIGEST: 1, |
| GENERIC: 8 |
| }; |
| |
| /** |
| * Get the component value. |
| * @return {Blob} The component value. |
| */ |
| Name.Component.prototype.getValue = function() |
| { |
| return this.value_; |
| }; |
| |
| /** |
| * @deprecated Use getValue. This method returns a Buffer which is the former |
| * behavior of getValue, and should only be used while updating your code. |
| */ |
| Name.Component.prototype.getValueAsBuffer = function() |
| { |
| // Assume the caller won't modify it. |
| return this.value_.buf(); |
| }; |
| |
| /** |
| * @deprecated Use getValue which returns a Blob. |
| */ |
| Object.defineProperty(Name.Component.prototype, "value", |
| { get: function() { return this.getValueAsBuffer(); } }); |
| |
| /** |
| * Convert this component value to a string by escaping characters according to the NDN URI Scheme. |
| * This also adds "..." to a value with zero or more ".". |
| * This adds a type code prefix as needed, such as "sha256digest=". |
| * @return {string} The escaped string. |
| */ |
| Name.Component.prototype.toEscapedString = function() |
| { |
| if (this.type_ === Name.Component.ComponentType.IMPLICIT_SHA256_DIGEST) |
| return "sha256digest=" + this.value_.toHex(); |
| else |
| return Name.toEscapedString(this.value_.buf()); |
| }; |
| |
| /** |
| * Check if this component is a segment number according to NDN naming |
| * conventions for "Segment number" (marker 0x00). |
| * http://named-data.net/doc/tech-memos/naming-conventions.pdf |
| * @return {number} True if this is a segment number. |
| */ |
| Name.Component.prototype.isSegment = function() |
| { |
| return this.value_.size() >= 1 && this.value_.buf()[0] == 0x00 && |
| this.isGeneric(); |
| }; |
| |
| /** |
| * Check if this component is a segment byte offset according to NDN |
| * naming conventions for segment "Byte offset" (marker 0xFB). |
| * http://named-data.net/doc/tech-memos/naming-conventions.pdf |
| * @return True if this is a segment byte offset. |
| */ |
| Name.Component.prototype.isSegmentOffset = function() |
| { |
| return this.value_.size() >= 1 && this.value_.buf()[0] == 0xFB && |
| this.isGeneric(); |
| }; |
| |
| /** |
| * Check if this component is a version number according to NDN naming |
| * conventions for "Versioning" (marker 0xFD). |
| * http://named-data.net/doc/tech-memos/naming-conventions.pdf |
| * @return {number} True if this is a version number. |
| */ |
| Name.Component.prototype.isVersion = function() |
| { |
| return this.value_.size() >= 1 && this.value_.buf()[0] == 0xFD && |
| this.isGeneric(); |
| }; |
| |
| /** |
| * Check if this component is a timestamp according to NDN naming |
| * conventions for "Timestamp" (marker 0xFC). |
| * http://named-data.net/doc/tech-memos/naming-conventions.pdf |
| * @return True if this is a timestamp. |
| */ |
| Name.Component.prototype.isTimestamp = function() |
| { |
| return this.value_.size() >= 1 && this.value_.buf()[0] == 0xFC && |
| this.isGeneric(); |
| }; |
| |
| /** |
| * Check if this component is a sequence number according to NDN naming |
| * conventions for "Sequencing" (marker 0xFE). |
| * http://named-data.net/doc/tech-memos/naming-conventions.pdf |
| * @return True if this is a sequence number. |
| */ |
| Name.Component.prototype.isSequenceNumber = function() |
| { |
| return this.value_.size() >= 1 && this.value_.buf()[0] == 0xFE && |
| this.isGeneric(); |
| }; |
| |
| /** |
| * Check if this component is a generic component. |
| * @return {boolean} True if this is an generic component. |
| */ |
| Name.Component.prototype.isGeneric = function() |
| { |
| return this.type_ === Name.Component.ComponentType.GENERIC; |
| }; |
| |
| /** |
| * Check if this component is an ImplicitSha256Digest component. |
| * @return {boolean} True if this is an ImplicitSha256Digest component. |
| */ |
| Name.Component.prototype.isImplicitSha256Digest = function() |
| { |
| return this.type_ === Name.Component.ComponentType.IMPLICIT_SHA256_DIGEST; |
| }; |
| |
| /** |
| * Interpret this name component as a network-ordered number and return an integer. |
| * @return {number} The integer number. |
| */ |
| Name.Component.prototype.toNumber = function() |
| { |
| return DataUtils.bigEndianToUnsignedInt(this.value_.buf()); |
| }; |
| |
| /** |
| * Interpret this name component as a network-ordered number with a marker and |
| * return an integer. |
| * @param {number} marker The required first byte of the component. |
| * @return {number} The integer number. |
| * @throws Error If the first byte of the component does not equal the marker. |
| */ |
| Name.Component.prototype.toNumberWithMarker = function(marker) |
| { |
| if (this.value_.size() == 0 || this.value_.buf()[0] != marker) |
| throw new Error("Name component does not begin with the expected marker"); |
| |
| return DataUtils.bigEndianToUnsignedInt(this.value_.buf().slice(1)); |
| }; |
| |
| /** |
| * Interpret this name component as a segment number according to NDN naming |
| * conventions for "Segment number" (marker 0x00). |
| * http://named-data.net/doc/tech-memos/naming-conventions.pdf |
| * @return {number} The integer segment number. |
| * @throws Error If the first byte of the component is not the expected marker. |
| */ |
| Name.Component.prototype.toSegment = function() |
| { |
| return this.toNumberWithMarker(0x00); |
| }; |
| |
| /** |
| * Interpret this name component as a segment byte offset according to NDN |
| * naming conventions for segment "Byte offset" (marker 0xFB). |
| * http://named-data.net/doc/tech-memos/naming-conventions.pdf |
| * @return The integer segment byte offset. |
| * @throws Error If the first byte of the component is not the expected marker. |
| */ |
| Name.Component.prototype.toSegmentOffset = function() |
| { |
| return this.toNumberWithMarker(0xFB); |
| }; |
| |
| /** |
| * Interpret this name component as a version number according to NDN naming |
| * conventions for "Versioning" (marker 0xFD). Note that this returns |
| * the exact number from the component without converting it to a time |
| * representation. |
| * @return {number} The integer version number. |
| * @throws Error If the first byte of the component is not the expected marker. |
| */ |
| Name.Component.prototype.toVersion = function() |
| { |
| return this.toNumberWithMarker(0xFD); |
| }; |
| |
| /** |
| * Interpret this name component as a timestamp according to NDN naming |
| * conventions for "Timestamp" (marker 0xFC). |
| * http://named-data.net/doc/tech-memos/naming-conventions.pdf |
| * @return The number of microseconds since the UNIX epoch (Thursday, |
| * 1 January 1970) not counting leap seconds. |
| * @throws Error If the first byte of the component is not the expected marker. |
| */ |
| Name.Component.prototype.toTimestamp = function() |
| { |
| return this.toNumberWithMarker(0xFC); |
| }; |
| |
| /** |
| * Interpret this name component as a sequence number according to NDN naming |
| * conventions for "Sequencing" (marker 0xFE). |
| * http://named-data.net/doc/tech-memos/naming-conventions.pdf |
| * @return The integer sequence number. |
| * @throws Error If the first byte of the component is not the expected marker. |
| */ |
| Name.Component.prototype.toSequenceNumber = function() |
| { |
| return this.toNumberWithMarker(0xFE); |
| }; |
| |
| /** |
| * Create a component whose value is the nonNegativeInteger encoding of the |
| * number. |
| * @param {number} number |
| * @return {Name.Component} |
| */ |
| Name.Component.fromNumber = function(number) |
| { |
| var encoder = new TlvEncoder(8); |
| encoder.writeNonNegativeInteger(number); |
| return new Name.Component(new Blob(encoder.getOutput(), false)); |
| }; |
| |
| /** |
| * Create a component whose value is the marker appended with the |
| * nonNegativeInteger encoding of the number. |
| * @param {number} number |
| * @param {number} marker |
| * @return {Name.Component} |
| */ |
| Name.Component.fromNumberWithMarker = function(number, marker) |
| { |
| var encoder = new TlvEncoder(9); |
| // Encode backwards. |
| encoder.writeNonNegativeInteger(number); |
| encoder.writeNonNegativeInteger(marker); |
| return new Name.Component(new Blob(encoder.getOutput(), false)); |
| }; |
| |
| /** |
| * Create a component with the encoded segment number according to NDN |
| * naming conventions for "Segment number" (marker 0x00). |
| * http://named-data.net/doc/tech-memos/naming-conventions.pdf |
| * param {number} segment The segment number. |
| * returns {Name.Component} The new Component. |
| */ |
| Name.Component.fromSegment = function(segment) |
| { |
| return Name.Component.fromNumberWithMarker(segment, 0x00); |
| }; |
| |
| /** |
| * Create a component with the encoded segment byte offset according to NDN |
| * naming conventions for segment "Byte offset" (marker 0xFB). |
| * http://named-data.net/doc/tech-memos/naming-conventions.pdf |
| * param {number} segmentOffset The segment byte offset. |
| * returns {Name.Component} The new Component. |
| */ |
| Name.Component.fromSegmentOffset = function(segmentOffset) |
| { |
| return Name.Component.fromNumberWithMarker(segmentOffset, 0xFB); |
| }; |
| |
| /** |
| * Create a component with the encoded version number according to NDN |
| * naming conventions for "Versioning" (marker 0xFD). |
| * http://named-data.net/doc/tech-memos/naming-conventions.pdf |
| * Note that this encodes the exact value of version without converting from a |
| * time representation. |
| * param {number} version The version number. |
| * returns {Name.Component} The new Component. |
| */ |
| Name.Component.fromVersion = function(version) |
| { |
| return Name.Component.fromNumberWithMarker(version, 0xFD); |
| }; |
| |
| /** |
| * Create a component with the encoded timestamp according to NDN naming |
| * conventions for "Timestamp" (marker 0xFC). |
| * http://named-data.net/doc/tech-memos/naming-conventions.pdf |
| * param {number} timestamp The number of microseconds since the UNIX epoch (Thursday, |
| * 1 January 1970) not counting leap seconds. |
| * returns {Name.Component} The new Component. |
| */ |
| Name.Component.fromTimestamp = function(timestamp) |
| { |
| return Name.Component.fromNumberWithMarker(timestamp, 0xFC); |
| }; |
| |
| /** |
| * Create a component with the encoded sequence number according to NDN naming |
| * conventions for "Sequencing" (marker 0xFE). |
| * http://named-data.net/doc/tech-memos/naming-conventions.pdf |
| * param {number} sequenceNumber The sequence number. |
| * returns {Name.Component} The new Component. |
| */ |
| Name.Component.fromSequenceNumber = function(sequenceNumber) |
| { |
| return Name.Component.fromNumberWithMarker(sequenceNumber, 0xFE); |
| }; |
| |
| /** |
| * Create a component of type ImplicitSha256DigestComponent, so that |
| * isImplicitSha256Digest() is true. |
| * @param {Blob|Buffer} digest The SHA-256 digest value. |
| * @return {Name.Component} The new Component. |
| * @throws DecodingException If the digest length is not 32 bytes. |
| */ |
| Name.Component.fromImplicitSha256Digest = function(digest) |
| { |
| digestBlob = typeof digest === 'object' && digest instanceof Blob ? |
| digest : new Blob(digest, true); |
| if (digestBlob.size() !== 32) |
| throw new DecodingException |
| ("Name.Component.fromImplicitSha256Digest: The digest length must be 32 bytes"); |
| |
| var result = new Name.Component(digestBlob); |
| result.type_ = Name.Component.ComponentType.IMPLICIT_SHA256_DIGEST; |
| return result; |
| }; |
| |
| /** |
| * Get the successor of this component, as described in Name.getSuccessor. |
| * @return {Name.Component} A new Name.Component which is the successor of this. |
| */ |
| Name.Component.prototype.getSuccessor = function() |
| { |
| // Allocate an extra byte in case the result is larger. |
| var result = new Buffer(this.value_.size() + 1); |
| |
| var carry = true; |
| for (var i = this.value_.size() - 1; i >= 0; --i) { |
| if (carry) { |
| result[i] = (this.value_.buf()[i] + 1) & 0xff; |
| carry = (result[i] === 0); |
| } |
| else |
| result[i] = this.value_.buf()[i]; |
| } |
| |
| if (carry) |
| // Assume all the bytes were set to zero (or the component was empty). In |
| // NDN ordering, carry does not mean to prepend a 1, but to make a component |
| // one byte longer of all zeros. |
| result[result.length - 1] = 0; |
| else |
| // We didn't need the extra byte. |
| result = result.slice(0, this.value_.size()); |
| |
| return new Name.Component(new Blob(result, false)); |
| }; |
| |
| /** |
| * Check if this is the same component as other. |
| * @param {Name.Component} other The other Component to compare with. |
| * @return {Boolean} true if the components are equal, otherwise false. |
| */ |
| Name.Component.prototype.equals = function(other) |
| { |
| return typeof other === 'object' && other instanceof Name.Component && |
| this.value_.equals(other.value_) && this.type_ === other.type_; |
| }; |
| |
| /** |
| * Compare this to the other Component using NDN canonical ordering. |
| * @param {Name.Component} other The other Component to compare with. |
| * @return {number} 0 if they compare equal, -1 if this comes before other in |
| * the canonical ordering, or 1 if this comes after other in the canonical |
| * ordering. |
| * |
| * @see http://named-data.net/doc/0.2/technical/CanonicalOrder.html |
| */ |
| Name.Component.prototype.compare = function(other) |
| { |
| if (this.type_ < other.type_) |
| return -1; |
| if (this.type_ > other.type_) |
| return 1; |
| |
| return Name.Component.compareBuffers(this.value_.buf(), other.value_.buf()); |
| }; |
| |
| /** |
| * Do the work of Name.Component.compare to compare the component buffers. |
| * @param {Buffer} component1 |
| * @param {Buffer} component2 |
| * @return {number} 0 if they compare equal, -1 if component1 comes before |
| * component2 in the canonical ordering, or 1 if component1 comes after |
| * component2 in the canonical ordering. |
| */ |
| Name.Component.compareBuffers = function(component1, component2) |
| { |
| if (component1.length < component2.length) |
| return -1; |
| if (component1.length > component2.length) |
| return 1; |
| |
| for (var i = 0; i < component1.length; ++i) { |
| if (component1[i] < component2[i]) |
| return -1; |
| if (component1[i] > component2[i]) |
| return 1; |
| } |
| |
| return 0; |
| }; |
| |
| /** |
| * @deprecated Use toUri. |
| */ |
| Name.prototype.getName = function() |
| { |
| return this.toUri(); |
| }; |
| |
| /** Parse uri as a URI and return an array of Buffer components. |
| */ |
| Name.createNameArray = function(uri) |
| { |
| uri = uri.trim(); |
| if (uri.length <= 0) |
| return []; |
| |
| var iColon = uri.indexOf(':'); |
| if (iColon >= 0) { |
| // Make sure the colon came before a '/'. |
| var iFirstSlash = uri.indexOf('/'); |
| if (iFirstSlash < 0 || iColon < iFirstSlash) |
| // Omit the leading protocol such as ndn: |
| uri = uri.substr(iColon + 1, uri.length - iColon - 1).trim(); |
| } |
| |
| if (uri[0] == '/') { |
| if (uri.length >= 2 && uri[1] == '/') { |
| // Strip the authority following "//". |
| var iAfterAuthority = uri.indexOf('/', 2); |
| if (iAfterAuthority < 0) |
| // Unusual case: there was only an authority. |
| return []; |
| else |
| uri = uri.substr(iAfterAuthority + 1, uri.length - iAfterAuthority - 1).trim(); |
| } |
| else |
| uri = uri.substr(1, uri.length - 1).trim(); |
| } |
| |
| var array = uri.split('/'); |
| |
| // Unescape the components. |
| var sha256digestPrefix = "sha256digest="; |
| for (var i = 0; i < array.length; ++i) { |
| var component; |
| if (array[i].substr(0, sha256digestPrefix.length) == sha256digestPrefix) { |
| var hexString = array[i].substr(sha256digestPrefix.length).trim(); |
| component = Name.Component.fromImplicitSha256Digest |
| (new Blob(new Buffer(hexString, 'hex')), false); |
| } |
| else |
| component = new Name.Component(Name.fromEscapedString(array[i])); |
| |
| if (component.getValue().isNull()) { |
| // Ignore the illegal componenent. This also gets rid of a trailing '/'. |
| array.splice(i, 1); |
| --i; |
| continue; |
| } |
| else |
| array[i] = component; |
| } |
| |
| return array; |
| }; |
| |
| /** |
| * Parse the uri according to the NDN URI Scheme and set the name with the |
| * components. |
| * @param {string} uri The URI string. |
| */ |
| Name.prototype.set = function(uri) |
| { |
| this.components = Name.createNameArray(uri); |
| ++this.changeCount; |
| }; |
| |
| /** |
| * Convert the component to a Buffer and append a GENERIC component to this Name. |
| * Return this Name object to allow chaining calls to add. |
| * @param {Name.Component|String|Array<number>|ArrayBuffer|Buffer|Name} component If a component is a string, encode as utf8 (but don't unescape). |
| * @return {Name} |
| */ |
| Name.prototype.append = function(component) |
| { |
| if (typeof component == 'object' && component instanceof Name) { |
| var components; |
| if (component == this) |
| // special case, when we need to create a copy |
| components = this.components.slice(0, this.components.length); |
| else |
| components = component.components; |
| |
| for (var i = 0; i < components.length; ++i) |
| this.components.push(new Name.Component(components[i])); |
| } |
| else if (typeof component === 'object' && component instanceof Name.Component) |
| // The Component is immutable, so use it as is. |
| this.components.push(component); |
| else |
| // Just use the Name.Component constructor. |
| this.components.push(new Name.Component(component)); |
| |
| ++this.changeCount; |
| return this; |
| }; |
| |
| /** |
| * @deprecated Use append. |
| */ |
| Name.prototype.add = function(component) |
| { |
| return this.append(component); |
| }; |
| |
| /** |
| * Clear all the components. |
| */ |
| Name.prototype.clear = function() |
| { |
| this.components = []; |
| ++this.changeCount; |
| }; |
| |
| /** |
| * Return the escaped name string according to NDN URI Scheme. |
| * @param {boolean} includeScheme (optional) If true, include the "ndn:" scheme |
| * in the URI, e.g. "ndn:/example/name". If false, just return the path, e.g. |
| * "/example/name". If ommitted, then just return the path which is the default |
| * case where toUri() is used for display. |
| * @return {String} |
| */ |
| Name.prototype.toUri = function(includeScheme) |
| { |
| if (this.size() == 0) |
| return includeScheme ? "ndn:/" : "/"; |
| |
| var result = includeScheme ? "ndn:" : ""; |
| |
| for (var i = 0; i < this.size(); ++i) |
| result += "/"+ this.components[i].toEscapedString(); |
| |
| return result; |
| }; |
| |
| /** |
| * @deprecated Use toUri. |
| */ |
| Name.prototype.to_uri = function() |
| { |
| return this.toUri(); |
| }; |
| |
| Name.prototype.toString = function() { return this.toUri(); } |
| |
| /** |
| * Append a component with the encoded segment number according to NDN |
| * naming conventions for "Segment number" (marker 0x00). |
| * http://named-data.net/doc/tech-memos/naming-conventions.pdf |
| * @param {number} segment The segment number. |
| * @return {Name} This name so that you can chain calls to append. |
| */ |
| Name.prototype.appendSegment = function(segment) |
| { |
| return this.append(Name.Component.fromSegment(segment)); |
| }; |
| |
| /** |
| * Append a component with the encoded segment byte offset according to NDN |
| * naming conventions for segment "Byte offset" (marker 0xFB). |
| * http://named-data.net/doc/tech-memos/naming-conventions.pdf |
| * @param {number} segmentOffset The segment byte offset. |
| * @return {Name} This name so that you can chain calls to append. |
| */ |
| Name.prototype.appendSegmentOffset = function(segmentOffset) |
| { |
| return this.append(Name.Component.fromSegmentOffset(segmentOffset)); |
| }; |
| |
| /** |
| * Append a component with the encoded version number according to NDN |
| * naming conventions for "Versioning" (marker 0xFD). |
| * http://named-data.net/doc/tech-memos/naming-conventions.pdf |
| * Note that this encodes the exact value of version without converting from a time representation. |
| * @param {number} version The version number. |
| * @return {Name} This name so that you can chain calls to append. |
| */ |
| Name.prototype.appendVersion = function(version) |
| { |
| return this.append(Name.Component.fromVersion(version)); |
| }; |
| |
| /** |
| * Append a component with the encoded timestamp according to NDN naming |
| * conventions for "Timestamp" (marker 0xFC). |
| * http://named-data.net/doc/tech-memos/naming-conventions.pdf |
| * @param {number} timestamp The number of microseconds since the UNIX epoch (Thursday, |
| * 1 January 1970) not counting leap seconds. |
| * @return This name so that you can chain calls to append. |
| */ |
| Name.prototype.appendTimestamp = function(timestamp) |
| { |
| return this.append(Name.Component.fromTimestamp(timestamp)); |
| }; |
| |
| /** |
| * Append a component with the encoded sequence number according to NDN naming |
| * conventions for "Sequencing" (marker 0xFE). |
| * http://named-data.net/doc/tech-memos/naming-conventions.pdf |
| * @param {number} sequenceNumber The sequence number. |
| * @return This name so that you can chain calls to append. |
| */ |
| Name.prototype.appendSequenceNumber = function(sequenceNumber) |
| { |
| return this.append(Name.Component.fromSequenceNumber(sequenceNumber)); |
| }; |
| |
| /** |
| * Append a component of type ImplicitSha256DigestComponent, so that |
| * isImplicitSha256Digest() is true. |
| * @param {Blob|Buffer} digest The SHA-256 digest value. |
| * @return This name so that you can chain calls to append. |
| * @throws DecodingException If the digest length is not 32 bytes. |
| */ |
| Name.prototype.appendImplicitSha256Digest = function(digest) |
| { |
| return this.append(Name.Component.fromImplicitSha256Digest(digest)); |
| }; |
| |
| /** |
| * @deprecated Use appendSegment. |
| */ |
| Name.prototype.addSegment = function(number) |
| { |
| return this.appendSegment(number); |
| }; |
| |
| /** |
| * Get a new name, constructed as a subset of components. |
| * @param {number} iStartComponent The index if the first component to get. If |
| * iStartComponent is -N then return return components starting from |
| * name.size() - N. |
| * @param {number} (optional) nComponents The number of components starting at |
| * iStartComponent. If omitted or greater than the size of this name, get until |
| * the end of the name. |
| * @return {Name} A new name. |
| */ |
| Name.prototype.getSubName = function(iStartComponent, nComponents) |
| { |
| if (iStartComponent < 0) |
| iStartComponent = this.components.length - (-iStartComponent); |
| |
| if (nComponents == undefined) |
| nComponents = this.components.length - iStartComponent; |
| |
| var result = new Name(); |
| |
| var iEnd = iStartComponent + nComponents; |
| for (var i = iStartComponent; i < iEnd && i < this.components.length; ++i) |
| result.components.push(this.components[i]); |
| |
| return result; |
| }; |
| |
| /** |
| * Return a new Name with the first nComponents components of this Name. |
| * @param {number} nComponents The number of prefix components. If nComponents is -N then return the prefix up |
| * to name.size() - N. For example getPrefix(-1) returns the name without the final component. |
| * @return {Name} A new name. |
| */ |
| Name.prototype.getPrefix = function(nComponents) |
| { |
| if (nComponents < 0) |
| return this.getSubName(0, this.components.length + nComponents); |
| else |
| return this.getSubName(0, nComponents); |
| }; |
| |
| /** |
| * @deprecated Use getPrefix(-nComponents). |
| */ |
| Name.prototype.cut = function(nComponents) |
| { |
| return new Name(this.components.slice(0, this.components.length - nComponents)); |
| }; |
| |
| /** |
| * Return the number of name components. |
| * @return {number} |
| */ |
| Name.prototype.size = function() |
| { |
| return this.components.length; |
| }; |
| |
| /** |
| * Get a Name Component by index number. |
| * @param {Number} i The index of the component, starting from 0. However, if i is negative, return the component |
| * at size() - (-i). |
| * @return {Name.Component} The name component at the index. You must not |
| * change the returned Name.Component object. |
| */ |
| Name.prototype.get = function(i) |
| { |
| if (i >= 0) { |
| if (i >= this.components.length) |
| throw new Error("Name.get: Index is out of bounds"); |
| |
| return this.components[i]; |
| } |
| else { |
| // Negative index. |
| if (i < -this.components.length) |
| throw new Error("Name.get: Index is out of bounds"); |
| |
| return this.components[this.components.length - (-i)]; |
| } |
| }; |
| |
| /** |
| * @deprecated Use size(). |
| */ |
| Name.prototype.getComponentCount = function() |
| { |
| return this.components.length; |
| }; |
| |
| /** |
| * @deprecated To get just the component value array, use get(i).getValue().buf(). |
| */ |
| Name.prototype.getComponent = function(i) |
| { |
| return new Buffer(this.components[i].getValue().buf()); |
| }; |
| |
| /** |
| * The "file name" in a name is the last component that isn't blank and doesn't start with one of the |
| * special marker octets (for version, etc.). Return the index in this.components of |
| * the file name, or -1 if not found. |
| */ |
| Name.prototype.indexOfFileName = function() |
| { |
| for (var i = this.size() - 1; i >= 0; --i) { |
| var component = this.components[i].getValue().buf(); |
| if (component.length <= 0) |
| continue; |
| |
| if (component[0] == 0 || component[0] == 0xC0 || component[0] == 0xC1 || |
| (component[0] >= 0xF5 && component[0] <= 0xFF)) |
| continue; |
| |
| return i; |
| } |
| |
| return -1; |
| }; |
| |
| /** |
| * Encode this Name for a particular wire format. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * this object. If omitted, use WireFormat.getDefaultWireFormat(). |
| * @return {Blob} The encoded buffer in a Blob object. |
| */ |
| Name.prototype.wireEncode = function(wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| return wireFormat.encodeName(this); |
| }; |
| |
| /** |
| * Decode the input using a particular wire format and update this Name. |
| * @param {Blob|Buffer} input The buffer with the bytes to decode. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to decode |
| * this object. If omitted, use WireFormat.getDefaultWireFormat(). |
| */ |
| Name.prototype.wireDecode = function(input, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| if (typeof input === 'object' && input instanceof Blob) |
| // Input is a blob, so get its buf() and set copy false. |
| wireFormat.decodeName(this, input.buf(), false); |
| else |
| wireFormat.decodeName(this, input, true); |
| }; |
| |
| /** |
| * Compare this to the other Name using NDN canonical ordering. If the first |
| * components of each name are not equal, this returns -1 if the first comes |
| * before the second using the NDN canonical ordering for name components, or 1 |
| * if it comes after. If they are equal, this compares the second components of |
| * each name, etc. If both names are the same up to the size of the shorter |
| * name, this returns -1 if the first name is shorter than the second or 1 if it |
| * is longer. For example, std::sort gives: /a/b/d /a/b/cc /c /c/a /bb . This |
| * is intuitive because all names with the prefix /a are next to each other. |
| * But it may be also be counter-intuitive because /c comes before /bb according |
| * to NDN canonical ordering since it is shorter. |
| * The first form of compare is simply compare(other). The second form is |
| * compare(iStartComponent, nComponents, other [, iOtherStartComponent] [, nOtherComponents]) |
| * which is equivalent to |
| * self.getSubName(iStartComponent, nComponents).compare |
| * (other.getSubName(iOtherStartComponent, nOtherComponents)) . |
| * @param {number} iStartComponent The index if the first component of this name |
| * to get. If iStartComponent is -N then compare components starting from |
| * name.size() - N. |
| * @param {number} nComponents The number of components starting at |
| * iStartComponent. If greater than the size of this name, compare until the end |
| * of the name. |
| * @param {Name} other The other Name to compare with. |
| * @param {number} iOtherStartComponent (optional) The index if the first |
| * component of the other name to compare. If iOtherStartComponent is -N then |
| * compare components starting from other.size() - N. If omitted, compare |
| * starting from index 0. |
| * @param {number} nOtherComponents (optional) The number of components |
| * starting at iOtherStartComponent. If omitted or greater than the size of this |
| * name, compare until the end of the name. |
| * @return {number} 0 If they compare equal, -1 if self comes before other in |
| * the canonical ordering, or 1 if self comes after other in the canonical |
| * ordering. |
| * @see http://named-data.net/doc/0.2/technical/CanonicalOrder.html |
| */ |
| Name.prototype.compare = function |
| (iStartComponent, nComponents, other, iOtherStartComponent, nOtherComponents) |
| { |
| if (iStartComponent instanceof Name) { |
| // compare(other) |
| other = iStartComponent; |
| iStartComponent = 0; |
| nComponents = this.size(); |
| } |
| |
| if (iOtherStartComponent == undefined) |
| iOtherStartComponent = 0; |
| if (nOtherComponents == undefined) |
| nOtherComponents = other.size(); |
| |
| if (iStartComponent < 0) |
| iStartComponent = this.size() - (-iStartComponent); |
| if (iOtherStartComponent < 0) |
| iOtherStartComponent = other.size() - (-iOtherStartComponent); |
| |
| nComponents = Math.min(nComponents, this.size() - iStartComponent); |
| nOtherComponents = Math.min(nOtherComponents, other.size() - iOtherStartComponent); |
| |
| var count = Math.min(nComponents, nOtherComponents); |
| for (var i = 0; i < count; ++i) { |
| var comparison = this.components[iStartComponent + i].compare |
| (other.components[iOtherStartComponent + i]); |
| if (comparison == 0) |
| // The components at this index are equal, so check the next components. |
| continue; |
| |
| // Otherwise, the result is based on the components at this index. |
| return comparison; |
| } |
| |
| // The components up to min(this.size(), other.size()) are equal, so the |
| // shorter name is less. |
| if (nComponents < nOtherComponents) |
| return -1; |
| else if (nComponents > nOtherComponents) |
| return 1; |
| else |
| return 0; |
| }; |
| |
| /** |
| * Return true if this Name has the same components as name. |
| */ |
| Name.prototype.equals = function(name) |
| { |
| if (this.components.length != name.components.length) |
| return false; |
| |
| // Start from the last component because they are more likely to differ. |
| for (var i = this.components.length - 1; i >= 0; --i) { |
| if (!this.components[i].equals(name.components[i])) |
| return false; |
| } |
| |
| return true; |
| }; |
| |
| /** |
| * @deprecated Use equals. |
| */ |
| Name.prototype.equalsName = function(name) |
| { |
| return this.equals(name); |
| }; |
| |
| /** |
| * Find the last component in name that has a ContentDigest and return the digest value as Buffer, |
| * or null if not found. See Name.getComponentContentDigestValue. |
| */ |
| Name.prototype.getContentDigestValue = function() |
| { |
| for (var i = this.size() - 1; i >= 0; --i) { |
| var digestValue = Name.getComponentContentDigestValue(this.components[i]); |
| if (digestValue != null) |
| return digestValue; |
| } |
| |
| return null; |
| }; |
| |
| /** |
| * If component is a ContentDigest, return the digest value as a Buffer slice (don't modify!). |
| * If not a ContentDigest, return null. |
| * A ContentDigest component is Name.ContentDigestPrefix + 32 bytes + Name.ContentDigestSuffix. |
| */ |
| Name.getComponentContentDigestValue = function(component) |
| { |
| if (typeof component == 'object' && component instanceof Name.Component) |
| component = component.getValue().buf(); |
| |
| var digestComponentLength = Name.ContentDigestPrefix.length + 32 + Name.ContentDigestSuffix.length; |
| // Check for the correct length and equal ContentDigestPrefix and ContentDigestSuffix. |
| if (component.length == digestComponentLength && |
| DataUtils.arraysEqual(component.slice(0, Name.ContentDigestPrefix.length), |
| Name.ContentDigestPrefix) && |
| DataUtils.arraysEqual(component.slice |
| (component.length - Name.ContentDigestSuffix.length, component.length), |
| Name.ContentDigestSuffix)) |
| return component.slice(Name.ContentDigestPrefix.length, Name.ContentDigestPrefix.length + 32); |
| else |
| return null; |
| }; |
| |
| // Meta GUID "%C1.M.G%C1" + ContentDigest with a 32 byte BLOB. |
| Name.ContentDigestPrefix = new Buffer([0xc1, 0x2e, 0x4d, 0x2e, 0x47, 0xc1, 0x01, 0xaa, 0x02, 0x85]); |
| Name.ContentDigestSuffix = new Buffer([0x00]); |
| |
| |
| /** |
| * Return value as an escaped string according to NDN URI Scheme. |
| * We can't use encodeURIComponent because that doesn't encode all the |
| * characters we want to. |
| * This does not add a type code prefix such as "sha256digest=". |
| * @param {Buffer|Name.Component} value The value or Name.Component to escape. |
| * @return {string} The escaped string. |
| */ |
| Name.toEscapedString = function(value) |
| { |
| if (typeof value == 'object' && value instanceof Name.Component) |
| value = value.getValue().buf(); |
| else if (typeof value === 'object' && value instanceof Blob) |
| value = value.buf(); |
| |
| var result = ""; |
| var gotNonDot = false; |
| for (var i = 0; i < value.length; ++i) { |
| if (value[i] != 0x2e) { |
| gotNonDot = true; |
| break; |
| } |
| } |
| if (!gotNonDot) { |
| // Special case for component of zero or more periods. Add 3 periods. |
| result = "..."; |
| for (var i = 0; i < value.length; ++i) |
| result += "."; |
| } |
| else { |
| for (var i = 0; i < value.length; ++i) { |
| var x = value[i]; |
| // Check for 0-9, A-Z, a-z, (+), (-), (.), (_) |
| if (x >= 0x30 && x <= 0x39 || x >= 0x41 && x <= 0x5a || |
| x >= 0x61 && x <= 0x7a || x == 0x2b || x == 0x2d || |
| x == 0x2e || x == 0x5f) |
| result += String.fromCharCode(x); |
| else |
| result += "%" + (x < 16 ? "0" : "") + x.toString(16).toUpperCase(); |
| } |
| } |
| return result; |
| }; |
| |
| /** |
| * Make a blob value by decoding the escapedString according to NDN URI Scheme. |
| * If escapedString is "", "." or ".." then return null, which means to skip the |
| * component in the name. |
| * This does not check for a type code prefix such as "sha256digest=". |
| * @param {string} escapedString The escaped string to decode. |
| * @return {Blob} The unescaped Blob value. If the escapedString is not a valid |
| * escaped component, then the Blob isNull(). |
| */ |
| Name.fromEscapedString = function(escapedString) |
| { |
| var value = unescape(escapedString.trim()); |
| |
| if (value.match(/[^.]/) == null) { |
| // Special case for value of only periods. |
| if (value.length <= 2) |
| // Zero, one or two periods is illegal. Ignore this componenent to be |
| // consistent with the C implementation. |
| return new Blob(); |
| else |
| // Remove 3 periods. |
| return new Blob |
| (DataUtils.toNumbersFromString(value.substr(3, value.length - 3)), false); |
| } |
| else |
| return new Blob(DataUtils.toNumbersFromString(value), false); |
| }; |
| |
| /** |
| * @deprecated Use fromEscapedString. This method returns a Buffer which is the former |
| * behavior of fromEscapedString, and should only be used while updating your code. |
| */ |
| Name.fromEscapedStringAsBuffer = function(escapedString) |
| { |
| return Name.fromEscapedString(escapedString).buf(); |
| }; |
| |
| /** |
| * Get the successor of this name which is defined as follows. |
| * |
| * N represents the set of NDN Names, and X,Y ∈ N. |
| * Operator < is defined by the NDN canonical order on N. |
| * Y is the successor of X, if (a) X < Y, and (b) ∄ Z ∈ N s.t. X < Z < Y. |
| * |
| * In plain words, the successor of a name is the same name, but with its last |
| * component advanced to a next possible value. |
| * |
| * Examples: |
| * |
| * - The successor of / is /%00 |
| * - The successor of /%00%01/%01%02 is /%00%01/%01%03 |
| * - The successor of /%00%01/%01%FF is /%00%01/%02%00 |
| * - The successor of /%00%01/%FF%FF is /%00%01/%00%00%00 |
| * |
| * @return {Name} A new name which is the successor of this. |
| */ |
| Name.prototype.getSuccessor = function() |
| { |
| if (this.size() == 0) { |
| // Return "/%00". |
| var result = new Name(); |
| result.append(new Blob(new Buffer([0]), false)); |
| return result; |
| } |
| else |
| return this.getPrefix(-1).append(this.get(-1).getSuccessor()); |
| }; |
| |
| /** |
| * Return true if the N components of this name are the same as the first N |
| * components of the given name. |
| * @param {Name} name The name to check. |
| * @return {Boolean} true if this matches the given name. This always returns |
| * true if this name is empty. |
| */ |
| Name.prototype.match = function(name) |
| { |
| var i_name = this.components; |
| var o_name = name.components; |
| |
| // This name is longer than the name we are checking it against. |
| if (i_name.length > o_name.length) |
| return false; |
| |
| // Check if at least one of given components doesn't match. Check from last to |
| // first since the last components are more likely to differ. |
| for (var i = i_name.length - 1; i >= 0; --i) { |
| if (!i_name[i].equals(o_name[i])) |
| return false; |
| } |
| |
| return true; |
| }; |
| |
| /** |
| * Return true if the N components of this name are the same as the first N |
| * components of the given name. |
| * @param {Name} name The name to check. |
| * @return {Boolean} true if this matches the given name. This always returns |
| * true if this name is empty. |
| */ |
| Name.prototype.isPrefixOf = function(name) { return this.match(name); } |
| |
| /** |
| * Get the change count, which is incremented each time this object is changed. |
| * @return {number} The change count. |
| */ |
| Name.prototype.getChangeCount = function() |
| { |
| return this.changeCount; |
| }; |
| |
| // Put these requires at the bottom to avoid circular references. |
| var TlvEncoder = require('./encoding/tlv/tlv-encoder.js').TlvEncoder; |
| var WireFormat = require('./encoding/wire-format.js').WireFormat; |
| /** |
| * This class represents an NDN KeyLocator object. |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Meki Cheraoui |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Blob = require('./util/blob.js').Blob; /** @ignore */ |
| var ChangeCounter = require('./util/change-counter.js').ChangeCounter; /** @ignore */ |
| var Name = require('./name.js').Name; |
| |
| /** |
| * KeyLocator |
| */ |
| var KeyLocatorType = { |
| KEYNAME: 1, |
| KEY_LOCATOR_DIGEST: 2 |
| }; |
| |
| exports.KeyLocatorType = KeyLocatorType; |
| |
| /** |
| * @constructor |
| */ |
| var KeyLocator = function KeyLocator(input, type) |
| { |
| if (typeof input === 'object' && input instanceof KeyLocator) { |
| // Copy from the input KeyLocator. |
| this.type_ = input.type_; |
| this.keyName_ = new ChangeCounter(new Name(input.getKeyName())); |
| this.keyData_ = input.keyData_; |
| } |
| else { |
| this.type_ = type; |
| this.keyName_ = new ChangeCounter(new Name()); |
| this.keyData_ = new Blob(); |
| |
| if (type == KeyLocatorType.KEYNAME) |
| this.keyName_.set(typeof input === 'object' && input instanceof Name ? |
| new Name(input) : new Name()); |
| else if (type == KeyLocatorType.KEY_LOCATOR_DIGEST) |
| this.keyData_ = new Blob(input); |
| } |
| |
| this.changeCount_ = 0; |
| }; |
| |
| exports.KeyLocator = KeyLocator; |
| |
| /** |
| * Get the key locator type. If KeyLocatorType.KEYNAME, you may also |
| * getKeyName(). If KeyLocatorType.KEY_LOCATOR_DIGEST, you may also |
| * getKeyData() to get the digest. |
| * @return {number} The key locator type as a KeyLocatorType enum value, |
| * or null if not specified. |
| */ |
| KeyLocator.prototype.getType = function() { return this.type_; }; |
| |
| /** |
| * Get the key name. This is meaningful if getType() is KeyLocatorType.KEYNAME. |
| * @return {Name} The key name. If not specified, the Name is empty. |
| */ |
| KeyLocator.prototype.getKeyName = function() |
| { |
| return this.keyName_.get(); |
| }; |
| |
| /** |
| * Get the key data. If getType() is KeyLocatorType.KEY_LOCATOR_DIGEST, this is |
| * the digest bytes. |
| * @return {Blob} The key data, or an isNull Blob if not specified. |
| */ |
| KeyLocator.prototype.getKeyData = function() |
| { |
| return this.keyData_; |
| }; |
| |
| /** |
| * @deprecated Use getKeyData. This method returns a Buffer which is the former |
| * behavior of getKeyData, and should only be used while updating your code. |
| */ |
| KeyLocator.prototype.getKeyDataAsBuffer = function() |
| { |
| return this.getKeyData().buf(); |
| }; |
| |
| /** |
| * Set the key locator type. If KeyLocatorType.KEYNAME, you must also |
| * setKeyName(). If KeyLocatorType.KEY_LOCATOR_DIGEST, you must also |
| * setKeyData() to the digest. |
| * @param {number} type The key locator type as a KeyLocatorType enum value. If |
| * null, the type is unspecified. |
| */ |
| KeyLocator.prototype.setType = function(type) |
| { |
| this.type_ = type; |
| ++this.changeCount_; |
| }; |
| |
| /** |
| * Set key name to a copy of the given Name. This is the name if getType() |
| * is KeyLocatorType.KEYNAME. |
| * @param {Name} name The key name which is copied. |
| */ |
| KeyLocator.prototype.setKeyName = function(name) |
| { |
| this.keyName_.set(typeof name === 'object' && name instanceof Name ? |
| new Name(name) : new Name()); |
| ++this.changeCount_; |
| }; |
| |
| /** |
| * Set the key data to the given value. This is the digest bytes if getType() is |
| * KeyLocatorType.KEY_LOCATOR_DIGEST. |
| * @param {Blob} keyData A Blob with the key data bytes. |
| */ |
| KeyLocator.prototype.setKeyData = function(keyData) |
| { |
| this.keyData_ = typeof keyData === 'object' && keyData instanceof Blob ? |
| keyData : new Blob(keyData); |
| ++this.changeCount_; |
| }; |
| |
| /** |
| * Clear the keyData and set the type to not specified. |
| */ |
| KeyLocator.prototype.clear = function() |
| { |
| this.type_ = null; |
| this.keyName_.set(new Name()); |
| this.keyData_ = new Blob(); |
| ++this.changeCount_; |
| }; |
| |
| /** |
| * Check if this key locator has the same values as the given key locator. |
| * @param {KeyLocator} other The other key locator to check. |
| * @return {boolean} true if the key locators are equal, otherwise false. |
| */ |
| KeyLocator.prototype.equals = function(other) |
| { |
| if (this.type_ != other.type_) |
| return false; |
| |
| if (this.type_ == KeyLocatorType.KEYNAME) { |
| if (!this.getKeyName().equals(other.getKeyName())) |
| return false; |
| } |
| else if (this.type_ == KeyLocatorType.KEY_LOCATOR_DIGEST) { |
| if (!this.getKeyData().equals(other.getKeyData())) |
| return false; |
| } |
| |
| return true; |
| }; |
| |
| /** |
| * If the signature is a type that has a KeyLocator (so that, |
| * getFromSignature will succeed), return true. |
| * Note: This is a static method of KeyLocator instead of a method of |
| * Signature so that the Signature base class does not need to be overloaded |
| * with all the different kinds of information that various signature |
| * algorithms may use. |
| * @param {Signature} signature An object of a subclass of Signature. |
| * @return {boolean} True if the signature is a type that has a KeyLocator, |
| * otherwise false. |
| */ |
| KeyLocator.canGetFromSignature = function(signature) |
| { |
| return signature instanceof Sha256WithRsaSignature || |
| signature instanceof Sha256WithEcdsaSignature || |
| signature instanceof HmacWithSha256Signature; |
| } |
| |
| /** |
| * If the signature is a type that has a KeyLocator, then return it. Otherwise |
| * throw an error. |
| * @param {Signature} signature An object of a subclass of Signature. |
| * @return {KeyLocator} The signature's KeyLocator. It is an error if signature |
| * doesn't have a KeyLocator. |
| */ |
| KeyLocator.getFromSignature = function(signature) |
| { |
| if (signature instanceof Sha256WithRsaSignature || |
| signature instanceof Sha256WithEcdsaSignature || |
| signature instanceof HmacWithSha256Signature) |
| return signature.getKeyLocator(); |
| else |
| throw new Error |
| ("KeyLocator.getFromSignature: Signature type does not have a KeyLocator"); |
| } |
| |
| /** |
| * Get the change count, which is incremented each time this object (or a child |
| * object) is changed. |
| * @return {number} The change count. |
| */ |
| KeyLocator.prototype.getChangeCount = function() |
| { |
| // Make sure each of the checkChanged is called. |
| var changed = this.keyName_.checkChanged(); |
| if (changed) |
| // A child object has changed, so update the change count. |
| ++this.changeCount_; |
| |
| return this.changeCount_; |
| }; |
| |
| // Define properties so we can change member variable types and implement changeCount_. |
| Object.defineProperty(KeyLocator.prototype, "type", |
| { get: function() { return this.getType(); }, |
| set: function(val) { this.setType(val); } }); |
| /** |
| * @@deprecated Use getKeyData and setKeyData. |
| */ |
| Object.defineProperty(KeyLocator.prototype, "keyData", |
| { get: function() { return this.getKeyDataAsBuffer(); }, |
| set: function(val) { this.setKeyData(val); } }); |
| |
| // Put this last to avoid a require loop. |
| var Sha256WithRsaSignature = require('./sha256-with-rsa-signature.js').Sha256WithRsaSignature; |
| var Sha256WithEcdsaSignature = require('./sha256-with-ecdsa-signature.js').Sha256WithEcdsaSignature; |
| var HmacWithSha256Signature = require('./hmac-with-sha256-signature.js').HmacWithSha256Signature; |
| /** |
| * This class represents an NDN Data MetaInfo object. |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Meki Cheraoui |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Name = require('./name.js').Name; |
| |
| /** |
| * A ContentType specifies the content type in a MetaInfo object. If the |
| * content type in the packet is not a recognized enum value, then we use |
| * ContentType.OTHER_CODE and you can call MetaInfo.getOtherTypeCode(). We do |
| * this to keep the recognized content type values independent of packet |
| * encoding formats. |
| */ |
| var ContentType = { |
| BLOB:0, |
| LINK:1, |
| KEY: 2, |
| NACK:3, |
| OTHER_CODE: 0x7fff |
| }; |
| |
| exports.ContentType = ContentType; |
| |
| /** |
| * Create a new MetaInfo with the optional values. |
| * @constructor |
| */ |
| var MetaInfo = function MetaInfo(publisherOrMetaInfo, timestamp, type, locator, freshnessSeconds, finalBlockId) |
| { |
| if (timestamp) |
| throw new Error |
| ("MetaInfo constructor: timestamp support has been removed."); |
| if (locator) |
| throw new Error |
| ("MetaInfo constructor: locator support has been removed."); |
| |
| if (typeof publisherOrMetaInfo === 'object' && |
| publisherOrMetaInfo instanceof MetaInfo) { |
| // Copy values. |
| var metaInfo = publisherOrMetaInfo; |
| this.publisher_ = metaInfo.publisher_; |
| this.type_ = metaInfo.type_; |
| this.otherTypeCode_ = metaInfo.otherTypeCode_; |
| this.freshnessPeriod_ = metaInfo.freshnessPeriod_; |
| this.finalBlockId_ = metaInfo.finalBlockId_; |
| } |
| else { |
| if (publisherOrMetaInfo) |
| throw new Error |
| ("MetaInfo constructor: publisher support has been removed."); |
| |
| this.type = type == null || type < 0 ? ContentType.BLOB : type; |
| this.otherTypeCode_ = -1; |
| this.freshnessSeconds = freshnessSeconds; // deprecated |
| this.finalBlockID = finalBlockId; // byte array // deprecated |
| } |
| |
| this.changeCount_ = 0; |
| }; |
| |
| exports.MetaInfo = MetaInfo; |
| |
| /** |
| * Get the content type. |
| * @return {number} The content type as an int from ContentType. If this is |
| * ContentType.OTHER_CODE, then call getOtherTypeCode() to get the unrecognized |
| * content type code. |
| */ |
| MetaInfo.prototype.getType = function() |
| { |
| return this.type_; |
| }; |
| |
| /** |
| * Get the content type code from the packet which is other than a recognized |
| * ContentType enum value. This is only meaningful if getType() is |
| * ContentType.OTHER_CODE. |
| * @return {number} The type code. |
| */ |
| MetaInfo.prototype.getOtherTypeCode = function() |
| { |
| return this.otherTypeCode_; |
| }; |
| |
| /** |
| * Get the freshness period. |
| * @return {number} The freshness period in milliseconds, or null if not |
| * specified. |
| */ |
| MetaInfo.prototype.getFreshnessPeriod = function() |
| { |
| return this.freshnessPeriod_; |
| }; |
| |
| /** |
| * Get the final block ID. |
| * @return {Name.Component} The final block ID as a Name.Component. If the |
| * Name.Component getValue().size() is 0, then the final block ID is not specified. |
| */ |
| MetaInfo.prototype.getFinalBlockId = function() |
| { |
| return this.finalBlockId_; |
| }; |
| |
| /** |
| * @deprecated Use getFinalBlockId. |
| */ |
| MetaInfo.prototype.getFinalBlockID = function() |
| { |
| return this.getFinalBlockId(); |
| }; |
| |
| /** |
| * @deprecated Use getFinalBlockId. This method returns a Buffer which is the former |
| * behavior of getFinalBlockId, and should only be used while updating your code. |
| */ |
| MetaInfo.prototype.getFinalBlockIDAsBuffer = function() |
| { |
| return this.finalBlockId_.getValue().buf(); |
| }; |
| |
| /** |
| * Set the content type. |
| * @param {number} type The content type as an int from ContentType. If null, |
| * this uses ContentType.BLOB. If the packet's content type is not a recognized |
| * ContentType enum value, use ContentType.OTHER_CODE and call setOtherTypeCode(). |
| */ |
| MetaInfo.prototype.setType = function(type) |
| { |
| this.type_ = type == null || type < 0 ? ContentType.BLOB : type; |
| ++this.changeCount_; |
| }; |
| |
| /** |
| * Set the packet’s content type code to use when the content type enum is |
| * ContentType.OTHER_CODE. If the packet’s content type code is a recognized |
| * enum value, just call setType(). |
| * @param {number} otherTypeCode The packet’s unrecognized content type code, |
| * which must be non-negative. |
| */ |
| MetaInfo.prototype.setOtherTypeCode = function(otherTypeCode) |
| { |
| if (otherTypeCode < 0) |
| throw new Error("MetaInfo other type code must be non-negative"); |
| |
| this.otherTypeCode_ = otherTypeCode; |
| ++this.changeCount_; |
| }; |
| |
| /** |
| * Set the freshness period. |
| * @param {number} freshnessPeriod The freshness period in milliseconds, or null |
| * for not specified. |
| */ |
| MetaInfo.prototype.setFreshnessPeriod = function(freshnessPeriod) |
| { |
| if (freshnessPeriod == null || freshnessPeriod < 0) |
| this.freshnessPeriod_ = null; |
| else |
| this.freshnessPeriod_ = freshnessPeriod; |
| ++this.changeCount_; |
| }; |
| |
| /** |
| * Set the final block ID. |
| * @param {Name.Component} finalBlockId The final block ID as a Name.Component. |
| * If not specified, set to a new default Name.Component(), or to a |
| * Name.Component where getValue().size() is 0. |
| */ |
| MetaInfo.prototype.setFinalBlockId = function(finalBlockId) |
| { |
| this.finalBlockId_ = typeof finalBlockId === 'object' && |
| finalBlockId instanceof Name.Component ? |
| finalBlockId : new Name.Component(finalBlockId); |
| ++this.changeCount_; |
| }; |
| |
| /** |
| * @deprecated Use setFinalBlockId. |
| */ |
| MetaInfo.prototype.setFinalBlockID = function(finalBlockId) |
| { |
| this.setFinalBlockId(finalBlockId); |
| }; |
| |
| /** |
| * Get the change count, which is incremented each time this object is changed. |
| * @return {number} The change count. |
| */ |
| MetaInfo.prototype.getChangeCount = function() |
| { |
| return this.changeCount_; |
| }; |
| |
| // Define properties so we can change member variable types and implement changeCount_. |
| Object.defineProperty(MetaInfo.prototype, "type", |
| { get: function() { return this.getType(); }, |
| set: function(val) { this.setType(val); } }); |
| /** |
| * @deprecated Use getFreshnessPeriod and setFreshnessPeriod. |
| */ |
| Object.defineProperty(MetaInfo.prototype, "freshnessSeconds", |
| { get: function() { |
| if (this.freshnessPeriod_ == null || this.freshnessPeriod_ < 0) |
| return null; |
| else |
| // Convert from milliseconds. |
| return this.freshnessPeriod_ / 1000.0; |
| }, |
| set: function(val) { |
| if (val == null || val < 0) |
| this.freshnessPeriod_ = null; |
| else |
| // Convert to milliseconds. |
| this.freshnessPeriod_ = val * 1000.0; |
| ++this.changeCount_; |
| } }); |
| /** |
| * @deprecated Use getFinalBlockId and setFinalBlockId. |
| */ |
| Object.defineProperty(MetaInfo.prototype, "finalBlockID", |
| { get: function() { return this.getFinalBlockIDAsBuffer(); }, |
| set: function(val) { this.setFinalBlockId(val); } }); |
| /** |
| * This class represents an NDN Data Signature object. |
| * Copyright (C) 2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Blob = require('./util/blob.js').Blob; /** @ignore */ |
| var ChangeCounter = require('./util/change-counter.js').ChangeCounter; /** @ignore */ |
| var KeyLocator = require('./key-locator.js').KeyLocator; |
| |
| /** |
| * Create a new Sha256WithEcdsaSignature object, possibly copying values from |
| * another object. |
| * |
| * @param {Sha256WithEcdsaSignature} value (optional) If value is a |
| * Sha256WithEcdsaSignature, copy its values. If value is omitted, the |
| * keyLocator is the default with unspecified values and the signature is |
| * unspecified. |
| * @constructor |
| */ |
| var Sha256WithEcdsaSignature = function Sha256WithEcdsaSignature(value) |
| { |
| if (typeof value === 'object' && value instanceof Sha256WithEcdsaSignature) { |
| // Copy the values. |
| this.keyLocator_ = new ChangeCounter(new KeyLocator(value.getKeyLocator())); |
| this.signature_ = value.signature_; |
| } |
| else { |
| this.keyLocator_ = new ChangeCounter(new KeyLocator()); |
| this.signature_ = new Blob(); |
| } |
| |
| this.changeCount_ = 0; |
| }; |
| |
| exports.Sha256WithEcdsaSignature = Sha256WithEcdsaSignature; |
| |
| /** |
| * Create a new Sha256WithEcdsaSignature which is a copy of this object. |
| * @return {Sha256WithEcdsaSignature} A new object which is a copy of this |
| * object. |
| */ |
| Sha256WithEcdsaSignature.prototype.clone = function() |
| { |
| return new Sha256WithEcdsaSignature(this); |
| }; |
| |
| /** |
| * Get the key locator. |
| * @return {KeyLocator} The key locator. |
| */ |
| Sha256WithEcdsaSignature.prototype.getKeyLocator = function() |
| { |
| return this.keyLocator_.get(); |
| }; |
| |
| /** |
| * Get the data packet's signature bytes. |
| * @return {Blob} The signature bytes. If not specified, the value isNull(). |
| */ |
| Sha256WithEcdsaSignature.prototype.getSignature = function() |
| { |
| return this.signature_; |
| }; |
| |
| /** |
| * Set the key locator to a copy of the given keyLocator. |
| * @param {KeyLocator} keyLocator The KeyLocator to copy. |
| */ |
| Sha256WithEcdsaSignature.prototype.setKeyLocator = function(keyLocator) |
| { |
| this.keyLocator_.set(typeof keyLocator === 'object' && |
| keyLocator instanceof KeyLocator ? |
| new KeyLocator(keyLocator) : new KeyLocator()); |
| ++this.changeCount_; |
| }; |
| |
| /** |
| * Set the data packet's signature bytes. |
| * @param {Blob} signature |
| */ |
| Sha256WithEcdsaSignature.prototype.setSignature = function(signature) |
| { |
| this.signature_ = typeof signature === 'object' && signature instanceof Blob ? |
| signature : new Blob(signature); |
| ++this.changeCount_; |
| }; |
| |
| /** |
| * Get the change count, which is incremented each time this object (or a child |
| * object) is changed. |
| * @return {number} The change count. |
| */ |
| Sha256WithEcdsaSignature.prototype.getChangeCount = function() |
| { |
| // Make sure each of the checkChanged is called. |
| var changed = this.keyLocator_.checkChanged(); |
| if (changed) |
| // A child object has changed, so update the change count. |
| ++this.changeCount_; |
| |
| return this.changeCount_; |
| }; |
| /** |
| * This class represents an NDN Data Signature object. |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Meki Cheraoui |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Blob = require('./util/blob.js').Blob; /** @ignore */ |
| var ChangeCounter = require('./util/change-counter.js').ChangeCounter; /** @ignore */ |
| var KeyLocator = require('./key-locator.js').KeyLocator; |
| |
| /** |
| * Create a new Sha256WithRsaSignature object, possibly copying values from |
| * another object. |
| * |
| * @param {Sha256WithRsaSignature} value (optional) If value is a |
| * Sha256WithRsaSignature, copy its values. If value is omitted, the keyLocator |
| * is the default with unspecified values and the signature is unspecified. |
| * @constructor |
| */ |
| var Sha256WithRsaSignature = function Sha256WithRsaSignature(value) |
| { |
| if (typeof value === 'object' && value instanceof Sha256WithRsaSignature) { |
| // Copy the values. |
| this.keyLocator_ = new ChangeCounter(new KeyLocator(value.getKeyLocator())); |
| this.signature_ = value.signature_; |
| } |
| else { |
| this.keyLocator_ = new ChangeCounter(new KeyLocator()); |
| this.signature_ = new Blob(); |
| } |
| |
| this.changeCount_ = 0; |
| }; |
| |
| exports.Sha256WithRsaSignature = Sha256WithRsaSignature; |
| |
| /** |
| * Create a new Sha256WithRsaSignature which is a copy of this object. |
| * @return {Sha256WithRsaSignature} A new object which is a copy of this object. |
| */ |
| Sha256WithRsaSignature.prototype.clone = function() |
| { |
| return new Sha256WithRsaSignature(this); |
| }; |
| |
| /** |
| * Get the key locator. |
| * @return {KeyLocator} The key locator. |
| */ |
| Sha256WithRsaSignature.prototype.getKeyLocator = function() |
| { |
| return this.keyLocator_.get(); |
| }; |
| |
| /** |
| * Get the data packet's signature bytes. |
| * @return {Blob} The signature bytes. If not specified, the value isNull(). |
| */ |
| Sha256WithRsaSignature.prototype.getSignature = function() |
| { |
| return this.signature_; |
| }; |
| |
| /** |
| * @deprecated Use getSignature. This method returns a Buffer which is the former |
| * behavior of getSignature, and should only be used while updating your code. |
| */ |
| Sha256WithRsaSignature.prototype.getSignatureAsBuffer = function() |
| { |
| return this.signature_.buf(); |
| }; |
| |
| /** |
| * Set the key locator to a copy of the given keyLocator. |
| * @param {KeyLocator} keyLocator The KeyLocator to copy. |
| */ |
| Sha256WithRsaSignature.prototype.setKeyLocator = function(keyLocator) |
| { |
| this.keyLocator_.set(typeof keyLocator === 'object' && |
| keyLocator instanceof KeyLocator ? |
| new KeyLocator(keyLocator) : new KeyLocator()); |
| ++this.changeCount_; |
| }; |
| |
| /** |
| * Set the data packet's signature bytes. |
| * @param {Blob} signature |
| */ |
| Sha256WithRsaSignature.prototype.setSignature = function(signature) |
| { |
| this.signature_ = typeof signature === 'object' && signature instanceof Blob ? |
| signature : new Blob(signature); |
| ++this.changeCount_; |
| }; |
| |
| /** |
| * Get the change count, which is incremented each time this object (or a child |
| * object) is changed. |
| * @return {number} The change count. |
| */ |
| Sha256WithRsaSignature.prototype.getChangeCount = function() |
| { |
| // Make sure each of the checkChanged is called. |
| var changed = this.keyLocator_.checkChanged(); |
| if (changed) |
| // A child object has changed, so update the change count. |
| ++this.changeCount_; |
| |
| return this.changeCount_; |
| }; |
| |
| // Define properties so we can change member variable types and implement changeCount_. |
| Object.defineProperty(Sha256WithRsaSignature.prototype, "keyLocator", |
| { get: function() { return this.getKeyLocator(); }, |
| set: function(val) { this.setKeyLocator(val); } }); |
| /** |
| * @@deprecated Use getSignature and setSignature. |
| */ |
| Object.defineProperty(Sha256WithRsaSignature.prototype, "signature", |
| { get: function() { return this.getSignatureAsBuffer(); }, |
| set: function(val) { this.setSignature(val); } }); |
| /** |
| * This class represents an NDN Data Signature object. |
| * Copyright (C) 2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Blob = require('./util/blob.js').Blob; |
| |
| /** |
| * A GenericSignature extends Signature and holds the encoding bytes of the |
| * SignatureInfo so that the application can process experimental signature |
| * types. When decoding a packet, if the type of SignatureInfo is not |
| * recognized, the library creates a GenericSignature. |
| * Create a new GenericSignature object, possibly copying values from another |
| * object. |
| * |
| * @param {GenericSignature} value (optional) If value is a GenericSignature, |
| * copy its values. |
| * @constructor |
| */ |
| var GenericSignature = function GenericSignature(value) |
| { |
| if (typeof value === 'object' && value instanceof GenericSignature) { |
| // Copy the values. |
| this.signature_ = value.signature_; |
| this.signatureInfoEncoding_ = value.signatureInfoEncoding_; |
| this.typeCode_ = value.typeCode_; |
| } |
| else { |
| this.signature_ = new Blob(); |
| this.signatureInfoEncoding_ = new Blob(); |
| this.typeCode_ = null; |
| } |
| |
| this.changeCount_ = 0; |
| }; |
| |
| exports.GenericSignature = GenericSignature; |
| |
| /** |
| * Create a new GenericSignature which is a copy of this object. |
| * @return {GenericSignature} A new object which is a copy of this object. |
| */ |
| GenericSignature.prototype.clone = function() |
| { |
| return new GenericSignature(this); |
| }; |
| |
| /** |
| * Get the data packet's signature bytes. |
| * @return {Blob} The signature bytes. If not specified, the value isNull(). |
| */ |
| GenericSignature.prototype.getSignature = function() |
| { |
| return this.signature_; |
| }; |
| |
| /** |
| * @deprecated Use getSignature. This method returns a Buffer which is the former |
| * behavior of getSignature, and should only be used while updating your code. |
| */ |
| GenericSignature.prototype.getSignatureAsBuffer = function() |
| { |
| return this.signature_.buf(); |
| }; |
| |
| /** |
| * Get the bytes of the entire signature info encoding (including the type |
| * code). |
| * @return {Blob} The encoding bytes. If not specified, the value isNull(). |
| */ |
| GenericSignature.prototype.getSignatureInfoEncoding = function() |
| { |
| return this.signatureInfoEncoding_; |
| }; |
| |
| /** |
| * Get the type code of the signature type. When wire decode calls |
| * setSignatureInfoEncoding, it sets the type code. Note that the type code |
| * is ignored during wire encode, which simply uses getSignatureInfoEncoding() |
| * where the encoding already has the type code. |
| * @return {number} The type code, or null if not known. |
| */ |
| GenericSignature.prototype.getTypeCode = function() { return this.typeCode_; }; |
| |
| /** |
| * Set the data packet's signature bytes. |
| * @param {Blob} signature |
| */ |
| GenericSignature.prototype.setSignature = function(signature) |
| { |
| this.signature_ = typeof signature === 'object' && signature instanceof Blob ? |
| signature : new Blob(signature); |
| ++this.changeCount_; |
| }; |
| |
| /** |
| * Set the bytes of the entire signature info encoding (including the type |
| * code). |
| * @param {Blob} signatureInfoEncoding A Blob with the encoding bytes. |
| * @param {number} (optional) The type code of the signature type, or null if |
| * not known. (When a GenericSignature is created by wire decoding, it sets |
| * the typeCode.) |
| */ |
| GenericSignature.prototype.setSignatureInfoEncoding = function |
| (signatureInfoEncoding, typeCode) |
| { |
| this.signatureInfoEncoding_ = |
| typeof signatureInfoEncoding === 'object' && signatureInfoEncoding instanceof Blob ? |
| signatureInfoEncoding : new Blob(signatureInfoEncoding); |
| this.typeCode_ = typeCode; |
| ++this.changeCount_; |
| }; |
| |
| /** |
| * Get the change count, which is incremented each time this object (or a child |
| * object) is changed. |
| * @return {number} The change count. |
| */ |
| GenericSignature.prototype.getChangeCount = function() |
| { |
| return this.changeCount_; |
| }; |
| |
| /** |
| * @@deprecated Use getSignature and setSignature. |
| */ |
| Object.defineProperty(GenericSignature.prototype, "signature", |
| { get: function() { return this.getSignatureAsBuffer(); }, |
| set: function(val) { this.setSignature(val); } }); |
| /** |
| * This class represents an NDN Data Signature object. |
| * Copyright (C) 2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Blob = require('./util/blob.js').Blob; /** @ignore */ |
| var ChangeCounter = require('./util/change-counter.js').ChangeCounter; /** @ignore */ |
| var KeyLocator = require('./key-locator.js').KeyLocator; |
| |
| /** |
| * An HmacWithSha256Signature holds the signature bits and other info |
| * representing an HmacWithSha256 signature in a packet. |
| * Create a new HmacWithSha256Signature object, possibly copying values from |
| * another object. |
| * |
| * @param {HmacWithSha256Signature} value (optional) If value is a |
| * HmacWithSha256Signature, copy its values. If value is omitted, the keyLocator |
| * is the default with unspecified values and the signature is unspecified. |
| * @constructor |
| */ |
| var HmacWithSha256Signature = function HmacWithSha256Signature(value) |
| { |
| if (typeof value === 'object' && value instanceof HmacWithSha256Signature) { |
| // Copy the values. |
| this.keyLocator_ = new ChangeCounter(new KeyLocator(value.getKeyLocator())); |
| this.signature_ = value.signature_; |
| } |
| else { |
| this.keyLocator_ = new ChangeCounter(new KeyLocator()); |
| this.signature_ = new Blob(); |
| } |
| |
| this.changeCount_ = 0; |
| }; |
| |
| exports.HmacWithSha256Signature = HmacWithSha256Signature; |
| |
| /** |
| * Create a new HmacWithSha256Signature which is a copy of this object. |
| * @return {HmacWithSha256Signature} A new object which is a copy of this object. |
| */ |
| HmacWithSha256Signature.prototype.clone = function() |
| { |
| return new HmacWithSha256Signature(this); |
| }; |
| |
| /** |
| * Get the key locator. |
| * @return {KeyLocator} The key locator. |
| */ |
| HmacWithSha256Signature.prototype.getKeyLocator = function() |
| { |
| return this.keyLocator_.get(); |
| }; |
| |
| /** |
| * Get the data packet's signature bytes. |
| * @return {Blob} The signature bytes. If not specified, the value isNull(). |
| */ |
| HmacWithSha256Signature.prototype.getSignature = function() |
| { |
| return this.signature_; |
| }; |
| |
| /** |
| * @deprecated Use getSignature. This method returns a Buffer which is the former |
| * behavior of getSignature, and should only be used while updating your code. |
| */ |
| HmacWithSha256Signature.prototype.getSignatureAsBuffer = function() |
| { |
| return this.signature_.buf(); |
| }; |
| |
| /** |
| * Set the key locator to a copy of the given keyLocator. |
| * @param {KeyLocator} keyLocator The KeyLocator to copy. |
| */ |
| HmacWithSha256Signature.prototype.setKeyLocator = function(keyLocator) |
| { |
| this.keyLocator_.set(typeof keyLocator === 'object' && |
| keyLocator instanceof KeyLocator ? |
| new KeyLocator(keyLocator) : new KeyLocator()); |
| ++this.changeCount_; |
| }; |
| |
| /** |
| * Set the data packet's signature bytes. |
| * @param {Blob} signature |
| */ |
| HmacWithSha256Signature.prototype.setSignature = function(signature) |
| { |
| this.signature_ = typeof signature === 'object' && signature instanceof Blob ? |
| signature : new Blob(signature); |
| ++this.changeCount_; |
| }; |
| |
| /** |
| * Get the change count, which is incremented each time this object (or a child |
| * object) is changed. |
| * @return {number} The change count. |
| */ |
| HmacWithSha256Signature.prototype.getChangeCount = function() |
| { |
| // Make sure each of the checkChanged is called. |
| var changed = this.keyLocator_.checkChanged(); |
| if (changed) |
| // A child object has changed, so update the change count. |
| ++this.changeCount_; |
| |
| return this.changeCount_; |
| }; |
| |
| // Define properties so we can change member variable types and implement changeCount_. |
| Object.defineProperty(HmacWithSha256Signature.prototype, "keyLocator", |
| { get: function() { return this.getKeyLocator(); }, |
| set: function(val) { this.setKeyLocator(val); } }); |
| /** |
| * @@deprecated Use getSignature and setSignature. |
| */ |
| Object.defineProperty(HmacWithSha256Signature.prototype, "signature", |
| { get: function() { return this.getSignatureAsBuffer(); }, |
| set: function(val) { this.setSignature(val); } }); |
| /** |
| * This class represents an NDN Data Signature object. |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Blob = require('./util/blob.js').Blob; |
| |
| /** |
| * A DigestSha256Signature extends Signature and holds the signature bits (which |
| * are only the SHA256 digest) and an empty SignatureInfo for a data packet or |
| * signed interest. |
| * |
| * Create a new DigestSha256Signature object, possibly copying values from |
| * another object. |
| * |
| * @param {DigestSha256Signature} value (optional) If value is a |
| * DigestSha256Signature, copy its values. If value is omitted, the signature |
| * is unspecified. |
| * @constructor |
| */ |
| var DigestSha256Signature = function DigestSha256Signature(value) |
| { |
| if (typeof value === 'object' && value instanceof DigestSha256Signature) |
| // Copy the values. |
| this.signature_ = value.signature_; |
| else |
| this.signature_ = new Blob(); |
| |
| this.changeCount_ = 0; |
| }; |
| |
| exports.DigestSha256Signature = DigestSha256Signature; |
| |
| /** |
| * Create a new DigestSha256Signature which is a copy of this object. |
| * @return {DigestSha256Signature} A new object which is a copy of this object. |
| */ |
| DigestSha256Signature.prototype.clone = function() |
| { |
| return new DigestSha256Signature(this); |
| }; |
| |
| /** |
| * Get the signature bytes (which are only the digest). |
| * @return {Blob} The signature bytes. If not specified, the value isNull(). |
| */ |
| DigestSha256Signature.prototype.getSignature = function() |
| { |
| return this.signature_; |
| }; |
| |
| /** |
| * Set the signature bytes to the given value. |
| * @param {Blob} signature |
| */ |
| DigestSha256Signature.prototype.setSignature = function(signature) |
| { |
| this.signature_ = typeof signature === 'object' && signature instanceof Blob ? |
| signature : new Blob(signature); |
| ++this.changeCount_; |
| }; |
| |
| /** |
| * Get the change count, which is incremented each time this object is changed. |
| * @return {number} The change count. |
| */ |
| DigestSha256Signature.prototype.getChangeCount = function() |
| { |
| return this.changeCount_; |
| }; |
| |
| // Define properties so we can change member variable types and implement changeCount_. |
| /** |
| * @@deprecated Use getSignature and setSignature. |
| */ |
| Object.defineProperty(DigestSha256Signature.prototype, "signature", |
| { get: function() { return this.getSignature(); }, |
| set: function(val) { this.setSignature(val); } }); |
| /** |
| * This class represents an NDN Data object. |
| * Copyright (C) 2013-2016 Regents of the University of California. |
| * @author: Meki Cheraoui |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Blob = require('./util/blob.js').Blob; /** @ignore */ |
| var SignedBlob = require('./util/signed-blob.js').SignedBlob; /** @ignore */ |
| var ChangeCounter = require('./util/change-counter.js').ChangeCounter; /** @ignore */ |
| var Name = require('./name.js').Name; /** @ignore */ |
| var Sha256WithRsaSignature = require('./sha256-with-rsa-signature.js').Sha256WithRsaSignature; /** @ignore */ |
| var MetaInfo = require('./meta-info.js').MetaInfo; /** @ignore */ |
| var IncomingFaceId = require('./lp/incoming-face-id.js').IncomingFaceId; /** @ignore */ |
| var WireFormat = require('./encoding/wire-format.js').WireFormat; /** @ignore */ |
| var Crypto = require('./crypto.js'); |
| |
| /** |
| * Create a new Data with the optional values. There are 2 forms of constructor: |
| * new Data([name] [, content]); |
| * new Data(name, metaInfo [, content]); |
| * |
| * @constructor |
| * @param {Name} name |
| * @param {MetaInfo} metaInfo |
| * @param {Buffer} content |
| */ |
| var Data = function Data(nameOrData, metaInfoOrContent, arg3) |
| { |
| if (nameOrData instanceof Data) { |
| // The copy constructor. |
| var data = nameOrData; |
| |
| // Copy the Data object. |
| this.name_ = new ChangeCounter(new Name(data.getName())); |
| this.metaInfo_ = new ChangeCounter(new MetaInfo(data.getMetaInfo())); |
| this.signature_ = new ChangeCounter(data.getSignature().clone()); |
| this.content_ = data.content_; |
| this.defaultWireEncoding_ = data.getDefaultWireEncoding(); |
| this.defaultFullName_ = data.defaultFullName_; |
| this.defaultWireEncodingFormat_ = data.defaultWireEncodingFormat_; |
| } |
| else { |
| var name = nameOrData; |
| if (typeof name === 'string') |
| this.name_ = new ChangeCounter(new Name(name)); |
| else |
| this.name_ = new ChangeCounter(typeof name === 'object' && name instanceof Name ? |
| new Name(name) : new Name()); |
| |
| var metaInfo; |
| var content; |
| if (typeof metaInfoOrContent === 'object' && |
| metaInfoOrContent instanceof MetaInfo) { |
| metaInfo = metaInfoOrContent; |
| content = arg3; |
| } |
| else { |
| metaInfo = null; |
| content = metaInfoOrContent; |
| } |
| |
| this.metaInfo_ = new ChangeCounter(typeof metaInfo === 'object' && metaInfo instanceof MetaInfo ? |
| new MetaInfo(metaInfo) : new MetaInfo()); |
| |
| this.content_ = typeof content === 'object' && content instanceof Blob ? |
| content : new Blob(content, true); |
| |
| this.signature_ = new ChangeCounter(new Sha256WithRsaSignature()); |
| this.defaultWireEncoding_ = new SignedBlob(); |
| this.defaultFullName_ = new Name(); |
| this.defaultWireEncodingFormat_ = null; |
| } |
| |
| this.getDefaultWireEncodingChangeCount_ = 0; |
| this.changeCount_ = 0; |
| this.lpPacket_ = null; |
| }; |
| |
| exports.Data = Data; |
| |
| /** |
| * Get the data packet's name. |
| * @return {Name} The name. If not specified, the name size() is 0. |
| */ |
| Data.prototype.getName = function() |
| { |
| return this.name_.get(); |
| }; |
| |
| /** |
| * Get the data packet's meta info. |
| * @return {MetaInfo} The meta info. |
| */ |
| Data.prototype.getMetaInfo = function() |
| { |
| return this.metaInfo_.get(); |
| }; |
| |
| /** |
| * Get the data packet's signature object. |
| * @return {Signature} The signature object. |
| */ |
| Data.prototype.getSignature = function() |
| { |
| return this.signature_.get(); |
| }; |
| |
| /** |
| * Get the data packet's content. |
| * @return {Blob} The content as a Blob, which isNull() if unspecified. |
| */ |
| Data.prototype.getContent = function() |
| { |
| return this.content_; |
| }; |
| |
| /** |
| * @deprecated Use getContent. This method returns a Buffer which is the former |
| * behavior of getContent, and should only be used while updating your code. |
| */ |
| Data.prototype.getContentAsBuffer = function() |
| { |
| return this.content_.buf(); |
| }; |
| |
| /** |
| * Return the default wire encoding, which was encoded with |
| * getDefaultWireEncodingFormat(). |
| * @return {SignedBlob} The default wire encoding, whose isNull() may be true |
| * if there is no default wire encoding. |
| */ |
| Data.prototype.getDefaultWireEncoding = function() |
| { |
| if (this.getDefaultWireEncodingChangeCount_ != this.getChangeCount()) { |
| // The values have changed, so the default wire encoding is invalidated. |
| this.defaultWireEncoding_ = new SignedBlob(); |
| this.defaultWireEncodingFormat_ = null; |
| this.getDefaultWireEncodingChangeCount_ = this.getChangeCount(); |
| } |
| |
| return this.defaultWireEncoding_; |
| }; |
| |
| /** |
| * Get the WireFormat which is used by getDefaultWireEncoding(). |
| * @return {WireFormat} The WireFormat, which is only meaningful if the |
| * getDefaultWireEncoding() is not isNull(). |
| */ |
| Data.prototype.getDefaultWireEncodingFormat = function() |
| { |
| return this.defaultWireEncodingFormat_; |
| }; |
| |
| /** |
| * Get the incoming face ID according to the incoming packet header. |
| * @return {number} The incoming face ID. If not specified, return null. |
| */ |
| Data.prototype.getIncomingFaceId = function() |
| { |
| var field = |
| this.lpPacket_ === null ? null : IncomingFaceId.getFirstHeader(this.lpPacket_); |
| return field === null ? null : field.getFaceId(); |
| }; |
| |
| /** |
| * Get the Data packet's full name, which includes the final |
| * ImplicitSha256Digest component based on the wire encoding for a particular |
| * wire format. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * this object. If omitted, use WireFormat.getDefaultWireFormat(). |
| * @return {Name} The full name. You must not change the Name object - if you |
| * need to change it then make a copy. |
| */ |
| Data.prototype.getFullName = function(wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| |
| // The default full name depends on the default wire encoding. |
| if (!this.getDefaultWireEncoding().isNull() && |
| this.defaultFullName_.size() > 0 && |
| this.getDefaultWireEncodingFormat() == wireFormat) |
| // We already have a full name. A non-null default wire encoding means |
| // that the Data packet fields have not changed. |
| return this.defaultFullName_; |
| |
| var fullName = new Name(this.getName()); |
| var hash = Crypto.createHash('sha256'); |
| // wireEncode will use the cached encoding if possible. |
| hash.update(this.wireEncode(wireFormat).buf()); |
| fullName.appendImplicitSha256Digest(new Blob(hash.digest(), false)); |
| |
| if (wireFormat == WireFormat.getDefaultWireFormat()) |
| // wireEncode has already set defaultWireEncodingFormat_. |
| this.defaultFullName_ = fullName; |
| |
| return fullName; |
| }; |
| |
| /** |
| * Set name to a copy of the given Name. |
| * @param {Name} name The Name which is copied. |
| * @return {Data} This Data so that you can chain calls to update values. |
| */ |
| Data.prototype.setName = function(name) |
| { |
| this.name_.set(typeof name === 'object' && name instanceof Name ? |
| new Name(name) : new Name()); |
| ++this.changeCount_; |
| return this; |
| }; |
| |
| /** |
| * Set metaInfo to a copy of the given MetaInfo. |
| * @param {MetaInfo} metaInfo The MetaInfo which is copied. |
| * @return {Data} This Data so that you can chain calls to update values. |
| */ |
| Data.prototype.setMetaInfo = function(metaInfo) |
| { |
| this.metaInfo_.set(typeof metaInfo === 'object' && metaInfo instanceof MetaInfo ? |
| new MetaInfo(metaInfo) : new MetaInfo()); |
| ++this.changeCount_; |
| return this; |
| }; |
| |
| /** |
| * Set the signature to a copy of the given signature. |
| * @param {Signature} signature The signature object which is cloned. |
| * @return {Data} This Data so that you can chain calls to update values. |
| */ |
| Data.prototype.setSignature = function(signature) |
| { |
| this.signature_.set(signature == null ? |
| new Sha256WithRsaSignature() : signature.clone()); |
| ++this.changeCount_; |
| return this; |
| }; |
| |
| /** |
| * Set the content to the given value. |
| * @param {Blob|Buffer} content The content bytes. If content is not a Blob, |
| * then create a new Blob to copy the bytes (otherwise take another pointer to |
| * the same Blob). |
| * @return {Data} This Data so that you can chain calls to update values. |
| */ |
| Data.prototype.setContent = function(content) |
| { |
| this.content_ = typeof content === 'object' && content instanceof Blob ? |
| content : new Blob(content, true); |
| ++this.changeCount_; |
| return this; |
| }; |
| |
| /** |
| * Encode this Data for a particular wire format. If wireFormat is the default |
| * wire format, also set the defaultWireEncoding field to the encoded result. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * this object. If omitted, use WireFormat.getDefaultWireFormat(). |
| * @return {SignedBlob} The encoded buffer in a SignedBlob object. |
| */ |
| Data.prototype.wireEncode = function(wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| |
| if (!this.getDefaultWireEncoding().isNull() && |
| this.getDefaultWireEncodingFormat() == wireFormat) |
| // We already have an encoding in the desired format. |
| return this.getDefaultWireEncoding(); |
| |
| var result = wireFormat.encodeData(this); |
| var wireEncoding = new SignedBlob |
| (result.encoding, result.signedPortionBeginOffset, |
| result.signedPortionEndOffset); |
| |
| if (wireFormat == WireFormat.getDefaultWireFormat()) |
| // This is the default wire encoding. |
| this.setDefaultWireEncoding |
| (wireEncoding, WireFormat.getDefaultWireFormat()); |
| return wireEncoding; |
| }; |
| |
| /** |
| * Decode the input using a particular wire format and update this Data. If |
| * wireFormat is the default wire format, also set the defaultWireEncoding to |
| * another pointer to the input. |
| * @param {Blob|Buffer} input The buffer with the bytes to decode. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to decode |
| * this object. If omitted, use WireFormat.getDefaultWireFormat(). |
| */ |
| Data.prototype.wireDecode = function(input, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| |
| var result; |
| if (typeof input === 'object' && input instanceof Blob) |
| // Input is a blob, so get its buf() and set copy false. |
| result = wireFormat.decodeData(this, input.buf(), false); |
| else |
| result = wireFormat.decodeData(this, input, true); |
| |
| if (wireFormat == WireFormat.getDefaultWireFormat()) |
| // This is the default wire encoding. In the Blob constructor, set copy |
| // true, but if input is already a Blob, it won't copy. |
| this.setDefaultWireEncoding(new SignedBlob |
| (new Blob(input, true), result.signedPortionBeginOffset, |
| result.signedPortionEndOffset), |
| WireFormat.getDefaultWireFormat()); |
| else |
| this.setDefaultWireEncoding(new SignedBlob(), null); |
| }; |
| |
| /** |
| * An internal library method to set the LpPacket for an incoming packet. The |
| * application should not call this. |
| * @param {LpPacket} lpPacket The LpPacket. This does not make a copy. |
| * @return {Data} This Data so that you can chain calls to update values. |
| * @note This is an experimental feature. This API may change in the future. |
| */ |
| Data.prototype.setLpPacket = function(lpPacket) |
| { |
| this.lpPacket_ = lpPacket; |
| // Don't update changeCount_ since this doesn't affect the wire encoding. |
| return this; |
| } |
| |
| /** |
| * Get the change count, which is incremented each time this object (or a child |
| * object) is changed. |
| * @return {number} The change count. |
| */ |
| Data.prototype.getChangeCount = function() |
| { |
| // Make sure each of the checkChanged is called. |
| var changed = this.name_.checkChanged(); |
| changed = this.metaInfo_.checkChanged() || changed; |
| changed = this.signature_.checkChanged() || changed; |
| if (changed) |
| // A child object has changed, so update the change count. |
| ++this.changeCount_; |
| |
| return this.changeCount_; |
| }; |
| |
| Data.prototype.setDefaultWireEncoding = function |
| (defaultWireEncoding, defaultWireEncodingFormat) |
| { |
| this.defaultWireEncoding_ = defaultWireEncoding; |
| this.defaultWireEncodingFormat_ = defaultWireEncodingFormat; |
| // Set getDefaultWireEncodingChangeCount_ so that the next call to |
| // getDefaultWireEncoding() won't clear _defaultWireEncoding. |
| this.getDefaultWireEncodingChangeCount_ = this.getChangeCount(); |
| }; |
| |
| // Define properties so we can change member variable types and implement changeCount_. |
| Object.defineProperty(Data.prototype, "name", |
| { get: function() { return this.getName(); }, |
| set: function(val) { this.setName(val); } }); |
| Object.defineProperty(Data.prototype, "metaInfo", |
| { get: function() { return this.getMetaInfo(); }, |
| set: function(val) { this.setMetaInfo(val); } }); |
| Object.defineProperty(Data.prototype, "signature", |
| { get: function() { return this.getSignature(); }, |
| set: function(val) { this.setSignature(val); } }); |
| /** |
| * @deprecated Use getContent and setContent. |
| */ |
| Object.defineProperty(Data.prototype, "content", |
| { get: function() { return this.getContentAsBuffer(); }, |
| set: function(val) { this.setContent(val); } }); |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * Create a new SecurityException to report an exception from the security |
| * library, wrapping the given error object. |
| * Call with: throw new SecurityException(new Error("message")). |
| * @constructor |
| * @param {Error} error The exception created with new Error. |
| */ |
| function SecurityException(error) |
| { |
| if (error) { |
| error.__proto__ = SecurityException.prototype; |
| return error; |
| } |
| } |
| |
| SecurityException.prototype = new Error(); |
| SecurityException.prototype.name = "SecurityException"; |
| |
| exports.SecurityException = SecurityException; |
| |
| function UnrecognizedKeyFormatException(error) |
| { |
| // Call the base constructor. |
| SecurityException.call(this, error); |
| } |
| UnrecognizedKeyFormatException.prototype = new SecurityException(); |
| UnrecognizedKeyFormatException.prototype.name = "UnrecognizedKeyFormatException"; |
| |
| exports.UnrecognizedKeyFormatException = UnrecognizedKeyFormatException; |
| |
| function UnrecognizedDigestAlgorithmException(error) |
| { |
| // Call the base constructor. |
| SecurityException.call(this, error); |
| } |
| UnrecognizedDigestAlgorithmException.prototype = new SecurityException(); |
| UnrecognizedDigestAlgorithmException.prototype.name = "UnrecognizedDigestAlgorithmException"; |
| |
| exports.UnrecognizedDigestAlgorithmException = UnrecognizedDigestAlgorithmException; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * From ndn-cxx security by Yingdi Yu <yingdi@cs.ucla.edu>. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * This module defines constants used by the security library. |
| */ |
| |
| /** |
| * The KeyType integer is used by the Sqlite key storage, so don't change them. |
| * Make these the same as ndn-cxx in case the storage file is shared. |
| * @constructor |
| */ |
| var KeyType = function KeyType() |
| { |
| } |
| |
| exports.KeyType = KeyType; |
| |
| KeyType.RSA = 0; |
| KeyType.ECDSA = 1; |
| KeyType.AES = 128; |
| |
| var KeyClass = function KeyClass() |
| { |
| }; |
| |
| exports.KeyClass = KeyClass; |
| |
| KeyClass.PUBLIC = 1; |
| KeyClass.PRIVATE = 2; |
| KeyClass.SYMMETRIC = 3; |
| |
| var DigestAlgorithm = function DigestAlgorithm() |
| { |
| }; |
| |
| exports.DigestAlgorithm = DigestAlgorithm; |
| |
| DigestAlgorithm.SHA256 = 1; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * From ndn-cxx security by Yingdi Yu <yingdi@cs.ucla.edu>. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var KeyType = require('./security-types.js').KeyType; |
| |
| /** |
| * KeyParams is a base class for key parameters. Its subclasses are used to |
| * store parameters for key generation. You should create one of the subclasses, |
| * for example RsaKeyParams. |
| * @constructor |
| */ |
| var KeyParams = function KeyParams(keyType) |
| { |
| this.keyType = keyType; |
| }; |
| |
| exports.KeyParams = KeyParams; |
| |
| KeyParams.prototype.getKeyType = function() |
| { |
| return this.keyType; |
| }; |
| |
| var RsaKeyParams = function RsaKeyParams(size) |
| { |
| // Call the base constructor. |
| KeyParams.call(this, RsaKeyParams.getType()); |
| |
| if (size == null) |
| size = RsaKeyParams.getDefaultSize(); |
| this.size = size; |
| }; |
| |
| RsaKeyParams.prototype = new KeyParams(); |
| RsaKeyParams.prototype.name = "RsaKeyParams"; |
| |
| exports.RsaKeyParams = RsaKeyParams; |
| |
| RsaKeyParams.prototype.getKeySize = function() |
| { |
| return this.size; |
| }; |
| |
| RsaKeyParams.getDefaultSize = function() { return 2048; }; |
| |
| RsaKeyParams.getType = function() { return KeyType.RSA; }; |
| |
| var EcdsaKeyParams = function EcdsaKeyParams(size) |
| { |
| // Call the base constructor. |
| KeyParams.call(this, EcdsaKeyParams.getType()); |
| |
| if (size == null) |
| size = EcdsaKeyParams.getDefaultSize(); |
| this.size = size; |
| }; |
| |
| EcdsaKeyParams.prototype = new KeyParams(); |
| EcdsaKeyParams.prototype.name = "EcdsaKeyParams"; |
| |
| exports.EcdsaKeyParams = EcdsaKeyParams; |
| |
| EcdsaKeyParams.prototype.getKeySize = function() |
| { |
| return this.size; |
| }; |
| |
| EcdsaKeyParams.getDefaultSize = function() { return 256; }; |
| |
| EcdsaKeyParams.getType = function() { return KeyType.ECDSA; }; |
| |
| var AesKeyParams = function AesKeyParams(size) |
| { |
| // Call the base constructor. |
| KeyParams.call(this, AesKeyParams.getType()); |
| |
| if (size == null) |
| size = AesKeyParams.getDefaultSize(); |
| this.size = size; |
| }; |
| |
| AesKeyParams.prototype = new KeyParams(); |
| AesKeyParams.prototype.name = "AesKeyParams"; |
| |
| exports.AesKeyParams = AesKeyParams; |
| |
| AesKeyParams.prototype.getKeySize = function() |
| { |
| return this.size; |
| }; |
| |
| AesKeyParams.getDefaultSize = function() { return 64; }; |
| |
| AesKeyParams.getType = function() { return KeyType.AES; }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * From ndn-cxx security by Yingdi Yu <yingdi@cs.ucla.edu>. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| // Use capitalized Crypto to not clash with the browser's crypto.subtle. |
| /** @ignore */ |
| var Crypto = require('../../crypto.js'); /** @ignore */ |
| var Blob = require('../../util/blob.js').Blob; /** @ignore */ |
| var DerDecodingException = require('../../encoding/der/der-decoding-exception.js').DerDecodingException; /** @ignore */ |
| var DerNode = require('../../encoding/der/der-node.js').DerNode; /** @ignore */ |
| var SecurityException = require('../security-exception.js').SecurityException; /** @ignore */ |
| var UnrecognizedKeyFormatException = require('../security-exception.js').UnrecognizedKeyFormatException; /** @ignore */ |
| var KeyType = require('../security-types.js').KeyType; /** @ignore */ |
| var DigestAlgorithm = require('../security-types.js').DigestAlgorithm; |
| |
| /** |
| * Create a new PublicKey by decoding the keyDer. Set the key type from the |
| * decoding. |
| * @param {Blob} keyDer The blob of the SubjectPublicKeyInfo DER. |
| * @throws UnrecognizedKeyFormatException if can't decode the key DER. |
| * @constructor |
| */ |
| var PublicKey = function PublicKey(keyDer) |
| { |
| if (!keyDer) { |
| this.keyDer = new Blob(); |
| this.keyType = null; |
| return; |
| } |
| |
| this.keyDer = keyDer; |
| |
| // Get the public key OID. |
| var oidString = null; |
| try { |
| var parsedNode = DerNode.parse(keyDer.buf(), 0); |
| var rootChildren = parsedNode.getChildren(); |
| var algorithmIdChildren = DerNode.getSequence(rootChildren, 0).getChildren(); |
| oidString = algorithmIdChildren[0].toVal(); |
| } |
| catch (ex) { |
| throw new UnrecognizedKeyFormatException(new Error |
| ("PublicKey.decodeKeyType: Error decoding the public key: " + ex.message)); |
| } |
| |
| // Verify that the we can decode. |
| if (oidString == PublicKey.RSA_ENCRYPTION_OID) { |
| this.keyType = KeyType.RSA; |
| // TODO: Check RSA decoding. |
| } |
| else if (oidString == PublicKey.EC_ENCRYPTION_OID) { |
| this.keyType = KeyType.ECDSA; |
| // TODO: Check EC decoding. |
| } |
| }; |
| |
| exports.PublicKey = PublicKey; |
| |
| /** |
| * Encode the public key into DER. |
| * @return {DerNode} The encoded DER syntax tree. |
| */ |
| PublicKey.prototype.toDer = function() |
| { |
| return DerNode.parse(this.keyDer.buf()); |
| }; |
| |
| /** |
| * Get the key type. |
| * @return {number} The key type as an int from KeyType. |
| */ |
| PublicKey.prototype.getKeyType = function() |
| { |
| return this.keyType; |
| }; |
| |
| /** |
| * Get the digest of the public key. |
| * @param {number} digestAlgorithm (optional) The integer from DigestAlgorithm, |
| * such as DigestAlgorithm.SHA256. If omitted, use DigestAlgorithm.SHA256 . |
| * @return {Blob} The digest value. |
| */ |
| PublicKey.prototype.getDigest = function(digestAlgorithm) |
| { |
| if (digestAlgorithm == undefined) |
| digestAlgorithm = DigestAlgorithm.SHA256; |
| |
| if (digestAlgorithm == DigestAlgorithm.SHA256) { |
| var hash = Crypto.createHash('sha256'); |
| hash.update(this.keyDer.buf()); |
| return new Blob(hash.digest(), false); |
| } |
| else |
| throw new SecurityException(new Error("Wrong format!")); |
| }; |
| |
| /** |
| * Get the raw bytes of the public key in DER format. |
| * @return {Blob} The public key DER. |
| */ |
| PublicKey.prototype.getKeyDer = function() |
| { |
| return this.keyDer; |
| }; |
| |
| PublicKey.RSA_ENCRYPTION_OID = "1.2.840.113549.1.1.1"; |
| PublicKey.EC_ENCRYPTION_OID = "1.2.840.10045.2.1"; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * From ndn-cxx security by Yingdi Yu <yingdi@cs.ucla.edu>. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var DerNode = require('../../encoding/der/der-node.js').DerNode; /** @ignore */ |
| var OID = require('../../encoding/oid.js').OID; |
| |
| /** |
| * A CertificateExtension represents the Extension entry in a certificate. |
| * Create a new CertificateExtension. |
| * @param {string|OID} oid The oid of subject description entry. |
| * @param {boolean} isCritical If true, the extension must be handled. |
| * @param {Blob} value The extension value. |
| * @constructor |
| */ |
| var CertificateExtension = function CertificateExtension(oid, isCritical, value) |
| { |
| if (typeof oid === 'string') |
| this.extensionId = new OID(oid); |
| else |
| // Assume oid is already an OID. |
| this.extensionId = oid; |
| |
| this.isCritical = isCritical; |
| this.extensionValue = value; |
| }; |
| |
| exports.CertificateExtension = CertificateExtension; |
| |
| /** |
| * Encode the object into a DER syntax tree. |
| * @return {DerNode} The encoded DER syntax tree. |
| */ |
| CertificateExtension.prototype.toDer = function() |
| { |
| var root = new DerNode.DerSequence(); |
| |
| var extensionId = new DerNode.DerOid(this.extensionId); |
| var isCritical = new DerNode.DerBoolean(this.isCritical); |
| var extensionValue = new DerNode.DerOctetString(this.extensionValue.buf()); |
| |
| root.addChild(extensionId); |
| root.addChild(isCritical); |
| root.addChild(extensionValue); |
| |
| root.getSize(); |
| |
| return root; |
| }; |
| |
| CertificateExtension.prototype.toDerBlob = function() |
| { |
| return this.toDer().encode(); |
| }; |
| |
| CertificateExtension.prototype.getOid = function() |
| { |
| return this.extensionId; |
| }; |
| |
| CertificateExtension.prototype.getIsCritical = function() |
| { |
| return this.isCritical; |
| }; |
| |
| CertificateExtension.prototype.getValue = function() |
| { |
| return this.extensionValue; |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * From ndn-cxx security by Yingdi Yu <yingdi@cs.ucla.edu>. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Blob = require('../../util/blob.js').Blob; /** @ignore */ |
| var OID = require('../../encoding/oid.js').OID; /** @ignore */ |
| var DerNode = require('../../encoding/der/der-node.js').DerNode; |
| |
| /** |
| * A CertificateSubjectDescription represents the SubjectDescription entry in a |
| * Certificate. |
| * Create a new CertificateSubjectDescription. |
| * @param {string|OID} oid The oid of the subject description entry. |
| * @param {string} value The value of the subject description entry. |
| * @constructor |
| */ |
| var CertificateSubjectDescription = function CertificateSubjectDescription |
| (oid, value) |
| { |
| if (typeof oid === 'string') |
| this.oid = new OID(oid); |
| else |
| // Assume oid is already an OID. |
| this.oid = oid; |
| |
| this.value = value; |
| }; |
| |
| exports.CertificateSubjectDescription = CertificateSubjectDescription; |
| |
| /** |
| * Encode the object into a DER syntax tree. |
| * @return {DerNode} The encoded DER syntax tree. |
| */ |
| CertificateSubjectDescription.prototype.toDer = function() |
| { |
| var root = new DerNode.DerSequence(); |
| |
| var oid = new DerNode.DerOid(this.oid); |
| // Use Blob to convert the String to a ByteBuffer. |
| var value = new DerNode.DerPrintableString(new Blob(this.value).buf()); |
| |
| root.addChild(oid); |
| root.addChild(value); |
| |
| return root; |
| }; |
| |
| CertificateSubjectDescription.prototype.getOidString = function() |
| { |
| return this.oid.toString(); |
| }; |
| |
| CertificateSubjectDescription.prototype.getValue = function() |
| { |
| return this.value; |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * From ndn-cxx security by Yingdi Yu <yingdi@cs.ucla.edu>. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Data = require('../../data.js').Data; /** @ignore */ |
| var ContentType = require('../../meta-info.js').ContentType; /** @ignore */ |
| var WireFormat = require('../../encoding/wire-format.js').WireFormat; /** @ignore */ |
| var DerNode = require('../../encoding/der/der-node.js').DerNode; /** @ignore */ |
| var KeyType = require('../../security/security-types.js').KeyType; /** @ignore */ |
| var PublicKey = require('./public-key.js').PublicKey; /** @ignore */ |
| var CertificateSubjectDescription = require('./certificate-subject-description.js').CertificateSubjectDescription; /** @ignore */ |
| var CertificateExtension = require('./certificate-extension.js').CertificateExtension; |
| |
| /** |
| * Create a Certificate from the content in the data packet (if not omitted). |
| * @param {Data} data (optional) The data packet with the content to decode. |
| * If omitted, create a Certificate with default values and the Data content |
| * is empty. |
| * @constructor |
| */ |
| var Certificate = function Certificate(data) |
| { |
| // Call the base constructor. |
| if (data != undefined) |
| Data.call(this, data); |
| else |
| Data.call(this); |
| |
| this.subjectDescriptionList = []; // of CertificateSubjectDescription |
| this.extensionList = []; // of CertificateExtension |
| this.notBefore = Number.MAX_VALUE; // MillisecondsSince1970 |
| this.notAfter = -Number.MAX_VALUE; // MillisecondsSince1970 |
| this.key = new PublicKey(); |
| |
| if (data != undefined) |
| this.decode(); |
| }; |
| Certificate.prototype = new Data(); |
| Certificate.prototype.name = "Certificate"; |
| |
| exports.Certificate = Certificate; |
| |
| /** |
| * Encode the contents of the certificate in DER format and set the Content |
| * and MetaInfo fields. |
| */ |
| Certificate.prototype.encode = function() |
| { |
| var root = this.toDer(); |
| this.setContent(root.encode()); |
| this.getMetaInfo().setType(ContentType.KEY); |
| }; |
| |
| /** |
| * Add a subject description. |
| * @param {CertificateSubjectDescription} description The description to be added. |
| */ |
| Certificate.prototype.addSubjectDescription = function(description) |
| { |
| this.subjectDescriptionList.push(description); |
| }; |
| |
| /** |
| * Get the subject description list. |
| * @return {Array<CertificateSubjectDescription>} The subject description list. |
| */ |
| Certificate.prototype.getSubjectDescriptionList = function() |
| { |
| return this.subjectDescriptionList; |
| }; |
| |
| /** |
| * Add a certificate extension. |
| * @param {CertificateSubjectDescription} extension The extension to be added. |
| */ |
| Certificate.prototype.addExtension = function(extension) |
| { |
| this.extensionList.push(extension); |
| }; |
| |
| /** |
| * Get the certificate extension list. |
| * @return {Array<CertificateExtension>} The extension list. |
| */ |
| Certificate.prototype.getExtensionList = function() |
| { |
| return this.extensionList; |
| }; |
| |
| Certificate.prototype.setNotBefore = function(notBefore) |
| { |
| this.notBefore = notBefore; |
| }; |
| |
| Certificate.prototype.getNotBefore = function() |
| { |
| return this.notBefore; |
| }; |
| |
| Certificate.prototype.setNotAfter = function(notAfter) |
| { |
| this.notAfter = notAfter; |
| }; |
| |
| Certificate.prototype.getNotAfter = function() |
| { |
| return this.notAfter; |
| }; |
| |
| Certificate.prototype.setPublicKeyInfo = function(key) |
| { |
| this.key = key; |
| }; |
| |
| Certificate.prototype.getPublicKeyInfo = function() |
| { |
| return this.key; |
| }; |
| |
| /** |
| * Check if the certificate is valid. |
| * @return {Boolean} True if the current time is earlier than notBefore. |
| */ |
| Certificate.prototype.isTooEarly = function() |
| { |
| var now = new Date().getTime(); |
| return now < this.notBefore; |
| }; |
| |
| /** |
| * Check if the certificate is valid. |
| * @return {Boolean} True if the current time is later than notAfter. |
| */ |
| Certificate.prototype.isTooLate = function() |
| { |
| var now = new Date().getTime(); |
| return now > this.notAfter; |
| }; |
| |
| /** |
| * Encode the certificate fields in DER format. |
| * @return {DerSequence} The DER encoded contents of the certificate. |
| */ |
| Certificate.prototype.toDer = function() |
| { |
| var root = new DerNode.DerSequence(); |
| var validity = new DerNode.DerSequence(); |
| var notBefore = new DerNode.DerGeneralizedTime(this.notBefore); |
| var notAfter = new DerNode.DerGeneralizedTime(this.notAfter); |
| |
| validity.addChild(notBefore); |
| validity.addChild(notAfter); |
| |
| root.addChild(validity); |
| |
| var subjectList = new DerNode.DerSequence(); |
| for (var i = 0; i < this.subjectDescriptionList.length; ++i) |
| subjectList.addChild(this.subjectDescriptionList[i].toDer()); |
| |
| root.addChild(subjectList); |
| root.addChild(this.key.toDer()); |
| |
| if (this.extensionList.length > 0) { |
| var extensionList = new DerNode.DerSequence(); |
| for (var i = 0; i < this.extensionList.length; ++i) |
| extensionList.addChild(this.extensionList[i].toDer()); |
| root.addChild(extensionList); |
| } |
| |
| return root; |
| }; |
| |
| /** |
| * Populate the fields by the decoding DER data from the Content. |
| */ |
| Certificate.prototype.decode = function() |
| { |
| var root = DerNode.parse(this.getContent().buf()); |
| |
| // We need to ensure that there are: |
| // validity (notBefore, notAfter) |
| // subject list |
| // public key |
| // (optional) extension list |
| |
| var rootChildren = root.getChildren(); |
| // 1st: validity info |
| var validityChildren = DerNode.getSequence(rootChildren, 0).getChildren(); |
| this.notBefore = validityChildren[0].toVal(); |
| this.notAfter = validityChildren[1].toVal(); |
| |
| // 2nd: subjectList |
| var subjectChildren = DerNode.getSequence(rootChildren, 1).getChildren(); |
| for (var i = 0; i < subjectChildren.length; ++i) { |
| var sd = DerNode.getSequence(subjectChildren, i); |
| var descriptionChildren = sd.getChildren(); |
| var oidStr = descriptionChildren[0].toVal(); |
| var value = descriptionChildren[1].toVal().buf().toString('binary'); |
| |
| this.addSubjectDescription(new CertificateSubjectDescription(oidStr, value)); |
| } |
| |
| // 3rd: public key |
| var publicKeyInfo = rootChildren[2].encode(); |
| this.key = new PublicKey(publicKeyInfo); |
| |
| if (rootChildren.length > 3) { |
| var extensionChildren = DerNode.getSequence(rootChildren, 3).getChildren(); |
| for (var i = 0; i < extensionChildren.length; ++i) { |
| var extInfo = DerNode.getSequence(extensionChildren, i); |
| |
| var children = extInfo.getChildren(); |
| var oidStr = children[0].toVal(); |
| var isCritical = children[1].toVal(); |
| var value = children[2].toVal(); |
| this.addExtension(new CertificateExtension(oidStr, isCritical, value)); |
| } |
| } |
| }; |
| |
| /** |
| * Override to call the base class wireDecode then populate the certificate |
| * fields. |
| * @param {Blob|Buffer} input The buffer with the bytes to decode. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to decode |
| * this object. If omitted, use WireFormat.getDefaultWireFormat(). |
| */ |
| Certificate.prototype.wireDecode = function(input, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| |
| Data.prototype.wireDecode.call(this, input, wireFormat); |
| this.decode(); |
| }; |
| |
| Certificate.prototype.toString = function() |
| { |
| var s = "Certificate name:\n"; |
| s += " " + this.getName().toUri() + "\n"; |
| s += "Validity:\n"; |
| |
| var notBeforeStr = Certificate.toIsoString(Math.round(this.notBefore)); |
| var notAfterStr = Certificate.toIsoString(Math.round(this.notAfter)); |
| |
| s += " NotBefore: " + notBeforeStr + "\n"; |
| s += " NotAfter: " + notAfterStr + "\n"; |
| for (var i = 0; i < this.subjectDescriptionList.length; ++i) { |
| var sd = this.subjectDescriptionList[i]; |
| s += "Subject Description:\n"; |
| s += " " + sd.getOidString() + ": " + sd.getValue() + "\n"; |
| } |
| |
| s += "Public key bits:\n"; |
| var keyDer = this.key.getKeyDer(); |
| var encodedKey = keyDer.buf().toString('base64'); |
| for (var i = 0; i < encodedKey.length; i += 64) |
| s += encodedKey.substring(i, Math.min(i + 64, encodedKey.length)) + "\n"; |
| |
| if (this.extensionList.length > 0) { |
| s += "Extensions:\n"; |
| for (var i = 0; i < this.extensionList.length; ++i) { |
| var ext = this.extensionList[i]; |
| s += " OID: " + ext.getOid() + "\n"; |
| s += " Is critical: " + (ext.getIsCritical() ? 'Y' : 'N') + "\n"; |
| |
| s += " Value: " + ext.getValue().toHex() + "\n" ; |
| } |
| } |
| |
| return s; |
| }; |
| |
| /** |
| * Convert a UNIX timestamp to ISO time representation with the "T" in the middle. |
| * @param {type} msSince1970 Timestamp as milliseconds since Jan 1, 1970. |
| * @return {string} The string representation. |
| */ |
| Certificate.toIsoString = function(msSince1970) |
| { |
| var utcTime = new Date(Math.round(msSince1970)); |
| return utcTime.getUTCFullYear() + |
| Certificate.to2DigitString(utcTime.getUTCMonth() + 1) + |
| Certificate.to2DigitString(utcTime.getUTCDate()) + |
| "T" + |
| Certificate.to2DigitString(utcTime.getUTCHours()) + |
| Certificate.to2DigitString(utcTime.getUTCMinutes()) + |
| Certificate.to2DigitString(utcTime.getUTCSeconds()); |
| }; |
| |
| /** |
| * A private method to zero pad an integer to 2 digits. |
| * @param {number} x The number to pad. Assume it is a non-negative integer. |
| * @return {string} The padded string. |
| */ |
| Certificate.to2DigitString = function(x) |
| { |
| var result = x.toString(); |
| return result.length === 1 ? "0" + result : result; |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * From ndn-cxx security by Yingdi Yu <yingdi@cs.ucla.edu>. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Data = require('../../data.js').Data; /** @ignore */ |
| var Name = require('../../name.js').Name; /** @ignore */ |
| var SecurityException = require('../../security//security-exception.js').SecurityException; /** @ignore */ |
| var Certificate = require('./certificate.js').Certificate; /** @ignore */ |
| var WireFormat = require('../../encoding/wire-format.js').WireFormat; |
| |
| /** |
| * @constructor |
| */ |
| var IdentityCertificate = function IdentityCertificate(data) |
| { |
| // Call the base constructor. |
| if (data != undefined) |
| // This works if data is Data or IdentityCertificate. |
| Certificate.call(this, data); |
| else |
| Certificate.call(this); |
| |
| this.publicKeyName = new Name(); |
| |
| if (data instanceof IdentityCertificate) { |
| // The copy constructor. |
| this.publicKeyName = new Name(data.publicKeyName); |
| } |
| else if (data instanceof Data) { |
| if (!IdentityCertificate.isCorrectName(data.getName())) |
| throw new SecurityException(new Error("Wrong Identity Certificate Name!")); |
| |
| this.setPublicKeyName(); |
| } |
| }; |
| IdentityCertificate.prototype = new Certificate(); |
| IdentityCertificate.prototype.name = "IdentityCertificate"; |
| |
| exports.IdentityCertificate = IdentityCertificate; |
| |
| /** |
| * Override the base class method to check that the name is a valid identity |
| * certificate name. |
| * @param {Name} name The identity certificate name which is copied. |
| * @return {Data} This Data so that you can chain calls to update values. |
| */ |
| IdentityCertificate.prototype.setName = function(name) |
| { |
| if (!IdentityCertificate.isCorrectName(name)) |
| throw new SecurityException(new Error("Wrong Identity Certificate Name!")); |
| |
| // Call the super class method. |
| Certificate.prototype.setName.call(this, name); |
| this.setPublicKeyName(); |
| return this; |
| }; |
| |
| /** |
| * Override to call the base class wireDecode then update the public key name. |
| * @param {Blob|Buffer} input The buffer with the bytes to decode. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to decode |
| * this object. If omitted, use WireFormat.getDefaultWireFormat(). |
| */ |
| IdentityCertificate.prototype.wireDecode = function(input, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| |
| Certificate.prototype.wireDecode.call(this, input, wireFormat); |
| this.setPublicKeyName(); |
| }; |
| |
| IdentityCertificate.prototype.getPublicKeyName = function() |
| { |
| return this.publicKeyName; |
| }; |
| |
| IdentityCertificate.isIdentityCertificate = function(certificate) |
| { |
| return IdentityCertificate.isCorrectName(certificate.getName()); |
| }; |
| |
| /** |
| * Get the public key name from the full certificate name. |
| * @param {Name} certificateName The full certificate name. |
| * @return {Name} The related public key name. |
| */ |
| IdentityCertificate.certificateNameToPublicKeyName = function(certificateName) |
| { |
| var idString = "ID-CERT"; |
| var foundIdString = false; |
| var idCertComponentIndex = certificateName.size() - 1; |
| for (; idCertComponentIndex + 1 > 0; --idCertComponentIndex) { |
| if (certificateName.get(idCertComponentIndex).toEscapedString() == idString) { |
| foundIdString = true; |
| break; |
| } |
| } |
| |
| if (!foundIdString) |
| throw new Error |
| ("Incorrect identity certificate name " + certificateName.toUri()); |
| |
| var tempName = certificateName.getSubName(0, idCertComponentIndex); |
| var keyString = "KEY"; |
| var foundKeyString = false; |
| var keyComponentIndex = 0; |
| for (; keyComponentIndex < tempName.size(); keyComponentIndex++) { |
| if (tempName.get(keyComponentIndex).toEscapedString() == keyString) { |
| foundKeyString = true; |
| break; |
| } |
| } |
| |
| if (!foundKeyString) |
| throw new Error |
| ("Incorrect identity certificate name " + certificateName.toUri()); |
| |
| return tempName |
| .getSubName(0, keyComponentIndex) |
| .append(tempName.getSubName |
| (keyComponentIndex + 1, tempName.size() - keyComponentIndex - 1)); |
| }; |
| |
| IdentityCertificate.isCorrectName = function(name) |
| { |
| var i = name.size() - 1; |
| |
| var idString = "ID-CERT"; |
| for (; i >= 0; i--) { |
| if (name.get(i).toEscapedString() == idString) |
| break; |
| } |
| |
| if (i < 0) |
| return false; |
| |
| var keyIdx = 0; |
| var keyString = "KEY"; |
| for (; keyIdx < name.size(); keyIdx++) { |
| if(name.get(keyIdx).toEscapedString() == keyString) |
| break; |
| } |
| |
| if (keyIdx >= name.size()) |
| return false; |
| |
| return true; |
| }; |
| |
| IdentityCertificate.prototype.setPublicKeyName = function() |
| { |
| this.publicKeyName = IdentityCertificate.certificateNameToPublicKeyName |
| (this.getName()); |
| }; |
| |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * From ndn-cxx security by Yingdi Yu <yingdi@cs.ucla.edu>. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Name = require('../../name.js').Name; /** @ignore */ |
| var SecurityException = require('../security-exception.js').SecurityException; /** @ignore */ |
| var SyncPromise = require('../../util/sync-promise.js').SyncPromise; |
| |
| /** |
| * IdentityStorage is a base class for the storage of identity, public keys and |
| * certificates. Private keys are stored in PrivateKeyStorage. |
| * This is an abstract base class. A subclass must implement the methods. |
| * @constructor |
| */ |
| var IdentityStorage = function IdentityStorage() |
| { |
| }; |
| |
| exports.IdentityStorage = IdentityStorage; |
| |
| /** |
| * Check if the specified identity already exists. |
| * @param {Name} identityName The identity name. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which returns true if the identity |
| * exists. |
| */ |
| IdentityStorage.prototype.doesIdentityExistPromise = function |
| (identityName, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("IdentityStorage.doesIdentityExistPromise is not implemented")); |
| }; |
| |
| /** |
| * Check if the specified identity already exists. |
| * @param {Name} identityName The identity name. |
| * @return {boolean} true if the identity exists, otherwise false. |
| * @throws Error If doesIdentityExistPromise doesn't return a SyncPromise which |
| * is already fulfilled. |
| */ |
| IdentityStorage.prototype.doesIdentityExist = function(identityName) |
| { |
| return SyncPromise.getValue(this.doesIdentityExistPromise(identityName, true)); |
| }; |
| |
| /** |
| * Add a new identity. Do nothing if the identity already exists. |
| * @param {Name} identityName The identity name to be added. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which fulfills when the identity is |
| * added. |
| */ |
| IdentityStorage.prototype.addIdentityPromise = function(identityName, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("IdentityStorage.addIdentityPromise is not implemented")); |
| }; |
| |
| /** |
| * Add a new identity. Do nothing if the identity already exists. |
| * @param {Name} identityName The identity name to be added. |
| * @throws Error If addIdentityPromise doesn't return a SyncPromise which |
| * is already fulfilled. |
| */ |
| IdentityStorage.prototype.addIdentity = function(identityName) |
| { |
| return SyncPromise.getValue(this.addIdentityPromise(identityName, true)); |
| }; |
| |
| /** |
| * Revoke the identity. |
| * @return {boolean} true if the identity was revoked, false if not. |
| */ |
| IdentityStorage.prototype.revokeIdentity = function() |
| { |
| return SyncPromise.reject(new Error |
| ("IdentityStorage.revokeIdentity is not implemented")); |
| }; |
| |
| /** |
| * Generate a name for a new key belonging to the identity. |
| * @param {Name} identityName The identity name. |
| * @param {boolean} useKsk If true, generate a KSK name, otherwise a DSK name. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns the generated key Name. |
| */ |
| IdentityStorage.prototype.getNewKeyNamePromise = function |
| (identityName, useKsk, useSync) |
| { |
| var timestamp = Math.floor(new Date().getTime() / 1000.0); |
| while (timestamp <= IdentityStorage.lastTimestamp) |
| // Make the timestamp unique. |
| timestamp += 1; |
| IdentityStorage.lastTimestamp = timestamp; |
| |
| // Get the number of seconds as a string. |
| var seconds = "" + timestamp; |
| |
| var keyIdStr; |
| if (useKsk) |
| keyIdStr = "ksk-" + seconds; |
| else |
| keyIdStr = "dsk-" + seconds; |
| |
| var keyName = new Name(identityName).append(keyIdStr); |
| |
| return this.doesKeyExistPromise(keyName, useSync) |
| .then(function(exists) { |
| if (exists) |
| throw new SecurityException(new Error("Key name already exists")); |
| |
| return SyncPromise.resolve(keyName); |
| }); |
| }; |
| |
| /** |
| * Generate a name for a new key belonging to the identity. |
| * @param {Name} identityName The identity name. |
| * @param {boolean} useKsk If true, generate a KSK name, otherwise a DSK name. |
| * @return {Name} The generated key name. |
| * @throws Error If getNewKeyNamePromise doesn't return a SyncPromise which |
| * is already fulfilled. |
| */ |
| IdentityStorage.prototype.getNewKeyName = function(identityName, useKsk) |
| { |
| return SyncPromise.getValue |
| (this.getNewKeyNamePromise(identityName, useKsk, true)); |
| }; |
| |
| /** |
| * Check if the specified key already exists. |
| * @param {Name} keyName The name of the key. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which returns true if the key exists. |
| */ |
| IdentityStorage.prototype.doesKeyExistPromise = function(keyName, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("IdentityStorage.doesKeyExistPromise is not implemented")); |
| }; |
| |
| /** |
| * Check if the specified key already exists. |
| * @param {Name} keyName The name of the key. |
| * @return {boolean} true if the key exists, otherwise false. |
| * @throws Error If doesKeyExistPromise doesn't return a SyncPromise which |
| * is already fulfilled. |
| */ |
| IdentityStorage.prototype.doesKeyExist = function(keyName) |
| { |
| return SyncPromise.getValue(this.doesKeyExistPromise(keyName, true)); |
| }; |
| |
| /** |
| * Add a public key to the identity storage. Also call addIdentity to ensure |
| * that the identityName for the key exists. However, if the key already |
| * exists, do nothing. |
| * @param {Name} keyName The name of the public key to be added. |
| * @param {number} keyType Type of the public key to be added from KeyType, such |
| * as KeyType.RSA.. |
| * @param {Blob} publicKeyDer A blob of the public key DER to be added. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which fulfills when complete. |
| */ |
| IdentityStorage.prototype.addKeyPromise = function |
| (keyName, keyType, publicKeyDer, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("IdentityStorage.addKeyPromise is not implemented")); |
| }; |
| |
| /** |
| * Add a public key to the identity storage. Also call addIdentity to ensure |
| * that the identityName for the key exists. |
| * @param {Name} keyName The name of the public key to be added. |
| * @param {number} keyType Type of the public key to be added from KeyType, such |
| * as KeyType.RSA.. |
| * @param {Blob} publicKeyDer A blob of the public key DER to be added. |
| * @throws SecurityException if a key with the keyName already exists. |
| * @throws Error If addKeyPromise doesn't return a SyncPromise which |
| * is already fulfilled. |
| */ |
| IdentityStorage.prototype.addKey = function(keyName, keyType, publicKeyDer) |
| { |
| return SyncPromise.getValue |
| (this.addKeyPromise(keyName, keyType, publicKeyDer, true)); |
| }; |
| |
| /** |
| * Get the public key DER blob from the identity storage. |
| * @param {Name} keyName The name of the requested public key. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which returns the DER Blob, or a |
| * promise rejected with SecurityException if the key doesn't exist. |
| */ |
| IdentityStorage.prototype.getKeyPromise = function(keyName, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("IdentityStorage.getKeyPromise is not implemented")); |
| }; |
| |
| /** |
| * Get the public key DER blob from the identity storage. |
| * @param {Name} keyName The name of the requested public key. |
| * @return {Blob} The DER Blob. |
| * @throws SecurityException if the key doesn't exist. |
| * @throws Error If getKeyPromise doesn't return a SyncPromise which |
| * is already fulfilled. |
| */ |
| IdentityStorage.prototype.getKey = function(keyName) |
| { |
| return SyncPromise.getValue(this.getKeyPromise(keyName, true)); |
| }; |
| |
| /** |
| * Activate a key. If a key is marked as inactive, its private part will not be |
| * used in packet signing. |
| * @param {Name} keyName name of the key |
| */ |
| IdentityStorage.prototype.activateKey = function(keyName) |
| { |
| throw new Error("IdentityStorage.activateKey is not implemented"); |
| }; |
| |
| /** |
| * Deactivate a key. If a key is marked as inactive, its private part will not |
| * be used in packet signing. |
| * @param {Name} keyName name of the key |
| */ |
| IdentityStorage.prototype.deactivateKey = function(keyName) |
| { |
| throw new Error("IdentityStorage.deactivateKey is not implemented"); |
| }; |
| |
| /** |
| * Check if the specified certificate already exists. |
| * @param {Name} certificateName The name of the certificate. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which returns true if the certificate |
| * exists. |
| */ |
| IdentityStorage.prototype.doesCertificateExistPromise = function |
| (certificateName, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("IdentityStorage.doesCertificateExistPromise is not implemented")); |
| }; |
| |
| /** |
| * Check if the specified certificate already exists. |
| * @param {Name} certificateName The name of the certificate. |
| * @return {boolean} true if the certificate exists, otherwise false. |
| * @throws Error If doesCertificateExistPromise doesn't return a SyncPromise |
| * which is already fulfilled. |
| */ |
| IdentityStorage.prototype.doesCertificateExist = function(certificateName) |
| { |
| return SyncPromise.getValue |
| (this.doesCertificateExistPromise(certificateName, true)); |
| }; |
| |
| /** |
| * Add a certificate to the identity storage. Also call addKey to ensure that |
| * the certificate key exists. If the certificate is already installed, don't |
| * replace it. |
| * @param {IdentityCertificate} certificate The certificate to be added. This |
| * makes a copy of the certificate. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which fulfills when finished. |
| */ |
| IdentityStorage.prototype.addCertificatePromise = function(certificate, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("IdentityStorage.addCertificatePromise is not implemented")); |
| }; |
| |
| /** |
| * Add a certificate to the identity storage. |
| * @param {IdentityCertificate} certificate The certificate to be added. This |
| * makes a copy of the certificate. |
| * @throws SecurityException if the certificate is already installed. |
| * @throws Error If addCertificatePromise doesn't return a SyncPromise which |
| * is already fulfilled. |
| */ |
| IdentityStorage.prototype.addCertificate = function(certificate) |
| { |
| return SyncPromise.getValue(this.addCertificatePromise(certificate, true)); |
| }; |
| |
| /** |
| * Get a certificate from the identity storage. |
| * @param {Name} certificateName The name of the requested certificate. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which returns the requested |
| * IdentityCertificate, or a promise rejected with SecurityException if the |
| * certificate doesn't exist. |
| */ |
| IdentityStorage.prototype.getCertificatePromise = function |
| (certificateName, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("IdentityStorage.getCertificatePromise is not implemented")); |
| }; |
| |
| /** |
| * Get a certificate from the identity storage. |
| * @param {Name} certificateName The name of the requested certificate. |
| * @return {IdentityCertificate} The requested certificate. |
| * @throws SecurityException if the certificate doesn't exist. |
| * @throws Error If getCertificatePromise doesn't return a SyncPromise which |
| * is already fulfilled. |
| */ |
| IdentityStorage.prototype.getCertificate = function(certificateName) |
| { |
| return SyncPromise.getValue(this.getValuePromise(certificateName, true)); |
| }; |
| |
| /** |
| * Get the TPM locator associated with this storage. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise|SyncPromise} A promise which returns the TPM locator, or a |
| * promise rejected with SecurityException if the TPM locator doesn't exist. |
| */ |
| IdentityStorage.prototype.getTpmLocatorPromise = function(useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("IdentityStorage.getTpmLocatorPromise is not implemented")); |
| }; |
| |
| /** |
| * Get the TPM locator associated with this storage. |
| * @return {string} The TPM locator. |
| * @throws SecurityException if the TPM locator doesn't exist. |
| * @throws Error If getTpmLocatorPromise doesn't return a SyncPromise which is |
| * already fulfilled. |
| */ |
| IdentityStorage.prototype.getTpmLocator = function() |
| { |
| return SyncPromise.getValue(this.getTpmLocatorPromise(true)); |
| }; |
| |
| /***************************************** |
| * Get/Set Default * |
| *****************************************/ |
| |
| /** |
| * Get the default identity. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which returns the Name of default |
| * identity, or a promise rejected with SecurityException if the default |
| * identity is not set. |
| */ |
| IdentityStorage.prototype.getDefaultIdentityPromise = function(useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("IdentityStorage.getDefaultIdentityPromise is not implemented")); |
| }; |
| |
| /** |
| * Get the default identity. |
| * @return {Name} The name of default identity. |
| * @throws SecurityException if the default identity is not set. |
| * @throws Error If getDefaultIdentityPromise doesn't return a SyncPromise |
| * which is already fulfilled. |
| */ |
| IdentityStorage.prototype.getDefaultIdentity = function() |
| { |
| return SyncPromise.getValue |
| (this.getDefaultIdentityPromise(true)); |
| }; |
| |
| /** |
| * Get the default key name for the specified identity. |
| * @param {Name} identityName The identity name. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which returns the default key Name, |
| * or a promise rejected with SecurityException if the default key name for the |
| * identity is not set. |
| */ |
| IdentityStorage.prototype.getDefaultKeyNameForIdentityPromise = function |
| (identityName, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("IdentityStorage.getDefaultKeyNameForIdentityPromise is not implemented")); |
| }; |
| |
| /** |
| * Get the default key name for the specified identity. |
| * @param {Name} identityName The identity name. |
| * @return {Name} The default key name. |
| * @throws SecurityException if the default key name for the identity is not set. |
| * @throws Error If getDefaultKeyNameForIdentityPromise doesn't return a |
| * SyncPromise which is already fulfilled. |
| */ |
| IdentityStorage.prototype.getDefaultKeyNameForIdentity = function(identityName) |
| { |
| return SyncPromise.getValue |
| (this.getDefaultKeyNameForIdentityPromise(identityName, true)); |
| }; |
| |
| /** |
| * Get the default certificate name for the specified identity. |
| * @param {Name} identityName The identity name. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which returns the default certificate |
| * Name, or a promise rejected with SecurityException if the default key name |
| * for the identity is not set or the default certificate name for the key name |
| * is not set. |
| */ |
| IdentityStorage.prototype.getDefaultCertificateNameForIdentityPromise = function |
| (identityName, useSync) |
| { |
| var thisStorage = this; |
| return this.getDefaultKeyNameForIdentityPromise(identityName) |
| .then(function(keyName) { |
| return thisStorage.getDefaultCertificateNameForKeyPromise(keyName); |
| }); |
| }; |
| |
| /** |
| * Get the default certificate name for the specified identity. |
| * @param {Name} identityName The identity name. |
| * @return {Name} The default certificate name. |
| * @throws SecurityException if the default key name for the identity is not |
| * set or the default certificate name for the key name is not set. |
| * @throws Error If getDefaultCertificateNameForIdentityPromise doesn't return |
| * a SyncPromise which is already fulfilled. |
| */ |
| IdentityStorage.prototype.getDefaultCertificateNameForIdentity = function |
| (identityName) |
| { |
| return SyncPromise.getValue |
| (this.getDefaultCertificateNameForIdentityPromise(identityName, true)); |
| }; |
| |
| /** |
| * Get the default certificate name for the specified key. |
| * @param {Name} keyName The key name. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which returns the default certificate |
| * Name, or a promise rejected with SecurityException if the default certificate |
| * name for the key name is not set. |
| */ |
| IdentityStorage.prototype.getDefaultCertificateNameForKeyPromise = function |
| (keyName, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("IdentityStorage.getDefaultCertificateNameForKeyPromise is not implemented")); |
| }; |
| |
| /** |
| * Get the default certificate name for the specified key. |
| * @param {Name} keyName The key name. |
| * @return {Name} The default certificate name. |
| * @throws SecurityException if the default certificate name for the key name |
| * is not set. |
| * @throws Error If getDefaultCertificateNameForKeyPromise doesn't return a |
| * SyncPromise which is already fulfilled. |
| */ |
| IdentityStorage.prototype.getDefaultCertificateNameForKey = function(keyName) |
| { |
| return SyncPromise.getValue |
| (this.getDefaultCertificateNameForKeyPromise(keyName, true)); |
| }; |
| |
| /** |
| * Append all the identity names to the nameList. |
| * @param {Array<Name>} nameList Append result names to nameList. |
| * @param {boolean} isDefault If true, add only the default identity name. If |
| * false, add only the non-default identity names. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which fulfills when the names are added to |
| * nameList. |
| */ |
| IdentityStorage.prototype.getAllIdentitiesPromise = function |
| (nameList, isDefault, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("IdentityStorage.getAllIdentitiesPromise is not implemented")); |
| }; |
| |
| /** |
| * Append all the key names of a particular identity to the nameList. |
| * @param {Name} identityName The identity name to search for. |
| * @param {Array<Name>} nameList Append result names to nameList. |
| * @param {boolean} isDefault If true, add only the default key name. If false, |
| * add only the non-default key names. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which fulfills when the names are |
| * added to nameList. |
| */ |
| IdentityStorage.prototype.getAllKeyNamesOfIdentityPromise = function |
| (identityName, nameList, isDefault, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("IdentityStorage.getAllKeyNamesOfIdentityPromise is not implemented")); |
| }; |
| |
| /** |
| * Append all the certificate names of a particular key name to the nameList. |
| * @param {Name} keyName The key name to search for. |
| * @param {Array<Name>} nameList Append result names to nameList. |
| * @param {boolean} isDefault If true, add only the default certificate name. |
| * If false, add only the non-default certificate names. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which fulfills when the names are added to |
| * nameList. |
| */ |
| IdentityStorage.prototype.getAllCertificateNamesOfKeyPromise = function |
| (keyName, nameList, isDefault, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("IdentityStorage.getAllCertificateNamesOfKeyPromise is not implemented")); |
| }; |
| |
| /** |
| * Append all the key names of a particular identity to the nameList. |
| * @param {Name} identityName The identity name to search for. |
| * @param {Array<Name>} nameList Append result names to nameList. |
| * @param {boolean} isDefault If true, add only the default key name. If false, |
| * add only the non-default key names. |
| * @throws Error If getAllKeyNamesOfIdentityPromise doesn't return a |
| * SyncPromise which is already fulfilled. |
| */ |
| IdentityStorage.prototype.getAllKeyNamesOfIdentity = function |
| (identityName, nameList, isDefault) |
| { |
| return SyncPromise.getValue |
| (this.getAllKeyNamesOfIdentityPromise(identityName, nameList, isDefault, true)); |
| }; |
| |
| /** |
| * Set the default identity. If the identityName does not exist, then clear the |
| * default identity so that getDefaultIdentity() throws an exception. |
| * @param {Name} identityName The default identity name. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which fulfills when the default |
| * identity is set. |
| */ |
| IdentityStorage.prototype.setDefaultIdentityPromise = function |
| (identityName, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("IdentityStorage.setDefaultIdentityPromise is not implemented")); |
| }; |
| |
| /** |
| * Set the default identity. If the identityName does not exist, then clear the |
| * default identity so that getDefaultIdentity() throws an exception. |
| * @param {Name} identityName The default identity name. |
| * @throws Error If setDefaultIdentityPromise doesn't return a SyncPromise which |
| * is already fulfilled. |
| */ |
| IdentityStorage.prototype.setDefaultIdentity = function(identityName) |
| { |
| return SyncPromise.getValue |
| (this.setDefaultIdentityPromise(identityName, true)); |
| }; |
| |
| /** |
| * Set a key as the default key of an identity. The identity name is inferred |
| * from keyName. |
| * @param {Name} keyName The name of the key. |
| * @param {Name} identityNameCheck (optional) The identity name to check that the |
| * keyName contains the same identity name. If an empty name, it is ignored. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which fulfills when the default key |
| * name is set. |
| */ |
| IdentityStorage.prototype.setDefaultKeyNameForIdentityPromise = function |
| (keyName, identityNameCheck, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("IdentityStorage.setDefaultKeyNameForIdentityPromise is not implemented")); |
| }; |
| |
| /** |
| * Set a key as the default key of an identity. The identity name is inferred |
| * from keyName. |
| * @param {Name} keyName The name of the key. |
| * @param {Name} identityNameCheck (optional) The identity name to check that the |
| * keyName contains the same identity name. If an empty name, it is ignored. |
| * @throws Error If setDefaultKeyNameForIdentityPromise doesn't return a |
| * SyncPromise which is already fulfilled. |
| */ |
| IdentityStorage.prototype.setDefaultKeyNameForIdentity = function |
| (keyName, identityNameCheck) |
| { |
| return SyncPromise.getValue |
| (this.setDefaultKeyNameForIdentityPromise(keyName, identityNameCheck, true)); |
| }; |
| |
| /** |
| * Set the default key name for the specified identity. |
| * @param {Name} keyName The key name. |
| * @param {Name} certificateName The certificate name. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which fulfills when the default |
| * certificate name is set. |
| */ |
| IdentityStorage.prototype.setDefaultCertificateNameForKeyPromise = function |
| (keyName, certificateName, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("IdentityStorage.setDefaultCertificateNameForKeyPromise is not implemented")); |
| }; |
| |
| /** |
| * Set the default key name for the specified identity. |
| * @param {Name} keyName The key name. |
| * @param {Name} certificateName The certificate name. |
| * @throws Error If setDefaultCertificateNameForKeyPromise doesn't return a |
| * SyncPromise which is already fulfilled. |
| */ |
| IdentityStorage.prototype.setDefaultCertificateNameForKey = function |
| (keyName, certificateName) |
| { |
| return SyncPromise.getValue |
| (this.setDefaultCertificateNameForKeyPromise(keyName, certificateName, true)); |
| }; |
| |
| /** |
| * Get the certificate of the default identity. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which returns the requested |
| * IdentityCertificate or null if not found. |
| */ |
| IdentityStorage.prototype.getDefaultCertificatePromise = function(useSync) |
| { |
| var thisStorage = this; |
| return this.getDefaultIdentityPromise(useSync) |
| .then(function(identityName) { |
| return thisStorage.getDefaultCertificateNameForIdentityPromise |
| (identityName, useSync); |
| }, function(ex) { |
| // The default is not defined. |
| return SyncPromise.resolve(null); |
| }) |
| .then(function(certName) { |
| if (certName == null) |
| return SyncPromise.resolve(null); |
| |
| return thisStorage.getCertificatePromise(certName, useSync); |
| }); |
| }; |
| |
| /** |
| * Get the certificate of the default identity. |
| * @return {IdentityCertificate} The requested certificate. If not found, |
| * return null. |
| * @throws Error If getDefaultCertificatePromise doesn't return a SyncPromise |
| * which is already fulfilled. |
| */ |
| IdentityStorage.prototype.getDefaultCertificate = function() |
| { |
| return SyncPromise.getValue |
| (this.getDefaultCertificatePromise(true)); |
| }; |
| |
| /***************************************** |
| * Delete Methods * |
| *****************************************/ |
| |
| /** |
| * Delete a certificate. |
| * @param {Name} certificateName The certificate name. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which fulfills when the certificate |
| * info is deleted. |
| */ |
| IdentityStorage.prototype.deleteCertificateInfoPromise = function |
| (certificateName, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("IdentityStorage.deleteCertificateInfoPromise is not implemented")); |
| }; |
| |
| /** |
| * Delete a certificate. |
| * @param {Name} certificateName The certificate name. |
| * @throws Error If deleteCertificateInfoPromise doesn't return a SyncPromise |
| * which is already fulfilled. |
| */ |
| IdentityStorage.prototype.deleteCertificateInfo = function(certificateName) |
| { |
| return SyncPromise.getValue |
| (this.deleteCertificateInfoPromise(certificateName, true)); |
| }; |
| |
| /** |
| * Delete a public key and related certificates. |
| * @param {Name} keyName The key name. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which fulfills when the public key |
| * info is deleted. |
| */ |
| IdentityStorage.prototype.deletePublicKeyInfoPromise = function(keyName, useSync) |
| { |
| return SyncPromise.reject |
| (new Error("IdentityStorage.deletePublicKeyInfoPromise is not implemented")); |
| }; |
| |
| /** |
| * Delete a public key and related certificates. |
| * @param {Name} keyName The key name. |
| * @throws Error If deletePublicKeyInfoPromise doesn't return a SyncPromise |
| * which is already fulfilled. |
| */ |
| IdentityStorage.prototype.deletePublicKeyInfo = function(keyName) |
| { |
| return SyncPromise.getValue |
| (this.deletePublicKeyInfoPromise(keyName, true)); |
| }; |
| |
| /** |
| * Delete an identity and related public keys and certificates. |
| * @param {Name} identityName The identity name. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which fulfills when the identity info |
| * is deleted. |
| */ |
| IdentityStorage.prototype.deleteIdentityInfoPromise = function |
| (identityName, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("IdentityStorage.deleteIdentityInfoPromise is not implemented")); |
| }; |
| |
| /** |
| * Delete an identity and related public keys and certificates. |
| * @param {Name} identityName The identity name. |
| * @throws Error If deleteIdentityInfoPromise doesn't return a SyncPromise |
| * which is already fulfilled. |
| */ |
| IdentityStorage.prototype.deleteIdentityInfo = function(identityName) |
| { |
| return SyncPromise.getValue |
| (this.deleteIdentityInfoPromise(identityName, true)); |
| }; |
| |
| // Track the lastTimestamp so that each timestamp is unique. |
| IdentityStorage.lastTimestamp = Math.floor(new Date().getTime() / 1000.0); |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| // Don't require modules since this is meant for the browser, not Node.js. |
| |
| /** |
| * IndexedDbIdentityStorage extends IdentityStorage and implements its methods |
| * to store identity, public key and certificate objects using the browser's |
| * IndexedDB service. |
| * @constructor |
| */ |
| var IndexedDbIdentityStorage = function IndexedDbIdentityStorage() |
| { |
| IdentityStorage.call(this); |
| |
| this.database = new Dexie("ndnsec-public-info"); |
| // The database schema imitates MemoryIdentityStorage. |
| this.database.version(1).stores({ |
| // A table for global values. It currently only has the defaultIdentityUri. |
| // "key" is the key like "defaultIdentityUri" // string |
| // "value" is the value. For "defaultIdentityUri" the value is the |
| // default identity name URI, or absent if not defined. // string |
| globals: "key", |
| |
| // "identityNameUri" is the identity name URI // string |
| // "defaultKeyUri" is the default key name URI or null // string |
| identity: "identityNameUri", |
| |
| // "keyNameUri" is the key name URI // string |
| // "keyType" is the type of the public key // number from KeyType |
| // "keyDer" is the public key DER // Uint8Array |
| // "defaultCertificateUri" is the default cert name URI or null // string |
| publicKey: "keyNameUri", |
| |
| // "certificateNameUri" is the certificate name URI // string |
| // "encoding" is the certificate wire encoding // Uint8Array |
| certificate: "certificateNameUri" |
| }); |
| this.database.open(); |
| }; |
| |
| IndexedDbIdentityStorage.prototype = new IdentityStorage(); |
| IndexedDbIdentityStorage.prototype.name = "IndexedDbIdentityStorage"; |
| |
| /** |
| * Check if the specified identity already exists. |
| * @param {Name} identityName The identity name. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which returns true if the identity exists. |
| */ |
| IndexedDbIdentityStorage.prototype.doesIdentityExistPromise = function |
| (identityName, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage.doesIdentityExistPromise is only supported for async"))); |
| |
| return this.database.identity.where("identityNameUri").equals |
| (identityName.toUri()) |
| .count() |
| .then(function(count) { |
| return Promise.resolve(count > 0); |
| }); |
| }; |
| |
| /** |
| * Add a new identity. Do nothing if the identity already exists. |
| * @param {Name} identityName The identity name to be added. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which fulfills when the identity is added. |
| */ |
| IndexedDbIdentityStorage.prototype.addIdentityPromise = function |
| (identityName, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage.addIdentityPromise is only supported for async"))); |
| |
| var thisStorage = this; |
| return this.doesIdentityExistPromise(identityName) |
| .then(function(exists) { |
| if (exists) |
| // Do nothing. |
| return Promise.resolve(); |
| |
| return thisStorage.database.identity.put |
| ({ identityNameUri: identityName.toUri(), defaultKeyUri: null }); |
| }); |
| }; |
| |
| /** |
| * Check if the specified key already exists. |
| * @param {Name} keyName The name of the key. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which returns true if the key exists. |
| */ |
| IndexedDbIdentityStorage.prototype.doesKeyExistPromise = function |
| (keyName, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage.doesKeyExistPromise is only supported for async"))); |
| |
| return this.database.publicKey.where("keyNameUri").equals(keyName.toUri()) |
| .count() |
| .then(function(count) { |
| return Promise.resolve(count > 0); |
| }); |
| }; |
| |
| /** |
| * Add a public key to the identity storage. Also call addIdentity to ensure |
| * that the identityName for the key exists. However, if the key already |
| * exists, do nothing. |
| * @param {Name} keyName The name of the public key to be added. |
| * @param {number} keyType Type of the public key to be added from KeyType, such |
| * as KeyType.RSA.. |
| * @param {Blob} publicKeyDer A blob of the public key DER to be added. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which fulfills when complete. |
| */ |
| IndexedDbIdentityStorage.prototype.addKeyPromise = function |
| (keyName, keyType, publicKeyDer, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage.addKeyPromise is only supported for async"))); |
| |
| if (keyName.size() === 0) |
| return Promise.resolve(); |
| |
| var thisStorage = this; |
| return this.doesKeyExistPromise(keyName) |
| .then(function(exists) { |
| if (exists) |
| return Promise.resolve(); |
| |
| var identityName = keyName.getPrefix(-1); |
| return thisStorage.addIdentityPromise(identityName) |
| .then(function() { |
| return thisStorage.database.publicKey.put |
| ({ keyNameUri: keyName.toUri(), keyType: keyType, |
| keyDer: new Blob(publicKeyDer, true).buf(), |
| defaultCertificate: null }); |
| }); |
| }); |
| }; |
| |
| /** |
| * Get the public key DER blob from the identity storage. |
| * @param {Name} keyName The name of the requested public key. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which returns the DER Blob, or a promise rejected |
| * with SecurityException if the key doesn't exist. |
| */ |
| IndexedDbIdentityStorage.prototype.getKeyPromise = function(keyName, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage.getKeyPromise is only supported for async"))); |
| |
| if (keyName.size() === 0) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage::getKeyPromise: Empty keyName"))); |
| |
| return this.database.publicKey.get(keyName.toUri()) |
| .then(function(publicKeyEntry) { |
| if (publicKeyEntry) |
| return Promise.resolve(new Blob(publicKeyEntry.keyDer)); |
| else |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage::getKeyPromise: The key does not exist"))); |
| }); |
| }; |
| |
| /** |
| * Check if the specified certificate already exists. |
| * @param {Name} certificateName The name of the certificate. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which returns true if the certificate exists. |
| */ |
| IndexedDbIdentityStorage.prototype.doesCertificateExistPromise = function |
| (certificateName, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage.doesCertificateExistPromise is only supported for async"))); |
| |
| return this.database.certificate.where("certificateNameUri").equals |
| (certificateName.toUri()) |
| .count() |
| .then(function(count) { |
| return Promise.resolve(count > 0); |
| }); |
| }; |
| |
| /** |
| * Add a certificate to the identity storage. Also call addKey to ensure that |
| * the certificate key exists. If the certificate is already installed, don't |
| * replace it. |
| * @param {IdentityCertificate} certificate The certificate to be added. This |
| * makes a copy of the certificate. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which fulfills when finished. |
| */ |
| IndexedDbIdentityStorage.prototype.addCertificatePromise = function |
| (certificate, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage.addCertificatePromise is only supported for async"))); |
| |
| var certificateName = certificate.getName(); |
| var keyName = certificate.getPublicKeyName(); |
| |
| var thisStorage = this; |
| return this.addKeyPromise |
| (keyName, certificate.getPublicKeyInfo().getKeyType(), |
| certificate.getPublicKeyInfo().getKeyDer(), useSync) |
| .then(function() { |
| return thisStorage.doesCertificateExistPromise(certificateName); |
| }) |
| .then(function(exists) { |
| if (exists) |
| return Promise.resolve(); |
| |
| // Insert the certificate. |
| // wireEncode returns the cached encoding if available. |
| return thisStorage.database.certificate.put |
| ({ certificateNameUri: certificateName.toUri(), |
| encoding: certificate.wireEncode().buf() }); |
| }); |
| }; |
| |
| /** |
| * Get a certificate from the identity storage. |
| * @param {Name} certificateName The name of the requested certificate. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which returns the requested |
| * IdentityCertificate, or a promise rejected with SecurityException if the |
| * certificate doesn't exist. |
| */ |
| IndexedDbIdentityStorage.prototype.getCertificatePromise = function |
| (certificateName, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage.getCertificatePromise is only supported for async"))); |
| |
| return this.database.certificate.get(certificateName.toUri()) |
| .then(function(certificateEntry) { |
| if (certificateEntry) { |
| var certificate = new IdentityCertificate(); |
| try { |
| certificate.wireDecode(certificateEntry.encoding); |
| } catch (ex) { |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage::getCertificatePromise: The certificate cannot be decoded"))); |
| } |
| return Promise.resolve(certificate); |
| } |
| else |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage::getCertificatePromise: The certificate does not exist"))); |
| }); |
| }; |
| |
| /***************************************** |
| * Get/Set Default * |
| *****************************************/ |
| |
| /** |
| * Get the default identity. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which returns the Name of default identity, |
| * or a promise rejected with SecurityException if the default identity is not |
| * set. |
| */ |
| IndexedDbIdentityStorage.prototype.getDefaultIdentityPromise = function(useSync) |
| { |
| return this.database.globals.get("defaultIdentityUri") |
| .then(function(defaultIdentityEntry) { |
| if (defaultIdentityEntry) |
| return Promise.resolve(new Name(defaultIdentityEntry.value)); |
| else |
| throw new SecurityException(new Error |
| ("IndexedDbIdentityStorage.getDefaultIdentity: The default identity is not defined")); |
| }); |
| }; |
| |
| /** |
| * Get the default key name for the specified identity. |
| * @param {Name} identityName The identity name. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which returns the default key Name, or a |
| * promise rejected with SecurityException if the default key name for the |
| * identity is not set. |
| */ |
| IndexedDbIdentityStorage.prototype.getDefaultKeyNameForIdentityPromise = function |
| (identityName, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage.getDefaultKeyNameForIdentityPromise is only supported for async"))); |
| |
| return this.database.identity.get(identityName.toUri()) |
| .then(function(identityEntry) { |
| if (identityEntry) { |
| if (identityEntry.defaultKeyUri != null) |
| return Promise.resolve(new Name(identityEntry.defaultKeyUri)); |
| else |
| throw new SecurityException(new Error("No default key set.")); |
| } |
| else |
| throw new SecurityException(new Error("Identity not found.")); |
| }); |
| }; |
| |
| /** |
| * Get the default certificate name for the specified key. |
| * @param {Name} keyName The key name. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which returns the default certificate Name, |
| * or a promise rejected with SecurityException if the default certificate name |
| * for the key name is not set. |
| */ |
| IndexedDbIdentityStorage.prototype.getDefaultCertificateNameForKeyPromise = function |
| (keyName, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage.getDefaultCertificateNameForKeyPromise is only supported for async"))); |
| |
| return this.database.publicKey.get(keyName.toUri()) |
| .then(function(publicKeyEntry) { |
| if (publicKeyEntry) { |
| if (publicKeyEntry.defaultCertificateUri != null) |
| return Promise.resolve(new Name(publicKeyEntry.defaultCertificateUri)); |
| else |
| throw new SecurityException(new Error("No default certificate set.")); |
| } |
| else |
| throw new SecurityException(new Error("Key not found.")); |
| }); |
| }; |
| |
| /** |
| * Append all the identity names to the nameList. |
| * @param {Array<Name>} nameList Append result names to nameList. |
| * @param {boolean} isDefault If true, add only the default identity name. If |
| * false, add only the non-default identity names. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which fulfills when the names are added to |
| * nameList. |
| */ |
| IndexedDbIdentityStorage.prototype.getAllIdentitiesPromise = function |
| (nameList, isDefault, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage.getAllIdentitiesPromise is only supported for async"))); |
| |
| var defaultIdentityName = null; |
| var thisStorage = this; |
| return this.getDefaultIdentityPromise() |
| .then(function(localDefaultIdentityName) { |
| defaultIdentityName = localDefaultIdentityName; |
| return SyncPromise.resolve(); |
| }, function(err) { |
| // The default identity name was not found. |
| return SyncPromise.resolve(); |
| }) |
| .then(function() { |
| return thisStorage.database.identity.each(function(identityEntry) { |
| var identityName = new Name(identityEntry.identityNameUri); |
| var identityNameIsDefault = |
| (defaultIdentityName !== null && identityName.equals(defaultIdentityName)); |
| if (isDefault && identityNameIsDefault) |
| nameList.push(identityName); |
| else if (!isDefault && !identityNameIsDefault) |
| nameList.push(identityName); |
| }); |
| }); |
| }; |
| |
| /** |
| * Append all the key names of a particular identity to the nameList. |
| * @param {Name} identityName The identity name to search for. |
| * @param {Array<Name>} nameList Append result names to nameList. |
| * @param {boolean} isDefault If true, add only the default key name. If false, |
| * add only the non-default key names. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which fulfills when the names are added to |
| * nameList. |
| */ |
| IndexedDbIdentityStorage.prototype.getAllKeyNamesOfIdentityPromise = function |
| (identityName, nameList, isDefault, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage.getAllKeyNamesOfIdentityPromise is only supported for async"))); |
| |
| var defaultKeyName = null; |
| var thisStorage = this; |
| return this.getDefaultKeyNameForIdentityPromise(identityName) |
| .then(function(localDefaultKeyName) { |
| defaultKeyName = localDefaultKeyName; |
| return SyncPromise.resolve(); |
| }, function(err) { |
| // The default key name was not found. |
| return SyncPromise.resolve(); |
| }) |
| .then(function() { |
| // Iterate through each publicKey a to find ones that match identityName. |
| // This is a little inefficient, but we don't expect the in-browser |
| // database to be very big, we don't expect to use this function often (for |
| // deleting an identity), and this is simpler than complicating the database |
| // schema to store the identityName with each publicKey. |
| return thisStorage.database.publicKey.each(function(publicKeyEntry) { |
| var keyName = new Name(publicKeyEntry.keyNameUri); |
| var keyIdentityName = keyName.getPrefix(-1); |
| |
| if (keyIdentityName.equals(identityName)) { |
| var keyNameIsDefault = |
| (defaultKeyName !== null && keyName.equals(defaultKeyName)); |
| if (isDefault && keyNameIsDefault) |
| nameList.push(keyName); |
| else if (!isDefault && !keyNameIsDefault) |
| nameList.push(keyName); |
| } |
| }); |
| }); |
| }; |
| |
| /** |
| * Append all the certificate names of a particular key name to the nameList. |
| * @param {Name} keyName The key name to search for. |
| * @param {Array<Name>} nameList Append result names to nameList. |
| * @param {boolean} isDefault If true, add only the default certificate name. |
| * If false, add only the non-default certificate names. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which fulfills when the names are added to |
| * nameList. |
| */ |
| IndexedDbIdentityStorage.prototype.getAllCertificateNamesOfKeyPromise = function |
| (keyName, nameList, isDefault, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage.getAllCertificateNamesOfKeyPromise is only supported for async"))); |
| |
| var defaultCertificateName = null; |
| var thisStorage = this; |
| return this.getDefaultCertificateNameForKeyPromise(keyName) |
| .then(function(localDefaultCertificateName) { |
| defaultCertificateName = localDefaultCertificateName; |
| return SyncPromise.resolve(); |
| }, function(err) { |
| // The default certificate name was not found. |
| return SyncPromise.resolve(); |
| }) |
| .then(function() { |
| // Iterate through each certificate record a to find ones that match keyName. |
| // This is a little inefficient, but we don't expect the in-browser |
| // database to be very big, we don't expect to use this function often (for |
| // deleting an identity), and this is simpler than complicating the database |
| // schema to store the keyName with each certificate record. |
| return thisStorage.database.certificate.each(function(certificateEntry) { |
| var certificateName = new Name(certificateEntry.certificateNameUri); |
| var certificateKeyName = IdentityCertificate.certificateNameToPublicKeyName |
| (certificateName); |
| |
| if (certificateKeyName.equals(keyName)) { |
| var certificateNameIsDefault = |
| (defaultCertificateName !== null && |
| certificateName.equals(defaultCertificateName)); |
| if (isDefault && certificateNameIsDefault) |
| nameList.push(certificateName); |
| else if (!isDefault && !certificateNameIsDefault) |
| nameList.push(certificateName); |
| } |
| }); |
| }); |
| }; |
| |
| /** |
| * Set the default identity. If the identityName does not exist, then clear the |
| * default identity so that getDefaultIdentity() throws an exception. |
| * @param {Name} identityName The default identity name. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which fulfills when the default identity is set. |
| */ |
| IndexedDbIdentityStorage.prototype.setDefaultIdentityPromise = function |
| (identityName, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage.setDefaultIdentityPromise is only supported for async"))); |
| |
| var thisStorage = this; |
| return this.doesIdentityExistPromise(identityName) |
| .then(function(exists) { |
| if (exists) |
| return thisStorage.database.globals.put |
| ({ key: "defaultIdentityUri", value: identityName.toUri() }); |
| else |
| // The identity doesn't exist, so clear the default. |
| return thisStorage.database.globals.delete("defaultIdentityUri"); |
| }); |
| }; |
| |
| /** |
| * Set a key as the default key of an identity. The identity name is inferred |
| * from keyName. |
| * @param {Name} keyName The name of the key. |
| * @param {Name} identityNameCheck (optional) The identity name to check that the |
| * keyName contains the same identity name. If an empty name, it is ignored. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which fulfills when the default key name is |
| * set. |
| */ |
| IndexedDbIdentityStorage.prototype.setDefaultKeyNameForIdentityPromise = function |
| (keyName, identityNameCheck, useSync) |
| { |
| useSync = (typeof identityNameCheck === "boolean") ? identityNameCheck : useSync; |
| identityNameCheck = (identityNameCheck instanceof Name) ? identityNameCheck : null; |
| |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage.setDefaultKeyNameForIdentityPromise is only supported for async"))); |
| |
| var identityName = keyName.getPrefix(-1); |
| |
| if (identityNameCheck != null && identityNameCheck.size() > 0 && |
| !identityNameCheck.equals(identityName)) |
| return Promise.reject(new SecurityException(new Error |
| ("The specified identity name does not match the key name"))); |
| |
| // update does nothing if the identityName doesn't exist. |
| return this.database.identity.update |
| (identityName.toUri(), { defaultKeyUri: keyName.toUri() }); |
| }; |
| |
| /** |
| * Set the default key name for the specified identity. |
| * @param {Name} keyName The key name. |
| * @param {Name} certificateName The certificate name. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which fulfills when the default certificate |
| * name is set. |
| */ |
| IndexedDbIdentityStorage.prototype.setDefaultCertificateNameForKeyPromise = function |
| (keyName, certificateName, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage.setDefaultCertificateNameForKeyPromise is only supported for async"))); |
| |
| // update does nothing if the keyName doesn't exist. |
| return this.database.publicKey.update |
| (keyName.toUri(), { defaultCertificateUri: certificateName.toUri() }); |
| }; |
| |
| /***************************************** |
| * Delete Methods * |
| *****************************************/ |
| |
| /** |
| * Delete a certificate. |
| * @param {Name} certificateName The certificate name. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which fulfills when the certificate info is |
| * deleted. |
| */ |
| IndexedDbIdentityStorage.prototype.deleteCertificateInfoPromise = function |
| (certificateName, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage.deleteCertificateInfoPromise is only supported for async"))); |
| |
| if (certificateName.size() === 0) |
| return Promise.resolve(); |
| |
| return this.database.certificate.delete(certificateName.toUri()); |
| }; |
| |
| /** |
| * Delete a public key and related certificates. |
| * @param {Name} keyName The key name. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which fulfills when the public key info is |
| * deleted. |
| */ |
| IndexedDbIdentityStorage.prototype.deletePublicKeyInfoPromise = function |
| (keyName, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage.deletePublicKeyInfoPromise is only supported for async"))); |
| |
| if (keyName.size() === 0) |
| return Promise.resolve(); |
| |
| var thisStorage = this; |
| return this.database.publicKey.delete(keyName.toUri()) |
| .then(function() { |
| // Iterate through each certificate to find ones that match keyName. This is |
| // a little inefficient, but we don't expect the in-browswer database to be |
| // very big, we don't expect to delete often, and this is simpler than |
| // complicating the database schema to store the keyName with each certificate. |
| return thisStorage.database.certificate.each(function(certificateEntry) { |
| if (IdentityCertificate.certificateNameToPublicKeyName |
| (new Name(certificateEntry.certificateNameUri)).equals(keyName)) |
| thisStorage.database.certificate.delete |
| (certificateEntry.certificateNameUri); |
| }); |
| }); |
| }; |
| |
| /** |
| * Delete an identity and related public keys and certificates. |
| * @param {Name} identityName The identity name. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which fulfills when the identity info is |
| * deleted. |
| */ |
| IndexedDbIdentityStorage.prototype.deleteIdentityInfoPromise = function |
| (identityName, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbIdentityStorage.deleteIdentityInfoPromise is only supported for async"))); |
| |
| var thisStorage = this; |
| return this.database.identity.delete(identityName.toUri()) |
| // Iterate through each publicKey and certificate to find ones that match |
| // identityName. This is a little inefficient, but we don't expect the |
| // in-browswer database to be very big, we don't expect to delete often, and |
| // this is simpler than complicating the database schema to store the |
| // identityName with each publicKey and certificate. |
| .then(function() { |
| return thisStorage.database.publicKey.each(function(publicKeyEntry) { |
| var keyIdentityName = new Name(publicKeyEntry.keyNameUri).getPrefix(-1); |
| if (keyIdentityName.equals(identityName)) |
| thisStorage.database.publicKey.delete(publicKeyEntry.keyNameUri); |
| }); |
| }) |
| .then(function() { |
| return thisStorage.database.certificate.each(function(certificateEntry) { |
| var certificateKeyName = IdentityCertificate.certificateNameToPublicKeyName |
| (new Name(certificateEntry.certificateNameUri)); |
| var certificateIdentityName = certificateKeyName.getPrefix(-1); |
| if (certificateIdentityName.equals(identityName)) |
| thisStorage.database.certificate.delete |
| (certificateEntry.certificateNameUri); |
| }); |
| }); |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * From ndn-cxx security by Yingdi Yu <yingdi@cs.ucla.edu>. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Name = require('../../name.js').Name; /** @ignore */ |
| var Blob = require('../../util/blob.js').Blob; /** @ignore */ |
| var SecurityException = require('../security-exception.js').SecurityException; /** @ignore */ |
| var IdentityCertificate = require('../certificate/identity-certificate.js').IdentityCertificate; /** @ignore */ |
| var SyncPromise = require('../../util/sync-promise.js').SyncPromise; /** @ignore */ |
| var IdentityStorage = require('./identity-storage.js').IdentityStorage; |
| |
| /** |
| * MemoryIdentityStorage extends IdentityStorage and implements its methods to |
| * store identity, public key and certificate objects in memory. The application |
| * must get the objects through its own means and add the objects to the |
| * MemoryIdentityStorage object. To use permanent file-based storage, see |
| * BasicIdentityStorage. |
| * @constructor |
| */ |
| var MemoryIdentityStorage = function MemoryIdentityStorage() |
| { |
| // Call the base constructor. |
| IdentityStorage.call(this); |
| |
| // The map key is the identityName.toUri(). The value is the object |
| // {defaultKey // Name |
| // }. |
| this.identityStore = {}; |
| // The default identity in identityStore, or "" if not defined. |
| this.defaultIdentity = ""; |
| // The key is the keyName.toUri(). The value is the object |
| // {keyType, // number from KeyType |
| // keyDer // Blob |
| // defaultCertificate // Name |
| // }. |
| this.keyStore = {}; |
| // The key is the key is the certificateName.toUri(). The value is the |
| // encoded certificate. |
| this.certificateStore = {}; |
| }; |
| |
| MemoryIdentityStorage.prototype = new IdentityStorage(); |
| MemoryIdentityStorage.prototype.name = "MemoryIdentityStorage"; |
| |
| exports.MemoryIdentityStorage = MemoryIdentityStorage; |
| /** |
| * Check if the specified identity already exists. |
| * @param {Name} identityName The identity name. |
| * @return {SyncPromise} A promise which returns true if the identity exists. |
| */ |
| MemoryIdentityStorage.prototype.doesIdentityExistPromise = function(identityName) |
| { |
| return SyncPromise.resolve |
| (this.identityStore[identityName.toUri()] !== undefined); |
| }; |
| |
| /** |
| * Add a new identity. Do nothing if the identity already exists. |
| * @param {Name} identityName The identity name to be added. |
| * @return {SyncPromise} A promise which fulfills when the identity is added. |
| */ |
| MemoryIdentityStorage.prototype.addIdentityPromise = function(identityName) |
| { |
| var identityUri = identityName.toUri(); |
| if (this.identityStore[identityUri] === undefined) |
| this.identityStore[identityUri] = { defaultKey: null }; |
| |
| return SyncPromise.resolve(); |
| }; |
| |
| /** |
| * Check if the specified key already exists. |
| * @param {Name} keyName The name of the key. |
| * @return {SyncPromise} A promise which returns true if the key exists. |
| */ |
| MemoryIdentityStorage.prototype.doesKeyExistPromise = function(keyName) |
| { |
| return SyncPromise.resolve(this.keyStore[keyName.toUri()] !== undefined); |
| }; |
| |
| /** |
| * Add a public key to the identity storage. Also call addIdentity to ensure |
| * that the identityName for the key exists. However, if the key already |
| * exists, do nothing. |
| * @param {Name} keyName The name of the public key to be added. |
| * @param {number} keyType Type of the public key to be added from KeyType, such |
| * as KeyType.RSA.. |
| * @param {Blob} publicKeyDer A blob of the public key DER to be added. |
| * @return {SyncPromise} A promise which fulfills when complete. |
| */ |
| MemoryIdentityStorage.prototype.addKeyPromise = function |
| (keyName, keyType, publicKeyDer) |
| { |
| if (keyName.size() === 0) |
| return SyncPromise.resolve(); |
| |
| if (this.doesKeyExist(keyName)) |
| return SyncPromise.resolve(); |
| |
| var identityName = keyName.getSubName(0, keyName.size() - 1); |
| |
| this.addIdentity(identityName); |
| |
| this.keyStore[keyName.toUri()] = |
| { keyType: keyType, keyDer: new Blob(publicKeyDer), defaultCertificate: null }; |
| |
| return SyncPromise.resolve(); |
| }; |
| |
| /** |
| * Get the public key DER blob from the identity storage. |
| * @param {Name} keyName The name of the requested public key. |
| * @return {SyncPromise} A promise which returns the DER Blob, or a promise |
| * rejected with SecurityException if the key doesn't exist. |
| */ |
| MemoryIdentityStorage.prototype.getKeyPromise = function(keyName) |
| { |
| if (keyName.size() === 0) |
| return SyncPromise.reject(new SecurityException(new Error |
| ("MemoryIdentityStorage::getKeyPromise: Empty keyName"))); |
| |
| var keyNameUri = keyName.toUri(); |
| var entry = this.keyStore[keyNameUri]; |
| if (entry === undefined) |
| return SyncPromise.reject(new SecurityException(new Error |
| ("MemoryIdentityStorage::getKeyPromise: The key does not exist"))); |
| |
| return SyncPromise.resolve(entry.keyDer); |
| }; |
| |
| /** |
| * Check if the specified certificate already exists. |
| * @param {Name} certificateName The name of the certificate. |
| * @return {SyncPromise} A promise which returns true if the certificate exists. |
| */ |
| MemoryIdentityStorage.prototype.doesCertificateExistPromise = function |
| (certificateName) |
| { |
| return SyncPromise.resolve |
| (this.certificateStore[certificateName.toUri()] !== undefined); |
| }; |
| |
| /** |
| * Add a certificate to the identity storage. Also call addKey to ensure that |
| * the certificate key exists. If the certificate is already installed, don't |
| * replace it. |
| * @param {IdentityCertificate} certificate The certificate to be added. This |
| * makes a copy of the certificate. |
| * @return {SyncPromise} A promise which fulfills when finished. |
| */ |
| MemoryIdentityStorage.prototype.addCertificatePromise = function(certificate) |
| { |
| var certificateName = certificate.getName(); |
| var keyName = certificate.getPublicKeyName(); |
| |
| this.addKey(keyName, certificate.getPublicKeyInfo().getKeyType(), |
| certificate.getPublicKeyInfo().getKeyDer()); |
| |
| if (this.doesCertificateExist(certificateName)) |
| return SyncPromise.resolve(); |
| |
| // Insert the certificate. |
| // wireEncode returns the cached encoding if available. |
| this.certificateStore[certificateName.toUri()] = certificate.wireEncode(); |
| |
| return SyncPromise.resolve(); |
| }; |
| |
| /** |
| * Get a certificate from the identity storage. |
| * @param {Name} certificateName The name of the requested certificate. |
| * @return {SyncPromise} A promise which returns the requested |
| * IdentityCertificate, or a promise rejected with SecurityException if the |
| * certificate doesn't exist. |
| */ |
| MemoryIdentityStorage.prototype.getCertificatePromise = function |
| (certificateName) |
| { |
| var certificateNameUri = certificateName.toUri(); |
| if (this.certificateStore[certificateNameUri] === undefined) |
| return SyncPromise.reject(new SecurityException(new Error |
| ("MemoryIdentityStorage::getCertificatePromise: The certificate does not exist"))); |
| |
| var certificate = new IdentityCertificate(); |
| try { |
| certificate.wireDecode(this.certificateStore[certificateNameUri]); |
| } catch (ex) { |
| return SyncPromise.reject(new SecurityException(new Error |
| ("MemoryIdentityStorage::getCertificatePromise: The certificate cannot be decoded"))); |
| } |
| return SyncPromise.resolve(certificate); |
| }; |
| |
| /** |
| * Get the TPM locator associated with this storage. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise|SyncPromise} A promise which returns the TPM locator, or a |
| * promise rejected with SecurityException if the TPM locator doesn't exist. |
| */ |
| IdentityStorage.prototype.getTpmLocatorPromise = function(useSync) |
| { |
| return SyncPromise.resolve("tpm-memory:"); |
| }; |
| |
| /***************************************** |
| * Get/Set Default * |
| *****************************************/ |
| |
| /** |
| * Get the default identity. |
| * @return {SyncPromise} A promise which returns the Name of default identity, |
| * or a promise rejected with SecurityException if the default identity is not |
| * set. |
| */ |
| MemoryIdentityStorage.prototype.getDefaultIdentityPromise = function() |
| { |
| if (this.defaultIdentity.length === 0) |
| return SyncPromise.reject(new SecurityException(new Error |
| ("MemoryIdentityStorage.getDefaultIdentity: The default identity is not defined"))); |
| |
| return SyncPromise.resolve(new Name(this.defaultIdentity)); |
| }; |
| |
| /** |
| * Get the default key name for the specified identity. |
| * @param {Name} identityName The identity name. |
| * @return {SyncPromise} A promise which returns the default key Name, or a |
| * promise rejected with SecurityException if the default key name for the |
| * identity is not set. |
| */ |
| MemoryIdentityStorage.prototype.getDefaultKeyNameForIdentityPromise = function |
| (identityName) |
| { |
| var identityUri = identityName.toUri(); |
| if (this.identityStore[identityUri] !== undefined) { |
| if (this.identityStore[identityUri].defaultKey != null) |
| return SyncPromise.resolve(this.identityStore[identityUri].defaultKey); |
| else |
| return SyncPromise.reject(new SecurityException(new Error |
| ("No default key set."))); |
| } |
| else |
| return SyncPromise.reject(new SecurityException(new Error("Identity not found."))); |
| }; |
| |
| /** |
| * Get the default certificate name for the specified key. |
| * @param {Name} keyName The key name. |
| * @return {SyncPromise} A promise which returns the default certificate Name, |
| * or a promise rejected with SecurityException if the default certificate name |
| * for the key name is not set. |
| */ |
| MemoryIdentityStorage.prototype.getDefaultCertificateNameForKeyPromise = function |
| (keyName) |
| { |
| var keyUri = keyName.toUri(); |
| if (this.keyStore[keyUri] !== undefined) { |
| if (this.keyStore[keyUri].defaultCertificate != null) |
| return SyncPromise.resolve(this.keyStore[keyUri].defaultCertificate); |
| else |
| return SyncPromise.reject(new SecurityException(new Error |
| ("No default certificate set."))); |
| } |
| else |
| return SyncPromise.reject(new SecurityException(new Error("Key not found."))); |
| }; |
| |
| /** |
| * Set the default identity. If the identityName does not exist, then clear the |
| * default identity so that getDefaultIdentity() throws an exception. |
| * @param {Name} identityName The default identity name. |
| * @return {SyncPromise} A promise which fulfills when the default identity is set. |
| */ |
| MemoryIdentityStorage.prototype.setDefaultIdentityPromise = function |
| (identityName) |
| { |
| var identityUri = identityName.toUri(); |
| if (this.identityStore[identityUri] !== undefined) |
| this.defaultIdentity = identityUri; |
| else |
| // The identity doesn't exist, so clear the default. |
| this.defaultIdentity = ""; |
| |
| return SyncPromise.resolve(); |
| }; |
| |
| /** |
| * Set a key as the default key of an identity. The identity name is inferred |
| * from keyName. |
| * @param {Name} keyName The name of the key. |
| * @param {Name} identityNameCheck (optional) The identity name to check that the |
| * keyName contains the same identity name. If an empty name, it is ignored. |
| * @return {SyncPromise} A promise which fulfills when the default key name is |
| * set. |
| */ |
| MemoryIdentityStorage.prototype.setDefaultKeyNameForIdentityPromise = function |
| (keyName, identityNameCheck) |
| { |
| identityNameCheck = (identityNameCheck instanceof Name) ? identityNameCheck : null; |
| |
| var identityName = keyName.getPrefix(-1); |
| |
| if (identityNameCheck != null && identityNameCheck.size() > 0 && |
| !identityNameCheck.equals(identityName)) |
| return SyncPromise.reject(new SecurityException(new Error |
| ("The specified identity name does not match the key name"))); |
| |
| var identityUri = identityName.toUri(); |
| if (this.identityStore[identityUri] !== undefined) |
| this.identityStore[identityUri].defaultKey = new Name(keyName); |
| |
| return SyncPromise.resolve(); |
| }; |
| |
| /** |
| * Set the default key name for the specified identity. |
| * @param {Name} keyName The key name. |
| * @param {Name} certificateName The certificate name. |
| * @return {SyncPromise} A promise which fulfills when the default certificate |
| * name is set. |
| */ |
| MemoryIdentityStorage.prototype.setDefaultCertificateNameForKeyPromise = function |
| (keyName, certificateName) |
| { |
| var keyUri = keyName.toUri(); |
| if (this.keyStore[keyUri] !== undefined) |
| this.keyStore[keyUri].defaultCertificate = new Name(certificateName); |
| |
| return SyncPromise.resolve(); |
| }; |
| |
| /***************************************** |
| * Delete Methods * |
| *****************************************/ |
| |
| /** |
| * Delete a certificate. |
| * @param {Name} certificateName The certificate name. |
| * @return {SyncPromise} A promise which fulfills when the certificate |
| * info is deleted. |
| */ |
| MemoryIdentityStorage.prototype.deleteCertificateInfoPromise = function |
| (certificateName) |
| { |
| return SyncPromise.reject(new Error |
| ("MemoryIdentityStorage.deleteCertificateInfoPromise is not implemented")); |
| }; |
| |
| /** |
| * Delete a public key and related certificates. |
| * @param {Name} keyName The key name. |
| * @return {SyncPromise} A promise which fulfills when the public key info is |
| * deleted. |
| */ |
| MemoryIdentityStorage.prototype.deletePublicKeyInfoPromise = function(keyName) |
| { |
| return SyncPromise.reject(new Error |
| ("MemoryIdentityStorage.deletePublicKeyInfoPromise is not implemented")); |
| }; |
| |
| /** |
| * Delete an identity and related public keys and certificates. |
| * @param {Name} identity The identity name. |
| * @return {SyncPromise} A promise which fulfills when the identity info is |
| * deleted. |
| */ |
| MemoryIdentityStorage.prototype.deleteIdentityInfoPromise = function(identity) |
| { |
| return SyncPromise.reject(new Error |
| ("MemoryIdentityStorage.deleteIdentityInfoPromise is not implemented")); |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * From ndn-cxx security by Yingdi Yu <yingdi@cs.ucla.edu>. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var SyncPromise = require('../../util/sync-promise.js').SyncPromise; /** @ignore */ |
| var DerNode = require('../../encoding/der/der-node.js').DerNode; |
| |
| /** |
| * PrivateKeyStorage is an abstract class which declares methods for working |
| * with a private key storage. You should use a subclass. |
| * @constructor |
| */ |
| var PrivateKeyStorage = function PrivateKeyStorage() |
| { |
| }; |
| |
| exports.PrivateKeyStorage = PrivateKeyStorage; |
| |
| /** |
| * Generate a pair of asymmetric keys. |
| * @param {Name} keyName The name of the key pair. |
| * @param {KeyParams} params The parameters of the key. |
| * @param {boolean} (optional) useSync If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that fulfills when the pair is |
| * generated. |
| */ |
| PrivateKeyStorage.prototype.generateKeyPairPromise = function |
| (keyName, params, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("PrivateKeyStorage.generateKeyPairPromise is not implemented")); |
| }; |
| |
| /** |
| * Generate a pair of asymmetric keys. |
| * @param {Name} keyName The name of the key pair. |
| * @param {KeyParams} params The parameters of the key. |
| * @throws Error If generateKeyPairPromise doesn't return a SyncPromise which |
| * is already fulfilled. |
| */ |
| PrivateKeyStorage.prototype.generateKeyPair = function(keyName, params) |
| { |
| SyncPromise.getValue(this.generateKeyPairPromise(keyName, params, true)); |
| }; |
| |
| /** |
| * Delete a pair of asymmetric keys. If the key doesn't exist, do nothing. |
| * @param {Name} keyName The name of the key pair. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that fulfills when the key pair is |
| * deleted. |
| */ |
| PrivateKeyStorage.prototype.deleteKeyPairPromise = function(keyName, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("PrivateKeyStorage.deleteKeyPairPromise is not implemented")); |
| }; |
| |
| /** |
| * Delete a pair of asymmetric keys. If the key doesn't exist, do nothing. |
| * @param {Name} keyName The name of the key pair. |
| * @throws Error If deleteKeyPairPromise doesn't return a SyncPromise which |
| * is already fulfilled. |
| */ |
| PrivateKeyStorage.prototype.deleteKeyPair = function(keyName) |
| { |
| SyncPromise.getValue(this.deleteKeyPairPromise(keyName, true)); |
| }; |
| |
| /** |
| * Get the public key |
| * @param {Name} keyName The name of public key. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns the PublicKey. |
| */ |
| PrivateKeyStorage.prototype.getPublicKeyPromise = function(keyName, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("PrivateKeyStorage.getPublicKeyPromise is not implemented")); |
| }; |
| |
| /** |
| * Get the public key |
| * @param {Name} keyName The name of public key. |
| * @return {PublicKey} The public key. |
| * @throws Error If getPublicKeyPromise doesn't return a SyncPromise which |
| * is already fulfilled. |
| */ |
| PrivateKeyStorage.prototype.getPublicKey = function(keyName) |
| { |
| return SyncPromise.getValue(this.getPublicKeyPromise(keyName, true)); |
| }; |
| |
| /** |
| * Fetch the private key for keyName and sign the data to produce a signature Blob. |
| * @param {Buffer} data Pointer to the input byte array. |
| * @param {Name} keyName The name of the signing key. |
| * @param {number} digestAlgorithm (optional) The digest algorithm from |
| * DigestAlgorithm, such as DigestAlgorithm.SHA256. If omitted, use |
| * DigestAlgorithm.SHA256. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns the signature Blob. |
| */ |
| PrivateKeyStorage.prototype.signPromise = function |
| (data, keyName, digestAlgorithm, useSync) |
| { |
| return SyncPromise.reject(new Error("PrivateKeyStorage.sign is not implemented")); |
| }; |
| |
| /** |
| * Fetch the private key for keyName and sign the data to produce a signature Blob. |
| * @param {Buffer} data Pointer to the input byte array. |
| * @param {Name} keyName The name of the signing key. |
| * @param {number} digestAlgorithm (optional) The digest algorithm from |
| * DigestAlgorithm, such as DigestAlgorithm.SHA256. If omitted, use |
| * DigestAlgorithm.SHA256. |
| * @return {Blob} The signature Blob. |
| * @throws Error If signPromise doesn't return a SyncPromise which is already |
| * fulfilled. |
| */ |
| PrivateKeyStorage.prototype.sign = function(data, keyName, digestAlgorithm) |
| { |
| return SyncPromise.getValue |
| (this.signPromise(data, keyName, digestAlgorithm, true)); |
| }; |
| |
| /** |
| * Decrypt data. |
| * @param {Name} keyName The name of the decrypting key. |
| * @param {Buffer} data The byte to be decrypted. |
| * @param {boolean} isSymmetric (optional) If true symmetric encryption is used, |
| * otherwise asymmetric encryption is used. If omitted, use asymmetric |
| * encryption. |
| * @return {Blob} The decrypted data. |
| */ |
| PrivateKeyStorage.prototype.decrypt = function(keyName, data, isSymmetric) |
| { |
| throw new Error("PrivateKeyStorage.decrypt is not implemented"); |
| }; |
| |
| /** |
| * Encrypt data. |
| * @param {Name} keyName The name of the encrypting key. |
| * @param {Buffer} data The byte to be encrypted. |
| * @param {boolean} isSymmetric (optional) If true symmetric encryption is used, |
| * otherwise asymmetric encryption is used. If omitted, use asymmetric |
| * encryption. |
| * @return {Blob} The encrypted data. |
| */ |
| PrivateKeyStorage.prototype.encrypt = function(keyName, data, isSymmetric) |
| { |
| throw new Error("PrivateKeyStorage.encrypt is not implemented"); |
| }; |
| |
| /** |
| * @brief Generate a symmetric key. |
| * @param {Name} keyName The name of the key. |
| * @param {KeyParams} params The parameters of the key. |
| */ |
| PrivateKeyStorage.prototype.generateKey = function(keyName, params) |
| { |
| throw new Error("PrivateKeyStorage.generateKey is not implemented"); |
| }; |
| |
| /** |
| * Check if a particular key exists. |
| * @param {Name} keyName The name of the key. |
| * @param {number} keyClass The class of the key, e.g. KeyClass.PUBLIC, |
| * KeyClass.PRIVATE, or KeyClass.SYMMETRIC. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which returns true if the key exists. |
| */ |
| PrivateKeyStorage.prototype.doesKeyExistPromise = function |
| (keyName, keyClass, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("PrivateKeyStorage.doesKeyExist is not implemented")); |
| }; |
| |
| /** |
| * Check if a particular key exists. |
| * @param {Name} keyName The name of the key. |
| * @param {number} keyClass The class of the key, e.g. KeyClass.PUBLIC, |
| * KeyClass.PRIVATE, or KeyClass.SYMMETRIC. |
| * @return {boolean} True if the key exists. |
| * @throws Error If doesKeyExistPromise doesn't return a SyncPromise which |
| * is already fulfilled. |
| */ |
| PrivateKeyStorage.prototype.doesKeyExist = function(keyName, keyClass) |
| { |
| return SyncPromise.getValue(this.doesKeyExistPromise(keyName, keyClass, true)); |
| }; |
| |
| /** |
| * Encode the private key to a PKCS #8 private key. We do this explicitly here |
| * to avoid linking to extra OpenSSL libraries. |
| * @param {Buffer} privateKeyDer The input private key DER. |
| * @param {OID} oid The OID of the privateKey. |
| * @param {DerNode} parameters The DerNode of the parameters for the OID. |
| * @return {Blob} The PKCS #8 private key DER. |
| */ |
| PrivateKeyStorage.encodePkcs8PrivateKey = function |
| (privateKeyDer, oid, parameters) |
| { |
| var algorithmIdentifier = new DerNode.DerSequence(); |
| algorithmIdentifier.addChild(new DerNode.DerOid(oid)); |
| algorithmIdentifier.addChild(parameters); |
| |
| var result = new DerNode.DerSequence(); |
| result.addChild(new DerNode.DerInteger(0)); |
| result.addChild(algorithmIdentifier); |
| result.addChild(new DerNode.DerOctetString(privateKeyDer)); |
| |
| return result.encode(); |
| }; |
| |
| /** |
| * Encode the RSAKey private key as a PKCS #1 private key. |
| * @param {RSAKey} rsaKey The RSAKey private key. |
| * @return {Blob} The PKCS #1 private key DER. |
| */ |
| PrivateKeyStorage.encodePkcs1PrivateKeyFromRSAKey = function(rsaKey) |
| { |
| // Imitate KJUR getEncryptedPKCS5PEMFromRSAKey. |
| var result = new DerNode.DerSequence(); |
| |
| result.addChild(new DerNode.DerInteger(0)); |
| result.addChild(new DerNode.DerInteger(PrivateKeyStorage.bigIntegerToBuffer(rsaKey.n))); |
| result.addChild(new DerNode.DerInteger(rsaKey.e)); |
| result.addChild(new DerNode.DerInteger(PrivateKeyStorage.bigIntegerToBuffer(rsaKey.d))); |
| result.addChild(new DerNode.DerInteger(PrivateKeyStorage.bigIntegerToBuffer(rsaKey.p))); |
| result.addChild(new DerNode.DerInteger(PrivateKeyStorage.bigIntegerToBuffer(rsaKey.q))); |
| result.addChild(new DerNode.DerInteger(PrivateKeyStorage.bigIntegerToBuffer(rsaKey.dmp1))); |
| result.addChild(new DerNode.DerInteger(PrivateKeyStorage.bigIntegerToBuffer(rsaKey.dmq1))); |
| result.addChild(new DerNode.DerInteger(PrivateKeyStorage.bigIntegerToBuffer(rsaKey.coeff))); |
| |
| return result.encode(); |
| }; |
| |
| /** |
| * Encode the public key values in the RSAKey private key as a |
| * SubjectPublicKeyInfo. |
| * @param {RSAKey} rsaKey The RSAKey private key with the public key values. |
| * @return {Blob} The SubjectPublicKeyInfo DER. |
| */ |
| PrivateKeyStorage.encodePublicKeyFromRSAKey = function(rsaKey) |
| { |
| var rsaPublicKey = new DerNode.DerSequence(); |
| |
| rsaPublicKey.addChild(new DerNode.DerInteger(PrivateKeyStorage.bigIntegerToBuffer(rsaKey.n))); |
| rsaPublicKey.addChild(new DerNode.DerInteger(rsaKey.e)); |
| |
| var algorithmIdentifier = new DerNode.DerSequence(); |
| algorithmIdentifier.addChild |
| (new DerNode.DerOid(new OID(PrivateKeyStorage.RSA_ENCRYPTION_OID))); |
| algorithmIdentifier.addChild(new DerNode.DerNull()); |
| |
| var result = new DerNode.DerSequence(); |
| |
| result.addChild(algorithmIdentifier); |
| result.addChild(new DerNode.DerBitString(rsaPublicKey.encode().buf(), 0)); |
| |
| return result.encode(); |
| }; |
| |
| /** |
| * Convert a BigInteger to a Buffer. |
| * @param {BigInteger} bigInteger The BigInteger. |
| * @return {Buffer} The Buffer. |
| */ |
| PrivateKeyStorage.bigIntegerToBuffer = function(bigInteger) |
| { |
| // Imitate KJUR.asn1.ASN1Util.bigIntToMinTwosComplementsHex. |
| var hex = bigInteger.toString(16); |
| if (hex.substr(0, 1) == "-") |
| throw new Error |
| ("PrivateKeyStorage.bigIntegerToBuffer: Negative integers are not currently supported"); |
| |
| if (hex.length % 2 == 1) |
| // Odd number of characters. |
| hex = "0" + hex; |
| else { |
| if (! hex.match(/^[0-7]/)) |
| // The first byte is >= 0x80, so prepend a zero to keep it positive. |
| hex = "00" + hex; |
| } |
| |
| return new Buffer(hex, 'hex'); |
| }; |
| |
| PrivateKeyStorage.RSA_ENCRYPTION_OID = "1.2.840.113549.1.1.1"; |
| PrivateKeyStorage.EC_ENCRYPTION_OID = "1.2.840.10045.2.1"; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * From ndn-cxx security by Yingdi Yu <yingdi@cs.ucla.edu>. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| // Use capitalized Crypto to not clash with the browser's crypto.subtle. |
| /** @ignore */ |
| var Crypto = require('../../crypto.js'); /** @ignore */ |
| var Blob = require('../../util/blob.js').Blob; /** @ignore */ |
| var SecurityException = require('../security-exception.js').SecurityException; /** @ignore */ |
| var PublicKey = require('../certificate/public-key.js').PublicKey; /** @ignore */ |
| var KeyClass = require('../security-types.js').KeyClass; /** @ignore */ |
| var KeyType = require('../security-types').KeyType; /** @ignore */ |
| var DigestAlgorithm = require('../security-types.js').DigestAlgorithm; /** @ignore */ |
| var DataUtils = require('../../encoding/data-utils.js').DataUtils; /** @ignore */ |
| var PrivateKeyStorage = require('./private-key-storage.js').PrivateKeyStorage; /** @ignore */ |
| var DerNode = require('../../encoding/der/der-node.js').DerNode; /** @ignore */ |
| var OID = require('../../encoding/oid.js').OID; /** @ignore */ |
| var SyncPromise = require('../../util/sync-promise.js').SyncPromise; /** @ignore */ |
| var UseSubtleCrypto = require('../../use-subtle-crypto-node.js').UseSubtleCrypto; /** @ignore */ |
| var rsaKeygen = null; |
| try { |
| // This should be installed with: sudo npm install rsa-keygen |
| rsaKeygen = require('rsa-keygen'); |
| } |
| catch (e) {} |
| |
| /** |
| * MemoryPrivateKeyStorage class extends PrivateKeyStorage to implement private |
| * key storage in memory. |
| * @constructor |
| */ |
| var MemoryPrivateKeyStorage = function MemoryPrivateKeyStorage() |
| { |
| // Call the base constructor. |
| PrivateKeyStorage.call(this); |
| |
| // The key is the keyName.toUri(). The value is security.certificate.PublicKey. |
| this.publicKeyStore = {}; |
| // The key is the keyName.toUri(). The value is the object |
| // {keyType, // number from KeyType |
| // privateKey // The PEM-encoded private key. |
| // }. |
| this.privateKeyStore = {}; |
| }; |
| |
| MemoryPrivateKeyStorage.prototype = new PrivateKeyStorage(); |
| MemoryPrivateKeyStorage.prototype.name = "MemoryPrivateKeyStorage"; |
| |
| exports.MemoryPrivateKeyStorage = MemoryPrivateKeyStorage; |
| |
| /** |
| * Set the public key for the keyName. |
| * @param {Name} keyName The key name. |
| * @param {number} keyType The KeyType, such as KeyType.RSA. |
| * @param {Buffer} publicKeyDer The public key DER byte array. |
| */ |
| MemoryPrivateKeyStorage.prototype.setPublicKeyForKeyName = function |
| (keyName, keyType, publicKeyDer) |
| { |
| this.publicKeyStore[keyName.toUri()] = new PublicKey |
| (new Blob(publicKeyDer, true)); |
| }; |
| |
| /** |
| * Set the private key for the keyName. |
| * @param {Name} keyName The key name. |
| * @param {number} keyType The KeyType, such as KeyType.RSA. |
| * @param {Buffer} privateKeyDer The private key DER byte array. |
| */ |
| MemoryPrivateKeyStorage.prototype.setPrivateKeyForKeyName = function |
| (keyName, keyType, privateKeyDer) |
| { |
| // Encode the DER as PEM. |
| var keyBase64 = privateKeyDer.toString('base64'); |
| var keyPem; |
| if (keyType === KeyType.RSA) { |
| keyPem = "-----BEGIN RSA PRIVATE KEY-----\n"; |
| for (var i = 0; i < keyBase64.length; i += 64) |
| keyPem += (keyBase64.substr(i, 64) + "\n"); |
| keyPem += "-----END RSA PRIVATE KEY-----"; |
| } |
| else if (keyType === KeyType.ECDSA) { |
| keyPem = "-----BEGIN EC PRIVATE KEY-----\n"; |
| for (var i = 0; i < keyBase64.length; i += 64) |
| keyPem += (keyBase64.substr(i, 64) + "\n"); |
| keyPem += "-----END EC PRIVATE KEY-----"; |
| } |
| else |
| throw new SecurityException(new Error |
| ("MemoryPrivateKeyStorage: KeyType is not supported")); |
| |
| this.privateKeyStore[keyName.toUri()] = |
| { keyType: keyType, privateKey: keyPem }; |
| }; |
| |
| /** |
| * Set the public and private key for the keyName. |
| * @param {Name} keyName The key name. |
| * @param {number} keyType The KeyType, such as KeyType.RSA. |
| * @param {Buffer} publicKeyDer The public key DER byte array. |
| * @param {Buffer} privateKeyDer The private key DER byte array. |
| */ |
| MemoryPrivateKeyStorage.prototype.setKeyPairForKeyName = function |
| (keyName, keyType, publicKeyDer, privateKeyDer) |
| { |
| this.setPublicKeyForKeyName(keyName, keyType, publicKeyDer); |
| this.setPrivateKeyForKeyName(keyName, keyType, privateKeyDer); |
| }; |
| |
| /** |
| * Generate a pair of asymmetric keys. |
| * @param {Name} keyName The name of the key pair. |
| * @param {KeyParams} params The parameters of the key. |
| * @param {boolean} useSync (optional) If true then use blocking crypto and |
| * return a SyncPromise which is already fulfilled. If omitted or false, if |
| * possible use crypto.subtle and return an async Promise, otherwise use |
| * blocking crypto and return a SyncPromise. |
| * @return {Promise|SyncPromise} A promise that fulfills when the pair is |
| * generated. |
| */ |
| MemoryPrivateKeyStorage.prototype.generateKeyPairPromise = function |
| (keyName, params, useSync) |
| { |
| if (this.doesKeyExist(keyName, KeyClass.PUBLIC)) |
| return SyncPromise.reject(new SecurityException(new Error |
| ("Public key already exists"))); |
| if (this.doesKeyExist(keyName, KeyClass.PRIVATE)) |
| return SyncPromise.reject(new SecurityException(new Error |
| ("Private key already exists"))); |
| |
| var thisStore = this; |
| |
| if (UseSubtleCrypto() && !useSync) { |
| if (params.getKeyType() === KeyType.RSA) { |
| var privateKey = null; |
| var publicKeyDer = null; |
| |
| return crypto.subtle.generateKey |
| ({ name: "RSASSA-PKCS1-v1_5", modulusLength: params.getKeySize(), |
| publicExponent: new Uint8Array([0x01, 0x00, 0x01]), |
| hash: {name: "SHA-256"} }, |
| true, ["sign", "verify"]) |
| .then(function(key) { |
| privateKey = key.privateKey; |
| |
| // Export the public key to DER. |
| return crypto.subtle.exportKey("spki", key.publicKey); |
| }) |
| .then(function(exportedPublicKey) { |
| publicKeyDer = new Blob(new Uint8Array(exportedPublicKey), false).buf(); |
| |
| // Export the private key to DER. |
| return crypto.subtle.exportKey("pkcs8", privateKey); |
| }) |
| .then(function(pkcs8Der) { |
| // Crypto.subtle exports the private key as PKCS #8. Decode it to find |
| // the inner private key DER. |
| var parsedNode = DerNode.parse |
| (new Blob(new Uint8Array(pkcs8Der), false).buf()); |
| // Get the value of the 3rd child which is the octet string. |
| var privateKeyDer = parsedNode.getChildren()[2].toVal(); |
| |
| // Save the key pair. |
| thisStore.setKeyPairForKeyName |
| (keyName, params.getKeyType(), publicKeyDer, privateKeyDer.buf()); |
| |
| // sign will use subtleKey directly. |
| thisStore.privateKeyStore[keyName.toUri()].subtleKey = privateKey; |
| |
| return Promise.resolve(); |
| }); |
| } |
| else |
| return SyncPromise.reject(new SecurityException(new Error |
| ("Only RSA key generation currently supported"))); |
| } |
| else { |
| return SyncPromise.resolve() |
| .then(function() { |
| if (typeof RSAKey !== 'undefined') { |
| // Assume we are in the browser. |
| if (params.getKeyType() === KeyType.RSA) { |
| var rsaKey = new RSAKey(); |
| rsaKey.generate(params.getKeySize(), '010001'); |
| thisStore.setKeyPairForKeyName |
| (keyName, params.getKeyType(), |
| PrivateKeyStorage.encodePublicKeyFromRSAKey(rsaKey).buf(), |
| PrivateKeyStorage.encodePkcs1PrivateKeyFromRSAKey(rsaKey).buf()); |
| } |
| else |
| return SyncPromise.reject(new SecurityException(new Error |
| ("Only RSA key generation currently supported"))); |
| } |
| else { |
| // Assume we are in Node.js. |
| var publicKeyDer; |
| var privateKeyPem; |
| |
| if (params.getKeyType() === KeyType.RSA) { |
| if (!rsaKeygen) |
| return SyncPromise.reject(new SecurityException(new Error |
| ("Need to install rsa-keygen: sudo npm install rsa-keygen"))); |
| |
| var keyPair = rsaKeygen.generate(params.getKeySize()); |
| |
| // Get the public key DER from the PEM string. |
| var publicKeyBase64 = keyPair.public_key.toString().replace |
| ("-----BEGIN PUBLIC KEY-----", "").replace |
| ("-----END PUBLIC KEY-----", ""); |
| publicKeyDer = new Buffer(publicKeyBase64, 'base64'); |
| |
| privateKeyPem = keyPair.private_key.toString(); |
| } |
| else |
| return SyncPromise.reject(new SecurityException(new Error |
| ("Only RSA key generation currently supported"))); |
| |
| thisStore.setPublicKeyForKeyName(keyName, params.getKeyType(), publicKeyDer); |
| thisStore.privateKeyStore[keyName.toUri()] = |
| { keyType: params.getKeyType(), privateKey: privateKeyPem }; |
| } |
| |
| return SyncPromise.resolve(); |
| }); |
| } |
| }; |
| |
| /** |
| * Delete a pair of asymmetric keys. If the key doesn't exist, do nothing. |
| * @param {Name} keyName The name of the key pair. |
| * @return {SyncPromise} A promise that fulfills when the key pair is deleted. |
| */ |
| MemoryPrivateKeyStorage.prototype.deleteKeyPairPromise = function(keyName) |
| { |
| var keyUri = keyName.toUri(); |
| |
| delete this.publicKeyStore[keyUri]; |
| delete this.privateKeyStore[keyUri]; |
| |
| return SyncPromise.resolve(); |
| }; |
| |
| /** |
| * Get the public key |
| * @param {Name} keyName The name of public key. |
| * @return {SyncPromise} A promise that returns the PublicKey. |
| */ |
| MemoryPrivateKeyStorage.prototype.getPublicKeyPromise = function(keyName) |
| { |
| var keyUri = keyName.toUri(); |
| var publicKey = this.publicKeyStore[keyUri]; |
| if (publicKey === undefined) |
| return SyncPromise.reject(new SecurityException(new Error |
| ("MemoryPrivateKeyStorage: Cannot find public key " + keyName.toUri()))); |
| |
| return SyncPromise.resolve(publicKey); |
| }; |
| |
| /** |
| * Fetch the private key for keyName and sign the data to produce a signature Blob. |
| * @param {Buffer} data Pointer to the input byte array. |
| * @param {Name} keyName The name of the signing key. |
| * @param {number} digestAlgorithm (optional) The digest algorithm from |
| * DigestAlgorithm, such as DigestAlgorithm.SHA256. If omitted, use |
| * DigestAlgorithm.SHA256. |
| * @param {boolean} useSync (optional) If true then use blocking crypto and |
| * return a SyncPromise which is already fulfilled. If omitted or false, if |
| * possible use crypto.subtle and return an async Promise, otherwise use |
| * blocking crypto and return a SyncPromise. |
| * @return {Promise|SyncPromise} A promise that returns the signature Blob. |
| */ |
| MemoryPrivateKeyStorage.prototype.signPromise = function |
| (data, keyName, digestAlgorithm, useSync) |
| { |
| useSync = (typeof digestAlgorithm === "boolean") ? digestAlgorithm : useSync; |
| digestAlgorithm = (typeof digestAlgorithm === "boolean" || !digestAlgorithm) ? DigestAlgorithm.SHA256 : digestAlgorithm; |
| |
| if (digestAlgorithm != DigestAlgorithm.SHA256) |
| return SyncPromise.reject(new SecurityException(new Error |
| ("MemoryPrivateKeyStorage.sign: Unsupported digest algorithm"))); |
| |
| // Find the private key. |
| var keyUri = keyName.toUri(); |
| var privateKey = this.privateKeyStore[keyUri]; |
| if (privateKey === undefined) |
| return SyncPromise.reject(new SecurityException(new Error |
| ("MemoryPrivateKeyStorage: Cannot find private key " + keyUri))); |
| |
| if (UseSubtleCrypto() && !useSync){ |
| var algo = {name:"RSASSA-PKCS1-v1_5",hash:{name:"SHA-256"}}; |
| |
| if (!privateKey.subtleKey){ |
| //this is the first time in the session that we're using crypto subtle with this key |
| //so we have to convert to pkcs8 and import it. |
| //assigning it to privateKey.subtleKey means we only have to do this once per session, |
| //giving us a small, but not insignificant, performance boost. |
| var privateDER = DataUtils.privateKeyPemToDer(privateKey.privateKey); |
| var pkcs8 = PrivateKeyStorage.encodePkcs8PrivateKey |
| (privateDER, new OID(PrivateKeyStorage.RSA_ENCRYPTION_OID), |
| new DerNode.DerNull()).buf(); |
| |
| var promise = crypto.subtle.importKey("pkcs8", pkcs8.buffer, algo, true, ["sign"]).then(function(subtleKey){ |
| //cache the crypto.subtle key object |
| privateKey.subtleKey = subtleKey; |
| return crypto.subtle.sign(algo, subtleKey, data); |
| }); |
| } else { |
| // The crypto.subtle key has been cached on a previous sign or from keygen. |
| var promise = crypto.subtle.sign(algo, privateKey.subtleKey, data); |
| } |
| |
| return promise.then(function(signature){ |
| var result = new Blob(new Uint8Array(signature), true); |
| return Promise.resolve(result); |
| }); |
| } else { |
| var signer; |
| if (privateKey.keyType === KeyType.RSA) |
| signer = Crypto.createSign("RSA-SHA256"); |
| else if (privateKey.keyType === KeyType.ECDSA) |
| // Just create a "sha256". The Crypto library will infer ECDSA from the key. |
| signer = Crypto.createSign("sha256"); |
| else |
| // We don't expect this to happen since setPrivateKeyForKeyName already checked. |
| return SyncPromise.reject(new SecurityException(new Error |
| ("MemoryPrivateKeyStorage.sign: Unrecognized private key type"))); |
| |
| signer.update(data); |
| var signature = new Buffer |
| (DataUtils.toNumbersIfString(signer.sign(privateKey.privateKey))); |
| var result = new Blob(signature, false); |
| |
| return SyncPromise.resolve(result); |
| } |
| }; |
| |
| /** |
| * Check if a particular key exists. |
| * @param {Name} keyName The name of the key. |
| * @param {number} keyClass The class of the key, e.g. KeyClass.PUBLIC, |
| * KeyClass.PRIVATE, or KeyClass.SYMMETRIC. |
| * @return {SyncPromise} A promise which returns true if the key exists. |
| */ |
| MemoryPrivateKeyStorage.prototype.doesKeyExistPromise = function |
| (keyName, keyClass) |
| { |
| var keyUri = keyName.toUri(); |
| var result = false; |
| if (keyClass == KeyClass.PUBLIC) |
| result = this.publicKeyStore[keyUri] !== undefined; |
| else if (keyClass == KeyClass.PRIVATE) |
| result = this.privateKeyStore[keyUri] !== undefined; |
| |
| return SyncPromise.resolve(result); |
| }; |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| // Use capitalized Crypto to not clash with the browser's crypto.subtle. |
| var Crypto = require('../../crypto.js'); |
| // Don't require other modules since this is meant for the browser, not Node.js. |
| |
| /** |
| * IndexedDbPrivateKeyStorage extends PrivateKeyStorage to implement private key |
| * storage using the browser's IndexedDB service. |
| * @constructor |
| */ |
| var IndexedDbPrivateKeyStorage = function IndexedDbPrivateKeyStorage() |
| { |
| PrivateKeyStorage.call(this); |
| |
| this.database = new Dexie("ndnsec-tpm"); |
| this.database.version(1).stores({ |
| // "nameHash" is transformName(keyName) // string |
| // "encoding" is the public key DER // Uint8Array |
| publicKey: "nameHash", |
| |
| // "nameHash" is transformName(keyName) // string |
| // "encoding" is the PKCS 8 private key DER // Uint8Array |
| privateKey: "nameHash" |
| }); |
| this.database.open(); |
| }; |
| |
| IndexedDbPrivateKeyStorage.prototype = new PrivateKeyStorage(); |
| IndexedDbPrivateKeyStorage.prototype.name = "IndexedDbPrivateKeyStorage"; |
| |
| /** |
| * Generate a pair of asymmetric keys. |
| * @param {Name} keyName The name of the key pair. |
| * @param {KeyParams} params The parameters of the key. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that fulfills when the pair is generated. |
| */ |
| IndexedDbPrivateKeyStorage.prototype.generateKeyPairPromise = function |
| (keyName, params, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbPrivateKeyStorage.generateKeyPairPromise is only supported for async"))); |
| |
| var thisStorage = this; |
| |
| return thisStorage.doesKeyExistPromise(keyName, KeyClass.PUBLIC) |
| .then(function(exists) { |
| if (exists) |
| throw new Error("Public key already exists"); |
| |
| return thisStorage.doesKeyExistPromise(keyName, KeyClass.PRIVATE); |
| }) |
| .then(function(exists) { |
| if (exists) |
| throw new Error("Private key already exists"); |
| |
| if (params.getKeyType() === KeyType.RSA) { |
| var privateKey = null; |
| var publicKeyDer = null; |
| |
| return crypto.subtle.generateKey |
| ({ name: "RSASSA-PKCS1-v1_5", modulusLength: params.getKeySize(), |
| publicExponent: new Uint8Array([0x01, 0x00, 0x01]), |
| hash: {name: "SHA-256"} }, |
| true, ["sign", "verify"]) |
| .then(function(key) { |
| privateKey = key.privateKey; |
| |
| // Export the public key to DER. |
| return crypto.subtle.exportKey("spki", key.publicKey); |
| }) |
| .then(function(exportedPublicKey) { |
| publicKeyDer = new Uint8Array(exportedPublicKey); |
| |
| // Export the private key to DER. |
| return crypto.subtle.exportKey("pkcs8", privateKey); |
| }) |
| .then(function(pkcs8Der) { |
| // Save the key pair |
| return thisStorage.database.transaction |
| ("rw", thisStorage.database.privateKey, thisStorage.database.publicKey, function () { |
| thisStorage.database.publicKey.put |
| ({nameHash: IndexedDbPrivateKeyStorage.transformName(keyName), |
| encoding: publicKeyDer}); |
| thisStorage.database.privateKey.put |
| ({nameHash: IndexedDbPrivateKeyStorage.transformName(keyName), |
| encoding: new Uint8Array(pkcs8Der)}); |
| }); |
| }); |
| } |
| else |
| throw new Error("Only RSA key generation currently supported"); |
| }); |
| }; |
| |
| |
| /** |
| * Delete a pair of asymmetric keys. If the key doesn't exist, do nothing. |
| * @param {Name} keyName The name of the key pair. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that fulfills when the key pair is deleted. |
| */ |
| IndexedDbPrivateKeyStorage.prototype.deleteKeyPairPromise = function |
| (keyName, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbPrivateKeyStorage.deleteKeyPairPromise is only supported for async"))); |
| |
| var thisStorage = this; |
| // delete does nothing if the key doesn't exist. |
| return this.database.publicKey.delete |
| (IndexedDbPrivateKeyStorage.transformName(keyName)) |
| .then(function() { |
| return thisStorage.database.privateKey.delete |
| (IndexedDbPrivateKeyStorage.transformName(keyName)); |
| }); |
| }; |
| |
| /** |
| * Get the public key |
| * @param {Name} keyName The name of public key. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that returns the PublicKey. |
| */ |
| IndexedDbPrivateKeyStorage.prototype.getPublicKeyPromise = function |
| (keyName, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbPrivateKeyStorage.getPublicKeyPromise is only supported for async"))); |
| |
| return this.database.publicKey.get |
| (IndexedDbPrivateKeyStorage.transformName(keyName)) |
| .then(function(publicKeyEntry) { |
| return Promise.resolve(new PublicKey(new Blob(publicKeyEntry.encoding))); |
| }); |
| }; |
| |
| /** |
| * Fetch the private key for keyName and sign the data to produce a signature Blob. |
| * @param {Buffer} data Pointer to the input byte array. |
| * @param {Name} keyName The name of the signing key. |
| * @param {number} digestAlgorithm (optional) The digest algorithm from |
| * DigestAlgorithm, such as DigestAlgorithm.SHA256. If omitted, use |
| * DigestAlgorithm.SHA256. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that returns the signature Blob. |
| */ |
| IndexedDbPrivateKeyStorage.prototype.signPromise = function |
| (data, keyName, digestAlgorithm, useSync) |
| { |
| useSync = (typeof digestAlgorithm === "boolean") ? digestAlgorithm : useSync; |
| digestAlgorithm = (typeof digestAlgorithm === "boolean" || !digestAlgorithm) ? DigestAlgorithm.SHA256 : digestAlgorithm; |
| |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbPrivateKeyStorage.signPromise is only supported for async"))); |
| |
| if (digestAlgorithm != DigestAlgorithm.SHA256) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbPrivateKeyStorage.sign: Unsupported digest algorithm"))); |
| |
| // TODO: Support non-RSA keys. |
| var algo = { name: "RSASSA-PKCS1-v1_5", hash: {name: "SHA-256" }}; |
| |
| // Find the private key. |
| return this.database.privateKey.get |
| (IndexedDbPrivateKeyStorage.transformName(keyName)) |
| .then(function(privateKeyEntry) { |
| return crypto.subtle.importKey |
| ("pkcs8", new Blob(privateKeyEntry.encoding).buf(), algo, true, ["sign"]); |
| }) |
| .then(function(privateKey) { |
| return crypto.subtle.sign(algo, privateKey, data); |
| }) |
| .then(function(signature) { |
| return Promise.resolve(new Blob(new Uint8Array(signature), true)); |
| }); |
| }; |
| |
| /** |
| * Check if a particular key exists. |
| * @param {Name} keyName The name of the key. |
| * @param {number} keyClass The class of the key, e.g. KeyClass.PUBLIC, |
| * KeyClass.PRIVATE, or KeyClass.SYMMETRIC. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise which returns true if the key exists. |
| */ |
| IndexedDbPrivateKeyStorage.prototype.doesKeyExistPromise = function |
| (keyName, keyClass, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new SecurityException(new Error |
| ("IndexedDbPrivateKeyStorage.doesKeyExistPromise is only supported for async"))); |
| |
| var table = null; |
| if (keyClass == KeyClass.PUBLIC) |
| table = this.database.publicKey; |
| else if (keyClass == KeyClass.PRIVATE) |
| table = this.database.privateKey; |
| else |
| // Silently say that anything else doesn't exist. |
| return Promise.resolve(false); |
| |
| return table.where("nameHash").equals |
| (IndexedDbPrivateKeyStorage.transformName(keyName)) |
| .count() |
| .then(function(count) { |
| return Promise.resolve(count > 0); |
| }); |
| }; |
| |
| /** |
| * Transform the key name into the base64 encoding of the hash (the same as in |
| * FilePrivateKeyStorage without the file name extension). |
| */ |
| IndexedDbPrivateKeyStorage.transformName = function(keyName) |
| { |
| var hash = Crypto.createHash('sha256'); |
| hash.update(new Buffer(keyName.toUri())); |
| var fileName = hash.digest('base64'); |
| return fileName.replace(/\//g, '%'); |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * From ndn-cxx security by Yingdi Yu <yingdi@cs.ucla.edu>. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| // Use capitalized Crypto to not clash with the browser's crypto.subtle. |
| /** @ignore */ |
| var Crypto = require('../../crypto.js'); /** @ignore */ |
| var Name = require('../../name.js').Name; /** @ignore */ |
| var Data = require('../../data.js').Data; /** @ignore */ |
| var Blob = require('../../util/blob.js').Blob; /** @ignore */ |
| var ConfigFile = require('../../util/config-file.js').ConfigFile; /** @ignore */ |
| var DigestSha256Signature = require('../../digest-sha256-signature.js').DigestSha256Signature; /** @ignore */ |
| var Sha256WithRsaSignature = require('../../sha256-with-rsa-signature.js').Sha256WithRsaSignature; /** @ignore */ |
| var Sha256WithEcdsaSignature = require('../../sha256-with-ecdsa-signature.js').Sha256WithEcdsaSignature; /** @ignore */ |
| var KeyLocatorType = require('../../key-locator.js').KeyLocatorType; /** @ignore */ |
| var WireFormat = require('../../encoding/wire-format.js').WireFormat; /** @ignore */ |
| var SecurityException = require('../security-exception.js').SecurityException; /** @ignore */ |
| var DigestAlgorithm = require('../security-types.js').DigestAlgorithm; /** @ignore */ |
| var KeyType = require('../security-types.js').KeyType; /** @ignore */ |
| var RsaKeyParams = require('../key-params.js').RsaKeyParams; /** @ignore */ |
| var IdentityCertificate = require('../certificate/identity-certificate.js').IdentityCertificate; /** @ignore */ |
| var PublicKey = require('../certificate/public-key.js').PublicKey; /** @ignore */ |
| var CertificateSubjectDescription = require('../certificate/certificate-subject-description.js').CertificateSubjectDescription; /** @ignore */ |
| var SyncPromise = require('../../util/sync-promise.js').SyncPromise; /** @ignore */ |
| var BasicIdentityStorage = require('./basic-identity-storage.js').BasicIdentityStorage; /** @ignore */ |
| var FilePrivateKeyStorage = require('./file-private-key-storage.js').FilePrivateKeyStorage; |
| |
| /** |
| * An IdentityManager is the interface of operations related to identity, keys, |
| * and certificates. |
| * |
| * Create a new IdentityManager to use the IdentityStorage and |
| * PrivateKeyStorage. |
| * @param {IdentityStorage} identityStorage An object of a subclass of |
| * IdentityStorage. In Node.js, if this is omitted then use BasicIdentityStorage. |
| * @param {PrivateKeyStorage} privateKeyStorage An object of a subclass of |
| * PrivateKeyStorage. In Node.js, if this is omitted then use the default |
| * PrivateKeyStorage for your system, which is FilePrivateKeyStorage for any |
| * system other than OS X. (OS X key chain storage is not yet implemented, so |
| * you must supply a different PrivateKeyStorage.) |
| * @throws SecurityException if this is not in Node.js and identityStorage or |
| * privateKeyStorage is omitted. |
| * @constructor |
| */ |
| var IdentityManager = function IdentityManager |
| (identityStorage, privateKeyStorage) |
| { |
| if (privateKeyStorage) { |
| // Don't call checkTpm() when using a custom PrivateKeyStorage. |
| if (!identityStorage) |
| // We don't expect this to happen. |
| throw new Error |
| ("IdentityManager: A custom privateKeyStorage is supplied with a null identityStorage") |
| |
| this.identityStorage = identityStorage; |
| this.privateKeyStorage = privateKeyStorage; |
| } |
| else { |
| if (!ConfigFile) |
| // Assume we are in the browser. |
| throw new SecurityException(new Error |
| ("IdentityManager: If not in Node.js then you must supply identityStorage and privateKeyStorage.")); |
| var config = new ConfigFile(); |
| |
| var canonicalTpmLocator = [null]; |
| var thisStorage = this; |
| // Make the function that BasicIdentityStorage will call the first time it |
| // is used. It has to be an async promise becuase getTpmLocatorPromise is async. |
| function initialCheckPromise() |
| { |
| return thisStorage.checkTpmPromise_(canonicalTpmLocator[0]); |
| } |
| |
| this.identityStorage = identityStorage ? identityStorage |
| : IdentityManager.getDefaultIdentityStorage_(config, initialCheckPromise); |
| this.privateKeyStorage = IdentityManager.getDefaultPrivateKeyStorage_ |
| (config, canonicalTpmLocator); |
| } |
| }; |
| |
| exports.IdentityManager = IdentityManager; |
| |
| /** |
| * Create an identity by creating a pair of Key-Signing-Key (KSK) for this |
| * identity and a self-signed certificate of the KSK. If a key pair or |
| * certificate for the identity already exists, use it. |
| * @param {Name} identityName The name of the identity. |
| * @params {KeyParams} params The key parameters if a key needs to be generated |
| * for the identity. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which returns the name of the default |
| * certificate of the identity. |
| */ |
| IdentityManager.prototype.createIdentityAndCertificatePromise = function |
| (identityName, params, useSync) |
| { |
| var thisManager = this; |
| var generateKey = true; |
| var keyName = null; |
| |
| return this.identityStorage.addIdentityPromise(identityName, useSync) |
| .then(function() { |
| return thisManager.identityStorage.getDefaultKeyNameForIdentityPromise |
| (identityName, useSync) |
| .then(function(localKeyName) { |
| keyName = localKeyName; |
| |
| // Set generateKey. |
| return thisManager.identityStorage.getKeyPromise(keyName, useSync) |
| .then(function(publicKeyDer) { |
| var key = new PublicKey(publicKeyDer); |
| if (key.getKeyType() == params.getKeyType()) |
| // The key exists and has the same type, so don't need to generate one. |
| generateKey = false; |
| return SyncPromise.resolve(); |
| }); |
| }, function(err) { |
| if (!(err instanceof SecurityException)) |
| throw err; |
| |
| // The key doesn't exist, so leave generateKey true. |
| return SyncPromise.resolve(); |
| }); |
| }) |
| .then(function() { |
| if (generateKey) |
| return thisManager.generateKeyPairPromise(identityName, true, params, useSync) |
| .then(function(localKeyName) { |
| keyName = localKeyName; |
| return thisManager.identityStorage.setDefaultKeyNameForIdentityPromise |
| (keyName, useSync); |
| }); |
| else |
| // Don't generate a key pair. Use the existing keyName. |
| return SyncPromise.resolve(); |
| }) |
| .then(function() { |
| return thisManager.identityStorage.getDefaultCertificateNameForKeyPromise |
| (keyName, useSync) |
| .then(function(certName) { |
| // The cert exists, so don't need to make it. |
| return SyncPromise.resolve(certName); |
| }, function(err) { |
| if (!(err instanceof SecurityException)) |
| throw err; |
| |
| // The cert doesn't exist, so make one. |
| var certName; |
| return thisManager.selfSignPromise(keyName, useSync) |
| .then(function(selfCert) { |
| certName = selfCert.getName(); |
| return thisManager.addCertificateAsIdentityDefaultPromise(selfCert, useSync); |
| }) |
| .then(function() { |
| return SyncPromise.resolve(certName); |
| }); |
| }); |
| }); |
| }; |
| |
| /** |
| * Create an identity by creating a pair of Key-Signing-Key (KSK) for this |
| * identity and a self-signed certificate of the KSK. If a key pair or |
| * certificate for the identity already exists, use it. |
| * @param {Name} identityName The name of the identity. |
| * @params {KeyParams} params The key parameters if a key needs to be generated |
| * for the identity. |
| * @param {function} onComplete (optional) This calls onComplete(certificateName) |
| * with the name of the default certificate of the identity. If omitted, the |
| * return value is described below. (Some crypto libraries only use a callback, |
| * so onComplete is required to use these.) |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some crypto libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * @return {Name} If onComplete is omitted, return the name of the default |
| * certificate of the identity. Otherwise, if onComplete is supplied then return |
| * undefined and use onComplete as described above. |
| */ |
| IdentityManager.prototype.createIdentityAndCertificate = function |
| (identityName, params, onComplete, onError) |
| { |
| return SyncPromise.complete(onComplete, onError, |
| this.createIdentityAndCertificatePromise(identityName, params, !onComplete)); |
| }; |
| |
| /** |
| * Create an identity by creating a pair of Key-Signing-Key (KSK) for this |
| * identity and a self-signed certificate of the KSK. If a key pair or |
| * certificate for the identity already exists, use it. |
| * @deprecated Use createIdentityAndCertificate which returns the |
| * certificate name instead of the key name. You can use |
| * IdentityCertificate.certificateNameToPublicKeyName to convert the |
| * certificate name to the key name. |
| * @param {Name} identityName The name of the identity. |
| * @params {KeyParams} params The key parameters if a key needs to be generated |
| * for the identity. |
| * @return {Name} The key name of the auto-generated KSK of the identity. |
| */ |
| IdentityManager.prototype.createIdentity = function(identityName, params) |
| { |
| return IdentityCertificate.certificateNameToPublicKeyName |
| (this.createIdentityAndCertificate(identityName, params)); |
| }; |
| |
| /** |
| * Delete the identity from the public and private key storage. If the |
| * identity to be deleted is the current default system default, this will not |
| * delete the identity and will return immediately. |
| * @param {Name} identityName The name of the identity. |
| * @param {function} onComplete (optional) This calls onComplete() when the |
| * operation is complete. If omitted, do not use it. (Some database libraries |
| * only use a callback, so onComplete is required to use these.) |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| */ |
| IdentityManager.prototype.deleteIdentity = function |
| (identityName, onComplete, onError) |
| { |
| var useSync = !onComplete; |
| var thisManager = this; |
| |
| var doDelete = true; |
| |
| var mainPromise = this.identityStorage.getDefaultIdentityPromise(useSync) |
| .then(function(defaultIdentityName) { |
| if (defaultIdentityName.equals(identityName)) |
| // Don't delete the default identity! |
| doDelete = false; |
| |
| return SyncPromise.resolve(); |
| }, function(err) { |
| // There is no default identity to check. |
| return SyncPromise.resolve(); |
| }) |
| .then(function() { |
| if (!doDelete) |
| return SyncPromise.resolve(); |
| |
| var keysToDelete = []; |
| return thisManager.identityStorage.getAllKeyNamesOfIdentityPromise |
| (identityName, keysToDelete, true) |
| .then(function() { |
| return thisManager.identityStorage.getAllKeyNamesOfIdentityPromise |
| (identityName, keysToDelete, false); |
| }) |
| .then(function() { |
| return thisManager.identityStorage.deleteIdentityInfoPromise(identityName); |
| }) |
| .then(function() { |
| // Recursively loop through keysToDelete, calling deleteKeyPairPromise. |
| function deleteKeyLoop(i) { |
| if (i >= keysToDelete.length) |
| return SyncPromise.resolve(); |
| |
| return thisManager.privateKeyStorage.deleteKeyPairPromise(keysToDelete[i]) |
| .then(function() { |
| return deleteKeyLoop(i + 1); |
| }); |
| } |
| |
| return deleteKeyLoop(0); |
| }); |
| }); |
| |
| return SyncPromise.complete(onComplete, onError, mainPromise); |
| }; |
| |
| /** |
| * Set the default identity. If the identityName does not exist, then clear the |
| * default identity so that getDefaultIdentity() throws an exception. |
| * @param {Name} identityName The default identity name. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which fulfills when the default |
| * identity is set. |
| */ |
| IdentityManager.prototype.setDefaultIdentityPromise = function |
| (identityName, useSync) |
| { |
| return this.identityStorage.setDefaultIdentityPromise(identityName, useSync); |
| }; |
| |
| /** |
| * Set the default identity. If the identityName does not exist, then clear the |
| * default identity so that getDefaultIdentity() throws an exception. |
| * @param {Name} identityName The default identity name. |
| * @param {function} onComplete (optional) This calls onComplete() when complete. |
| * (Some database libraries only use a callback, so onComplete is required to |
| * use these.) |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| */ |
| IdentityManager.prototype.setDefaultIdentity = function |
| (identityName, onComplete, onError) |
| { |
| return SyncPromise.complete(onComplete, onError, |
| this.identityStorage.setDefaultIdentityPromise(identityName, !onComplete)); |
| }; |
| |
| /** |
| * Get the default identity. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which returns the Name of default |
| * identity, or a promise rejected with SecurityException if the default |
| * identity is not set. |
| */ |
| IdentityManager.prototype.getDefaultIdentityPromise = function(useSync) |
| { |
| return this.identityStorage.getDefaultIdentityPromise(useSync); |
| }; |
| |
| /** |
| * Get the default identity. |
| * @param {function} onComplete (optional) This calls onComplete(identityName) |
| * with name of the default identity. If omitted, the return value is described |
| * below. (Some database libraries only use a callback, so onComplete is required |
| * to use these.) |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * @return {Name} If onComplete is omitted, return the name of the default |
| * identity. Otherwise, if onComplete is supplied then return undefined and use |
| * onComplete as described above. |
| * @throws SecurityException if the default identity is not set. However, if |
| * onComplete and onError are defined, then if there is an exception return |
| * undefined and call onError(exception). |
| */ |
| IdentityManager.prototype.getDefaultIdentity = function(onComplete, onError) |
| { |
| return SyncPromise.complete(onComplete, onError, |
| this.identityStorage.getDefaultIdentityPromise(!onComplete)); |
| }; |
| |
| /** |
| * Get the certificate of the default identity. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which returns the requested |
| * IdentityCertificate or null if not found. |
| */ |
| IdentityManager.prototype.getDefaultCertificatePromise = function(useSync) |
| { |
| return this.identityStorage.getDefaultCertificatePromise(useSync); |
| }; |
| |
| /** |
| * Generate a pair of RSA keys for the specified identity. |
| * @param {Name} identityName The name of the identity. |
| * @param {boolean} isKsk True for generating a Key-Signing-Key (KSK), false for |
| * a Data-Signing-Key (DSK). |
| * @param {number} keySize The size of the key. |
| * @return {Name} The generated key name. |
| */ |
| IdentityManager.prototype.generateRSAKeyPair = function |
| (identityName, isKsk, keySize) |
| { |
| // For now, require sync. This method may be removed from the API. |
| return SyncPromise.getValue |
| (this.generateKeyPairPromise |
| (identityName, isKsk, new RsaKeyParams(keySize), true)); |
| }; |
| |
| /** |
| * Set a key as the default key of an identity. The identity name is inferred |
| * from keyName. |
| * @param {Name} keyName The name of the key. |
| * @param {Name} identityNameCheck (optional) The identity name to check that the |
| * keyName contains the same identity name. If an empty name, it is ignored. |
| * @param {function} onComplete (optional) This calls onComplete() when complete. |
| * (Some database libraries only use a callback, so onComplete is required to |
| * use these.) |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| */ |
| IdentityManager.prototype.setDefaultKeyForIdentity = function |
| (keyName, identityNameCheck, onComplete, onError) |
| { |
| onError = (typeof identityNameCheck === "function") ? onComplete : onError; |
| onComplete = (typeof identityNameCheck === "function") ? |
| identityNameCheck : onComplete; |
| identityNameCheck = (typeof identityNameCheck === "function" || !identityNameCheck) ? |
| new Name() : identityNameCheck; |
| |
| return SyncPromise.complete(onComplete, onError, |
| this.identityStorage.setDefaultKeyNameForIdentityPromise |
| (keyName, identityNameCheck, !onComplete)); |
| }; |
| |
| /** |
| * Get the default key for an identity. |
| * @param {Name} identityName The name of the identity. |
| * @param {function} onComplete (optional) This calls onComplete(keyName) |
| * with name of the default key. If omitted, the return value is described |
| * below. (Some database libraries only use a callback, so onComplete is required |
| * to use these.) |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * @return {Name} If onComplete is omitted, return the default key name. |
| * Otherwise, if onComplete is supplied then return undefined and use onComplete |
| * as described above. |
| * @throws SecurityException if the default key name for the identity is not set. |
| * However, if onComplete and onError are defined, then if there is an exception |
| * return undefined and call onError(exception). |
| */ |
| IdentityManager.prototype.getDefaultKeyNameForIdentity = function |
| (identityName, onComplete, onError) |
| { |
| return SyncPromise.complete(onComplete, onError, |
| this.identityStorage.getDefaultKeyNameForIdentityPromise |
| (identityName, !onComplete)); |
| }; |
| |
| /** |
| * Generate a pair of RSA keys for the specified identity and set it as default |
| * key for the identity. |
| * @param {Name} identityName The name of the identity. |
| * @param {boolean} isKsk True for generating a Key-Signing-Key (KSK), false for |
| * a Data-Signing-Key (DSK). |
| * @param {number} keySize The size of the key. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If false, this may return a SyncPromise or an async |
| * Promise. |
| * @return {Promise|SyncPromise} A promise which returns the generated key name. |
| */ |
| IdentityManager.prototype.generateRSAKeyPairAsDefaultPromise = function |
| (identityName, isKsk, keySize, useSync) |
| { |
| var newKeyName; |
| var thisManager = this; |
| return this.generateKeyPairPromise(identityName, isKsk, new RsaKeyParams(keySize)) |
| .then(function(localKeyName) { |
| newKeyName = localKeyName; |
| |
| return thisManager.identityStorage.setDefaultKeyNameForIdentityPromise |
| (newKeyName); |
| }) |
| .then(function() { |
| return SyncPromise.resolve(newKeyName); |
| }); |
| }; |
| |
| /** |
| * Generate a pair of RSA keys for the specified identity and set it as default |
| * key for the identity. |
| * @param {Name} identityName The name of the identity. |
| * @param {boolean} isKsk True for generating a Key-Signing-Key (KSK), false for |
| * a Data-Signing-Key (DSK). |
| * @param {number} keySize The size of the key. |
| * @return {Name} The generated key name. |
| */ |
| IdentityManager.prototype.generateRSAKeyPairAsDefault = function |
| (identityName, isKsk, keySize) |
| { |
| return SyncPromise.getValue |
| (this.generateRSAKeyPairAsDefaultPromise(identityName, isKsk, keySize, true)); |
| }; |
| |
| /** |
| * Get the public key with the specified name. |
| * @param {Name} keyName The name of the key. |
| * @param {function} onComplete (optional) This calls onComplete(publicKey) |
| * with PublicKey. If omitted, the return value is described below. (Some database |
| * libraries only use a callback, so onComplete is required to use these.) |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * @return {PublicKey} If onComplete is omitted, return the public key. |
| * Otherwise, if onComplete is supplied then return undefined and use onComplete |
| * as described above. |
| */ |
| IdentityManager.prototype.getPublicKey = function(keyName, onComplete, onError) |
| { |
| return SyncPromise.complete(onComplete, onError, |
| this.identityStorage.getKeyPromise(keyName, !onComplete) |
| .then(function(keyDer) { |
| return SyncPromise.resolve(new PublicKey(keyDer)); |
| })); |
| }; |
| |
| // TODO: Add two versions of createIdentityCertificate. |
| |
| /** |
| * Prepare an unsigned identity certificate. |
| * @param {Name} keyName The key name, e.g., `/{identity_name}/ksk-123456`. |
| * @param {PublicKey} publicKey (optional) The public key to sign. If ommited, |
| * use the keyName to get the public key from the identity storage. |
| * @param {Name} signingIdentity The signing identity. |
| * @param {number} notBefore See IdentityCertificate. |
| * @param {number} notAfter See IdentityCertificate. |
| * @param {Array<CertificateSubjectDescription>} subjectDescription A list of |
| * CertificateSubjectDescription. See IdentityCertificate. If null or empty, |
| * this adds a an ATTRIBUTE_NAME based on the keyName. |
| * @param {Name} certPrefix (optional) The prefix before the `KEY` component. If |
| * null or omitted, this infers the certificate name according to the relation |
| * between the signingIdentity and the subject identity. If the signingIdentity |
| * is a prefix of the subject identity, `KEY` will be inserted after the |
| * signingIdentity, otherwise `KEY` is inserted after subject identity (i.e., |
| * before `ksk-...`). |
| * @param {function} onComplete (optional) This calls onComplete(certificate) |
| * with the unsigned IdentityCertificate, or null if the inputs are invalid. If |
| * omitted, the return value is described below. (Some database libraries only |
| * use a callback, so onComplete is required to use these.) |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * @return {IdentityCertificate} If onComplete is omitted, return the the |
| * unsigned IdentityCertificate, or null if the inputs are invalid. Otherwise, |
| * if onComplete is supplied then return undefined and use onComplete as |
| * described above. |
| */ |
| IdentityManager.prototype.prepareUnsignedIdentityCertificate = function |
| (keyName, publicKey, signingIdentity, notBefore, notAfter, subjectDescription, |
| certPrefix, onComplete, onError) |
| { |
| if (!(publicKey instanceof PublicKey)) { |
| // The publicKey was omitted. Shift arguments. |
| onError = onComplete; |
| onComplete = certPrefix; |
| certPrefix = subjectDescription; |
| subjectDescription = notAfter; |
| notAfter = notBefore; |
| notBefore = signingIdentity; |
| signingIdentity = publicKey; |
| publicKey = null; |
| } |
| |
| // certPrefix may be omitted or null, so check for it and the following args. |
| var arg7 = certPrefix; |
| var arg8 = onComplete; |
| var arg9 = onError; |
| if (arg7 instanceof Name) |
| certPrefix = arg7; |
| else |
| certPrefix = null; |
| |
| if (typeof arg7 === 'function') { |
| onComplete = arg7; |
| onError = arg8; |
| } |
| else if (typeof arg8 === 'function') { |
| onComplete = arg8; |
| onError = arg9; |
| } |
| else { |
| onComplete = null; |
| onError = null; |
| } |
| |
| var promise; |
| if (publicKey == null) |
| promise = this.prepareUnsignedIdentityCertificatePromise |
| (keyName, signingIdentity, notBefore, notAfter, subjectDescription, |
| certPrefix, !onComplete); |
| else |
| promise = this.prepareUnsignedIdentityCertificatePromise |
| (keyName, publicKey, signingIdentity, notBefore, notAfter, |
| subjectDescription, certPrefix, !onComplete); |
| return SyncPromise.complete(onComplete, onError, promise); |
| }; |
| |
| /** |
| * Prepare an unsigned identity certificate. |
| * @param {Name} keyName The key name, e.g., `/{identity_name}/ksk-123456`. |
| * @param {PublicKey} publicKey (optional) The public key to sign. If ommited, |
| * use the keyName to get the public key from the identity storage. |
| * @param {Name} signingIdentity The signing identity. |
| * @param {number} notBefore See IdentityCertificate. |
| * @param {number} notAfter See IdentityCertificate. |
| * @param {Array<CertificateSubjectDescription>} subjectDescription A list of |
| * CertificateSubjectDescription. See IdentityCertificate. If null or empty, |
| * this adds a an ATTRIBUTE_NAME based on the keyName. |
| * @param {Name} certPrefix (optional) The prefix before the `KEY` component. If |
| * null or omitted, this infers the certificate name according to the relation |
| * between the signingIdentity and the subject identity. If the signingIdentity |
| * is a prefix of the subject identity, `KEY` will be inserted after the |
| * signingIdentity, otherwise `KEY` is inserted after subject identity (i.e., |
| * before `ksk-...`). |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns the unsigned |
| * IdentityCertificate, or that returns null if the inputs are invalid. |
| */ |
| IdentityManager.prototype.prepareUnsignedIdentityCertificatePromise = function |
| (keyName, publicKey, signingIdentity, notBefore, notAfter, subjectDescription, |
| certPrefix, useSync) |
| { |
| if (!(publicKey instanceof PublicKey)) { |
| // The publicKey was omitted. Shift arguments. |
| useSync = certPrefix; |
| certPrefix = subjectDescription; |
| subjectDescription = notAfter; |
| notAfter = notBefore; |
| notBefore = signingIdentity; |
| signingIdentity = publicKey; |
| publicKey = null; |
| } |
| |
| // certPrefix may be omitted or null, so check for it and the following arg. |
| var arg7 = certPrefix; |
| var arg8 = useSync; |
| if (arg7 instanceof Name) |
| certPrefix = arg7; |
| else |
| certPrefix = null; |
| |
| if (typeof arg7 === 'boolean') |
| useSync = arg7; |
| else if (typeof arg8 === 'boolean') |
| useSync = arg8; |
| else |
| useSync = false; |
| |
| var promise; |
| if (publicKey == null) { |
| promise = this.identityStorage.getKeyPromise(keyName, useSync) |
| .then(function(keyDer) { |
| publicKey = new PublicKey(keyDer); |
| return SyncPromise.resolve(); |
| }); |
| } |
| else |
| promise = SyncPromise.resolve(); |
| |
| return promise |
| .then(function() { |
| return SyncPromise.resolve |
| (IdentityManager.prepareUnsignedIdentityCertificateHelper_ |
| (keyName, publicKey, signingIdentity, notBefore, notAfter, |
| subjectDescription, certPrefix)); |
| }); |
| }; |
| |
| /** |
| * A helper for prepareUnsignedIdentityCertificatePromise where the publicKey |
| * is known. |
| */ |
| IdentityManager.prepareUnsignedIdentityCertificateHelper_ = function |
| (keyName, publicKey, signingIdentity, notBefore, notAfter, subjectDescription, |
| certPrefix) |
| { |
| if (keyName.size() < 1) |
| return null; |
| |
| var tempKeyIdPrefix = keyName.get(-1).toEscapedString(); |
| if (tempKeyIdPrefix.length < 4) |
| return null; |
| keyIdPrefix = tempKeyIdPrefix.substr(0, 4); |
| if (keyIdPrefix != "ksk-" && keyIdPrefix != "dsk-") |
| return null; |
| |
| var certificate = new IdentityCertificate(); |
| var certName = new Name(); |
| |
| if (certPrefix == null) { |
| // No certificate prefix hint, so infer the prefix. |
| if (signingIdentity.match(keyName)) |
| certName.append(signingIdentity) |
| .append("KEY") |
| .append(keyName.getSubName(signingIdentity.size())) |
| .append("ID-CERT") |
| .appendVersion(new Date().getTime()); |
| else |
| certName.append(keyName.getPrefix(-1)) |
| .append("KEY") |
| .append(keyName.get(-1)) |
| .append("ID-CERT") |
| .appendVersion(new Date().getTime()); |
| } |
| else { |
| // A cert prefix hint is supplied, so determine the cert name. |
| if (certPrefix.match(keyName) && !certPrefix.equals(keyName)) |
| certName.append(certPrefix) |
| .append("KEY") |
| .append(keyName.getSubName(certPrefix.size())) |
| .append("ID-CERT") |
| .appendVersion(new Date().getTime()); |
| else |
| return null; |
| } |
| |
| certificate.setName(certName); |
| certificate.setNotBefore(notBefore); |
| certificate.setNotAfter(notAfter); |
| certificate.setPublicKeyInfo(publicKey); |
| |
| if (subjectDescription == null || subjectDescription.length === 0) |
| certificate.addSubjectDescription(new CertificateSubjectDescription |
| ("2.5.4.41", keyName.getPrefix(-1).toUri())); |
| else { |
| for (var i = 0; i < subjectDescription.length; ++i) |
| certificate.addSubjectDescription(subjectDescription[i]); |
| } |
| |
| try { |
| certificate.encode(); |
| } catch (ex) { |
| throw SecurityException(new Error("DerEncodingException: " + ex)); |
| } |
| |
| return certificate; |
| }; |
| |
| /** |
| * Add a certificate into the public key identity storage. |
| * @param {IdentityCertificate} certificate The certificate to to added. This |
| * makes a copy of the certificate. |
| * @param {function} onComplete (optional) This calls onComplete() when complete. |
| * (Some database libraries only use a callback, so onComplete is required to |
| * use these.) |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| */ |
| IdentityManager.prototype.addCertificate = function |
| (certificate, onComplete, onError) |
| { |
| return SyncPromise.complete(onComplete, onError, |
| this.identityStorage.addCertificatePromise(certificate, !onComplete)); |
| }; |
| |
| /** |
| * Set the certificate as the default for its corresponding key. |
| * @param {IdentityCertificate} certificate The certificate. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If false, this may return a SyncPromise or an async |
| * Promise. |
| * @return {Promise|SyncPromise} A promise which fulfills when the default |
| * certificate is set. |
| */ |
| IdentityManager.prototype.setDefaultCertificateForKeyPromise = function |
| (certificate, useSync) |
| { |
| var thisManager = this; |
| |
| var keyName = certificate.getPublicKeyName(); |
| return this.identityStorage.doesKeyExistPromise(keyName, useSync) |
| .then(function(exists) { |
| if (!exists) |
| throw new SecurityException(new Error |
| ("No corresponding Key record for certificate!")); |
| |
| return thisManager.identityStorage.setDefaultCertificateNameForKeyPromise |
| (keyName, certificate.getName(), useSync); |
| }); |
| }; |
| |
| /** |
| * Set the certificate as the default for its corresponding key. |
| * @param {IdentityCertificate} certificate The certificate. |
| * @param {function} onComplete (optional) This calls onComplete() when complete. |
| * (Some database libraries only use a callback, so onComplete is required to |
| * use these.) |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| */ |
| IdentityManager.prototype.setDefaultCertificateForKey = function |
| (certificate, onComplete, onError) |
| { |
| return SyncPromise.complete(onComplete, onError, |
| this.setDefaultCertificateForKeyPromise(certificate, !onComplete)); |
| }; |
| |
| /** |
| * Add a certificate into the public key identity storage and set the |
| * certificate as the default for its corresponding identity. |
| * @param {IdentityCertificate} certificate The certificate to be added. This |
| * makes a copy of the certificate. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If false, this may return a SyncPromise or an async |
| * Promise. |
| * @return {Promise|SyncPromise} A promise which fulfills when the certificate |
| * is added. |
| */ |
| IdentityManager.prototype.addCertificateAsIdentityDefaultPromise = function |
| (certificate, useSync) |
| { |
| var thisManager = this; |
| return this.identityStorage.addCertificatePromise(certificate, useSync) |
| .then(function() { |
| var keyName = certificate.getPublicKeyName(); |
| return thisManager.identityStorage.setDefaultKeyNameForIdentityPromise |
| (keyName, useSync); |
| }) |
| .then(function() { |
| return thisManager.setDefaultCertificateForKeyPromise(certificate, useSync); |
| }); |
| }; |
| |
| /** |
| * Add a certificate into the public key identity storage and set the |
| * certificate as the default of its corresponding key. |
| * @param {IdentityCertificate} certificate The certificate to be added. This |
| * makes a copy of the certificate. |
| * @param {function} onComplete (optional) This calls onComplete() when complete. |
| * (Some database libraries only use a callback, so onComplete is required to use |
| * these.) |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| */ |
| IdentityManager.prototype.addCertificateAsDefault = function |
| (certificate, onComplete, onError) |
| { |
| var useSync = !onComplete; |
| var thisManager = this; |
| |
| return SyncPromise.complete(onComplete, onError, |
| this.identityStorage.addCertificatePromise(certificate, useSync) |
| .then(function() { |
| return thisManager.setDefaultCertificateForKeyPromise(certificate, useSync); |
| })); |
| }; |
| |
| /** |
| * Get a certificate which is still valid with the specified name. |
| * @param {Name} certificateName The name of the requested certificate. |
| * @param {function} onComplete (optional) This calls onComplete(certificate) |
| * with the requested IdentityCertificate. If omitted, the return value is |
| * described below. (Some database libraries only use a callback, so onComplete |
| * is required to use these.) |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * @return {IdentityCertificate} If onComplete is omitted, return the requested |
| * certificate. Otherwise, if onComplete is supplied then return undefined and |
| * use onComplete as described above. |
| */ |
| IdentityManager.prototype.getCertificate = function |
| (certificateName, onComplete, onError) |
| { |
| return SyncPromise.complete(onComplete, onError, |
| this.identityStorage.getCertificatePromise |
| (certificateName, false, !onComplete)); |
| }; |
| |
| /** |
| * Get the default certificate name for the specified identity. |
| * @param {Name} identityName The identity name. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which returns the default certificate |
| * Name, or a promise rejected with SecurityException if the default key name |
| * for the identity is not set or the default certificate name for the key name |
| * is not set. |
| */ |
| IdentityManager.prototype.getDefaultCertificateNameForIdentityPromise = function |
| (identityName, useSync) |
| { |
| return this.identityStorage.getDefaultCertificateNameForIdentityPromise |
| (identityName, useSync); |
| } |
| |
| /** |
| * Get the default certificate name for the specified identity, which will be |
| * used when signing is performed based on identity. |
| * @param {Name} identityName The name of the specified identity. |
| * @param {function} onComplete (optional) This calls onComplete(certificateName) |
| * with name of the default certificate. If omitted, the return value is described |
| * below. (Some database libraries only use a callback, so onComplete is required |
| * to use these.) |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * @return {Name} If onComplete is omitted, return the default certificate name. |
| * Otherwise, if onComplete is supplied then return undefined and use |
| * onComplete as described above. |
| * @throws SecurityException if the default key name for the identity is not |
| * set or the default certificate name for the key name is not set. However, if |
| * onComplete and onError are defined, then if there is an exception return |
| * undefined and call onError(exception). |
| */ |
| IdentityManager.prototype.getDefaultCertificateNameForIdentity = function |
| (identityName, onComplete, onError) |
| { |
| return SyncPromise.complete(onComplete, onError, |
| this.identityStorage.getDefaultCertificateNameForIdentityPromise |
| (identityName, !onComplete)); |
| }; |
| |
| /** |
| * Get the default certificate name of the default identity, which will be used |
| * when signing is based on identity and the identity is not specified. |
| * @param {function} onComplete (optional) This calls onComplete(certificateName) |
| * with name of the default certificate. If omitted, the return value is described |
| * below. (Some database libraries only use a callback, so onComplete is required |
| * to use these.) |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * @return {Name} If onComplete is omitted, return the default certificate name. |
| * Otherwise, if onComplete is supplied then return undefined and use |
| * onComplete as described above. |
| * @throws SecurityException if the default identity is not set or the default |
| * key name for the identity is not set or the default certificate name for |
| * the key name is not set. However, if onComplete and onError are defined, then |
| * if there is an exception return undefined and call onError(exception). |
| */ |
| IdentityManager.prototype.getDefaultCertificateName = function |
| (onComplete, onError) |
| { |
| var useSync = !onComplete; |
| var thisManager = this; |
| |
| return SyncPromise.complete(onComplete, onError, |
| this.identityStorage.getDefaultIdentityPromise(useSync) |
| .then(function(identityName) { |
| return thisManager.identityStorage.getDefaultCertificateNameForIdentityPromise |
| (identityName, useSync); |
| })); |
| }; |
| |
| /** |
| * Append all the identity names to the nameList. |
| * @param {Array<Name>} nameList Append result names to nameList. |
| * @param {boolean} isDefault If true, add only the default identity name. If |
| * false, add only the non-default identity names. |
| * @param {function} onComplete (optional) This calls onComplete() when finished |
| * adding to nameList. If omitted, this returns when complete. (Some database |
| * libraries only use a callback, so onComplete is required to use these.) |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * @return {void} If onComplete is omitted, return when complete. Otherwise, if |
| * onComplete is supplied then return undefined and use onComplete as described |
| * above. |
| */ |
| IdentityManager.prototype.getAllIdentities = function |
| (nameList, isDefault, onComplete, onError) |
| { |
| return SyncPromise.complete(onComplete, onError, |
| this.identityStorage.getAllIdentitiesPromise |
| (nameList, isDefault, !onComplete)); |
| }; |
| |
| /** |
| * Append all the key names of a particular identity to the nameList. |
| * @param {Name} identityName The identity name to search for. |
| * @param {Array<Name>} nameList Append result names to nameList. |
| * @param {boolean} isDefault If true, add only the default key name. If false, |
| * add only the non-default key names. |
| * @param {function} onComplete (optional) This calls onComplete() when finished |
| * adding to nameList. If omitted, this returns when complete. (Some database |
| * libraries only use a callback, so onComplete is required to use these.) |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * @return {void} If onComplete is omitted, return when complete. Otherwise, if |
| * onComplete is supplied then return undefined and use onComplete as described |
| * above. |
| */ |
| IdentityManager.prototype.getAllKeyNamesOfIdentity = function |
| (identityName, nameList, isDefault, onComplete, onError) |
| { |
| return SyncPromise.complete(onComplete, onError, |
| this.identityStorage.getAllKeyNamesOfIdentityPromise |
| (identityName, nameList, isDefault, !onComplete)); |
| }; |
| |
| /** |
| * Append all the certificate names of a particular key name to the nameList. |
| * @param {Name} keyName The key name to search for. |
| * @param {Array<Name>} nameList Append result names to nameList. |
| * @param {boolean} isDefault If true, add only the default certificate name. If |
| * false, add only the non-default certificate names. |
| * @param {function} onComplete (optional) This calls onComplete() when finished |
| * adding to nameList. If omitted, this returns when complete. (Some database |
| * libraries only use a callback, so onComplete is required to use these.) |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * @return {void} If onComplete is omitted, return when complete. Otherwise, if |
| * onComplete is supplied then return undefined and use onComplete as described |
| * above. |
| */ |
| IdentityManager.prototype.getAllCertificateNamesOfKey = function |
| (keyName, nameList, isDefault, onComplete, onError) |
| { |
| return SyncPromise.complete(onComplete, onError, |
| this.identityStorage.getAllCertificateNamesOfKeyPromise |
| (keyName, nameList, isDefault, !onComplete)); |
| }; |
| |
| /** |
| * Sign the Data packet or byte array data based on the certificate name. |
| * @param {Data|Buffer} target If this is a Data object, wire encode for signing, |
| * update its signature and key locator field and wireEncoding. If it is a |
| * Buffer, sign it to produce a Signature object. |
| * @param {Name} certificateName The Name identifying the certificate which |
| * identifies the signing key. |
| * @param {WireFormat} (optional) The WireFormat for calling encodeData, or |
| * WireFormat.getDefaultWireFormat() if omitted. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns the generated Signature |
| * object (if target is a Buffer) or the target (if target is Data). |
| */ |
| IdentityManager.prototype.signByCertificatePromise = function |
| (target, certificateName, wireFormat, useSync) |
| { |
| useSync = (typeof wireFormat === "boolean") ? wireFormat : useSync; |
| wireFormat = (typeof wireFormat === "boolean" || !wireFormat) ? WireFormat.getDefaultWireFormat() : wireFormat; |
| |
| var keyName = IdentityManager.certificateNameToPublicKeyName(certificateName); |
| |
| var thisManager = this; |
| if (target instanceof Data) { |
| var data = target; |
| var digestAlgorithm = [0]; |
| |
| return this.makeSignatureByCertificatePromise |
| (certificateName, digestAlgorithm, useSync) |
| .then(function(signature) { |
| data.setSignature(signature); |
| // Encode once to get the signed portion. |
| var encoding = data.wireEncode(wireFormat); |
| |
| return thisManager.privateKeyStorage.signPromise |
| (encoding.signedBuf(), keyName, digestAlgorithm[0], useSync); |
| }) |
| .then(function(signatureValue) { |
| data.getSignature().setSignature(signatureValue); |
| // Encode again to include the signature. |
| data.wireEncode(wireFormat); |
| |
| return SyncPromise.resolve(data); |
| }); |
| } |
| else { |
| var digestAlgorithm = [0]; |
| return this.makeSignatureByCertificatePromise |
| (certificateName, digestAlgorithm, useSync) |
| .then(function(signature) { |
| return thisManager.privateKeyStorage.signPromise |
| (target, keyName, digestAlgorithm[0], useSync); |
| }) |
| .then(function (signatureValue) { |
| signature.setSignature(signatureValue); |
| return SyncPromise.resolve(signature); |
| }); |
| } |
| }; |
| |
| /** |
| * Sign the Data packet or byte array data based on the certificate name. |
| * @param {Data|Buffer} target If this is a Data object, wire encode for signing, |
| * update its signature and key locator field and wireEncoding. If it is a |
| * Buffer, sign it to produce a Signature object. |
| * @param {Name} certificateName The Name identifying the certificate which |
| * identifies the signing key. |
| * @param {WireFormat} (optional) The WireFormat for calling encodeData, or |
| * WireFormat.getDefaultWireFormat() if omitted. |
| * @param {function} onComplete (optional) If target is a Data object, this calls |
| * onComplete(data) with the supplied Data object which has been modified to set |
| * its signature. If target is a Buffer, this calls onComplete(signature) where |
| * signature is the produced Signature object. If omitted, the return value is |
| * described below. (Some crypto libraries only use a callback, so onComplete is |
| * required to use these.) |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some crypto libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * @return {Signature} If onComplete is omitted, return the generated Signature |
| * object (if target is a Buffer) or the target (if target is Data). Otherwise, |
| * if onComplete is supplied then return undefined and use onComplete as described |
| * above. |
| */ |
| IdentityManager.prototype.signByCertificate = function |
| (target, certificateName, wireFormat, onComplete, onError) |
| { |
| onError = (typeof wireFormat === "function") ? onComplete : onError; |
| onComplete = (typeof wireFormat === "function") ? wireFormat : onComplete; |
| wireFormat = (typeof wireFormat === "function" || !wireFormat) ? WireFormat.getDefaultWireFormat() : wireFormat; |
| |
| return SyncPromise.complete(onComplete, onError, |
| this.signByCertificatePromise |
| (target, certificateName, wireFormat, !onComplete)); |
| }; |
| |
| /** |
| * Append a SignatureInfo to the Interest name, sign the name components and |
| * append a final name component with the signature bits. |
| * @param {Interest} interest The Interest object to be signed. This appends |
| * name components of SignatureInfo and the signature bits. |
| * @param {Name} certificateName The certificate name of the key to use for |
| * signing. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * the input. If omitted, use WireFormat getDefaultWireFormat(). |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns the supplied Interest. |
| */ |
| IdentityManager.prototype.signInterestByCertificatePromise = function |
| (interest, certificateName, wireFormat, useSync) |
| { |
| useSync = (typeof wireFormat === "boolean") ? wireFormat : useSync; |
| wireFormat = (typeof wireFormat === "boolean" || !wireFormat) ? WireFormat.getDefaultWireFormat() : wireFormat; |
| |
| var thisManager = this; |
| var signature; |
| var digestAlgorithm = [0]; |
| return this.makeSignatureByCertificatePromise |
| (certificateName, digestAlgorithm, useSync) |
| .then(function(localSignature) { |
| signature = localSignature; |
| // Append the encoded SignatureInfo. |
| interest.getName().append(wireFormat.encodeSignatureInfo(signature)); |
| |
| // Append an empty signature so that the "signedPortion" is correct. |
| interest.getName().append(new Name.Component()); |
| // Encode once to get the signed portion. |
| var encoding = interest.wireEncode(wireFormat); |
| var keyName = IdentityManager.certificateNameToPublicKeyName |
| (certificateName); |
| |
| return thisManager.privateKeyStorage.signPromise |
| (encoding.signedBuf(), keyName, digestAlgorithm[0], useSync); |
| }) |
| .then(function(signatureValue) { |
| signature.setSignature(signatureValue); |
| |
| // Remove the empty signature and append the real one. |
| interest.setName(interest.getName().getPrefix(-1).append |
| (wireFormat.encodeSignatureValue(signature))); |
| return SyncPromise.resolve(interest); |
| }); |
| }; |
| |
| /** |
| * Append a SignatureInfo to the Interest name, sign the name components and |
| * append a final name component with the signature bits. |
| * @param {Interest} interest The Interest object to be signed. This appends |
| * name components of SignatureInfo and the signature bits. |
| * @param {Name} certificateName The certificate name of the key to use for |
| * signing. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * the input. If omitted, use WireFormat getDefaultWireFormat(). |
| * @param {function} onComplete (optional) This calls onComplete(interest) with |
| * the supplied Interest object which has been modified to set its signature. If |
| * omitted, then return when the interest has been signed. (Some crypto |
| * libraries only use a callback, so onComplete is required to use these.) |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some crypto libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * @return {Signature} If onComplete is omitted, return the interest. Otherwise, |
| * if onComplete is supplied then return undefined and use onComplete as |
| * described above. |
| */ |
| IdentityManager.prototype.signInterestByCertificate = function |
| (interest, certificateName, wireFormat, onComplete, onError) |
| { |
| onError = (typeof wireFormat === "function") ? onComplete : onError; |
| onComplete = (typeof wireFormat === "function") ? wireFormat : onComplete; |
| wireFormat = (typeof wireFormat === "function" || !wireFormat) ? WireFormat.getDefaultWireFormat() : wireFormat; |
| |
| return SyncPromise.complete(onComplete, onError, |
| this.signInterestByCertificatePromise |
| (interest, certificateName, wireFormat, !onComplete)); |
| }; |
| |
| /** |
| * Wire encode the Data object, digest it and set its SignatureInfo to a |
| * DigestSha256. |
| * @param {Data} data The Data object to be signed. This updates its signature |
| * and wireEncoding. |
| * @param {WireFormat} (optional) The WireFormat for calling encodeData, or |
| * WireFormat.getDefaultWireFormat() if omitted. |
| */ |
| IdentityManager.prototype.signWithSha256 = function(data, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| |
| data.setSignature(new DigestSha256Signature()); |
| // Encode once to get the signed portion. |
| var encoding = data.wireEncode(wireFormat); |
| |
| // Digest and set the signature. |
| var hash = Crypto.createHash('sha256'); |
| hash.update(encoding.signedBuf()); |
| data.getSignature().setSignature(new Blob(hash.digest(), false)); |
| |
| // Encode again to include the signature. |
| data.wireEncode(wireFormat); |
| }; |
| |
| /** |
| * Append a SignatureInfo for DigestSha256 to the Interest name, digest the |
| * name components and append a final name component with the signature bits |
| * (which is the digest). |
| * @param {Interest} interest The Interest object to be signed. This appends |
| * name components of SignatureInfo and the signature bits. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * the input. If omitted, use WireFormat getDefaultWireFormat(). |
| */ |
| IdentityManager.prototype.signInterestWithSha256 = function(interest, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| |
| var signature = new DigestSha256Signature(); |
| |
| // Append the encoded SignatureInfo. |
| interest.getName().append(wireFormat.encodeSignatureInfo(signature)); |
| |
| // Append an empty signature so that the "signedPortion" is correct. |
| interest.getName().append(new Name.Component()); |
| // Encode once to get the signed portion. |
| var encoding = interest.wireEncode(wireFormat); |
| |
| // Digest and set the signature. |
| var hash = Crypto.createHash('sha256'); |
| hash.update(encoding.signedBuf()); |
| signature.setSignature(new Blob(hash.digest(), false)); |
| |
| // Remove the empty signature and append the real one. |
| interest.setName(interest.getName().getPrefix(-1).append |
| (wireFormat.encodeSignatureValue(signature))); |
| }; |
| |
| /** |
| * Generate a self-signed certificate for a public key. |
| * @param {Name} keyName The name of the public key. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If false, this may return a SyncPromise or an async |
| * Promise. |
| * @return {Promise|SyncPromise} A promise which returns the generated |
| * IdentityCertificate. |
| */ |
| IdentityManager.prototype.selfSignPromise = function(keyName, useSync) |
| { |
| var certificate = new IdentityCertificate(); |
| |
| var thisManager = this; |
| return this.identityStorage.getKeyPromise(keyName, useSync) |
| .then(function(keyBlob) { |
| var publicKey = new PublicKey(keyBlob); |
| |
| var notBefore = new Date().getTime(); |
| var notAfter = notBefore + 2 * 365 * 24 * 3600 * 1000; // about 2 years |
| |
| certificate.setNotBefore(notBefore); |
| certificate.setNotAfter(notAfter); |
| |
| var certificateName = keyName.getPrefix(-1).append("KEY").append |
| (keyName.get(-1)).append("ID-CERT").appendVersion(certificate.getNotBefore()); |
| certificate.setName(certificateName); |
| |
| certificate.setPublicKeyInfo(publicKey); |
| certificate.addSubjectDescription(new CertificateSubjectDescription |
| ("2.5.4.41", keyName.toUri())); |
| certificate.encode(); |
| |
| return thisManager.signByCertificatePromise |
| (certificate, certificate.getName(), useSync); |
| }) |
| }; |
| |
| /** |
| * Generate a self-signed certificate for a public key. |
| * @param {Name} keyName The name of the public key. |
| * @param {function} onComplete (optional) This calls onComplete(certificate) |
| * with the the generated IdentityCertificate. If omitted, the return value is |
| * described below. (Some crypto libraries only use a callback, so onComplete is |
| * required to use these.) |
| * @return {IdentityCertificate} If onComplete is omitted, return the |
| * generated certificate. Otherwise, if onComplete is supplied then return |
| * undefined and use onComplete as described above. |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some crypto libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| */ |
| IdentityManager.prototype.selfSign = function(keyName, onComplete, onError) |
| { |
| return SyncPromise.complete(onComplete, onError, |
| this.selfSignPromise(keyName, !onComplete)); |
| }; |
| |
| /** |
| * Get the public key name from the full certificate name. |
| * |
| * @param {Name} certificateName The full certificate name. |
| * @return {Name} The related public key name. |
| * TODO: Move this to IdentityCertificate |
| */ |
| IdentityManager.certificateNameToPublicKeyName = function(certificateName) |
| { |
| var i = certificateName.size() - 1; |
| var idString = "ID-CERT"; |
| while (i >= 0) { |
| if (certificateName.get(i).toEscapedString() == idString) |
| break; |
| --i; |
| } |
| |
| var tmpName = certificateName.getSubName(0, i); |
| var keyString = "KEY"; |
| i = 0; |
| while (i < tmpName.size()) { |
| if (tmpName.get(i).toEscapedString() == keyString) |
| break; |
| ++i; |
| } |
| |
| return tmpName.getSubName(0, i).append(tmpName.getSubName |
| (i + 1, tmpName.size() - i - 1)); |
| }; |
| |
| /** |
| * Return a new Signature object based on the signature algorithm of the public |
| * key with keyName (derived from certificateName). |
| * @param {Name} certificateName The certificate name. |
| * @param {Array} digestAlgorithm Set digestAlgorithm[0] to the signature |
| * algorithm's digest algorithm, e.g. DigestAlgorithm.SHA256. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If false, this may return a SyncPromise or an async |
| * Promise. |
| * @return {Promise|SyncPromise} A promise which returns a new object of the |
| * correct subclass of Signature. |
| */ |
| IdentityManager.prototype.makeSignatureByCertificatePromise = function |
| (certificateName, digestAlgorithm, useSync) |
| { |
| var keyName = IdentityManager.certificateNameToPublicKeyName(certificateName); |
| return this.privateKeyStorage.getPublicKeyPromise(keyName, useSync) |
| .then(function(publicKey) { |
| var keyType = publicKey.getKeyType(); |
| |
| var signature = null; |
| if (keyType == KeyType.RSA) { |
| signature = new Sha256WithRsaSignature(); |
| digestAlgorithm[0] = DigestAlgorithm.SHA256; |
| |
| signature.getKeyLocator().setType(KeyLocatorType.KEYNAME); |
| signature.getKeyLocator().setKeyName(certificateName.getPrefix(-1)); |
| } |
| else if (keyType == KeyType.ECDSA) { |
| signature = new Sha256WithEcdsaSignature(); |
| digestAlgorithm[0] = DigestAlgorithm.SHA256; |
| |
| signature.getKeyLocator().setType(KeyLocatorType.KEYNAME); |
| signature.getKeyLocator().setKeyName(certificateName.getPrefix(-1)); |
| } |
| else |
| throw new SecurityException(new Error("Key type is not recognized")); |
| |
| return SyncPromise.resolve(signature); |
| }); |
| }; |
| |
| /** |
| * A private method to generate a pair of keys for the specified identity. |
| * @param {Name} identityName The name of the identity. |
| * @param {boolean} isKsk true for generating a Key-Signing-Key (KSK), false for |
| * a Data-Signing-Key (DSK). |
| * @param {KeyParams} params The parameters of the key. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If false, this may return a SyncPromise or an async |
| * Promise. |
| * @return {Promise|SyncPromise} A promise which returns the generated key name. |
| */ |
| IdentityManager.prototype.generateKeyPairPromise = function |
| (identityName, isKsk, params, useSync) |
| { |
| var keyName; |
| var thisManager = this; |
| return this.identityStorage.getNewKeyNamePromise(identityName, isKsk, useSync) |
| .then(function(localKeyName) { |
| keyName = localKeyName; |
| return thisManager.privateKeyStorage.generateKeyPairPromise |
| (keyName, params, useSync); |
| }) |
| .then(function() { |
| return thisManager.privateKeyStorage.getPublicKeyPromise |
| (keyName, useSync); |
| }) |
| .then(function(publicKey) { |
| return thisManager.identityStorage.addKeyPromise |
| (keyName, params.getKeyType(), publicKey.getKeyDer()); |
| }) |
| .then(function() { |
| return SyncPromise.resolve(keyName); |
| }); |
| }; |
| |
| /** |
| * Get the IdentityStorage from the pib value in the configuration file if |
| * supplied. Otherwise, get the default for this platform. |
| * @param {ConfigFile} config The configuration file to check. |
| * @param {function} initialCheckPromise This is passed to the |
| * BasicIdentityStorage constructor. See it for details. |
| * @return {IdentityStorage} A new IdentityStorage. |
| */ |
| IdentityManager.getDefaultIdentityStorage_ = function(config, initialCheckPromise) |
| { |
| // Assume we are in Node.js. |
| var pibLocator = config.get("pib", ""); |
| |
| if (pibLocator !== "") { |
| // Don't support non-default locations for now. |
| if (pibLocator !== "pib-sqlite3") |
| throw new SecurityException(new Error |
| ("Invalid config file pib value: " + pibLocator)); |
| } |
| |
| return new BasicIdentityStorage(initialCheckPromise); |
| }; |
| |
| /** |
| * Get the PrivateKeyStorage from the tpm value in the configuration file if |
| * supplied. Otherwise, get the default for this platform. |
| * @param {ConfigFile} config The configuration file to check. |
| * @param {Array<string>} canonicalTpmLocator Set canonicalTpmLocator[0] to the |
| * canonical value including the colon, * e.g. "tpm-file:". |
| * @return A new PrivateKeyStorage. |
| */ |
| IdentityManager.getDefaultPrivateKeyStorage_ = function |
| (config, canonicalTpmLocator) |
| { |
| var tpmLocator = config.get("tpm", ""); |
| |
| if (tpmLocator === "") { |
| // Assume we are in Node.js, so check the system. |
| if (process.platform === "darwin") { |
| canonicalTpmLocator[0] = "tpm-osxkeychain:"; |
| throw new SecurityException(new Error |
| ("IdentityManager: OS X key chain storage is not yet implemented. You must supply a privateKeyStorage.")); |
| } |
| else { |
| canonicalTpmLocator[0] = "tpm-file:"; |
| return new FilePrivateKeyStorage(); |
| } |
| } |
| else if (tpmLocator === "tpm-osxkeychain") { |
| canonicalTpmLocator[0] = "tpm-osxkeychain:"; |
| throw new SecurityException(new Error |
| ("IdentityManager: tpm-osxkeychain is not yet implemented.")); |
| } |
| else if (tpmLocator === "tpm-file") { |
| canonicalTpmLocator[0] = "tpm-file:"; |
| return new FilePrivateKeyStorage(); |
| } |
| else |
| throw new SecurityException(new Error |
| ("Invalid config file tpm value: " + tpmLocator)); |
| }; |
| |
| /** |
| * Check that identityStorage.getTpmLocatorPromise() (if defined) matches the |
| * canonicalTpmLocator. This has to be an async Promise because it calls async |
| * getTpmLocatorPromise. |
| * @param canonicalTpmLocator The canonical locator from |
| * getDefaultPrivateKeyStorage(). |
| * @return {Promise} A promise which resolves if canonicalTpmLocator is OK, or a |
| * promise rejected with SecurityException if the private key storage does not |
| * match. |
| */ |
| IdentityManager.prototype.checkTpmPromise_ = function(canonicalTpmLocator) |
| { |
| return this.identityStorage.getTpmLocatorPromise() |
| .then(function(tpmLocator) { |
| // Just check. If a PIB reset is required, expect ndn-cxx/NFD to do it. |
| if (tpmLocator !== "" && tpmLocator !== canonicalTpmLocator) |
| return Promise.reject(new SecurityException(new Error |
| ("The TPM locator supplied does not match the TPM locator in the PIB: " + |
| tpmLocator + " != " + canonicalTpmLocator))); |
| else |
| return Promise.resolve(); |
| }, function(err) { |
| // The TPM locator is not set in the PIB yet. |
| return Promise.resolve(); |
| }); |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * From ndn-cxx security by Yingdi Yu <yingdi@cs.ucla.edu>. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * A ValidationRequest is used to return information from |
| * PolicyManager.checkVerificationPolicy. |
| * |
| * Create a new ValidationRequest with the given values. |
| * @param {Interest} interest An interest for fetching more data. |
| * @param {function} onVerified If the signature is verified, this calls |
| * onVerified(data). |
| * @param {function} onValidationFailed If the signature check fails, this calls |
| * onValidationFailed(data, reason). |
| * @param {number} retry The number of retrials when there is an interest timeout. |
| * @param {number} stepCount The number of verification steps that have been |
| * done, used to track the verification progress. |
| * @constructor |
| */ |
| var ValidationRequest = function ValidationRequest |
| (interest, onVerified, onValidationFailed, retry, stepCount) |
| { |
| this.interest = interest; |
| this.onVerified = onVerified; |
| this.onValidationFailed = onValidationFailed; |
| this.retry = retry; |
| this.stepCount = stepCount; |
| }; |
| |
| exports.ValidationRequest = ValidationRequest; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * From ndn-cxx security by Yingdi Yu <yingdi@cs.ucla.edu>. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| // Use capitalized Crypto to not clash with the browser's crypto.subtle. |
| /** @ignore */ |
| var Crypto = require('../../crypto.js'); /** @ignore */ |
| var Blob = require('../../util/blob.js').Blob; /** @ignore */ |
| var DataUtils = require('../../encoding/data-utils.js').DataUtils; /** @ignore */ |
| var SecurityException = require('../security-exception.js').SecurityException; /** @ignore */ |
| var DigestSha256Signature = require('../../digest-sha256-signature.js').DigestSha256Signature; /** @ignore */ |
| var Sha256WithRsaSignature = require('../../sha256-with-rsa-signature.js').Sha256WithRsaSignature; /** @ignore */ |
| var Sha256WithEcdsaSignature = require('../../sha256-with-ecdsa-signature.js').Sha256WithEcdsaSignature; /** @ignore */ |
| var UseSubtleCrypto = require("../../use-subtle-crypto-node.js").UseSubtleCrypto; |
| |
| /** |
| * A PolicyManager is an abstract base class to represent the policy for |
| * verifying data packets. You must create an object of a subclass. |
| * @constructor |
| */ |
| var PolicyManager = function PolicyManager() |
| { |
| }; |
| |
| exports.PolicyManager = PolicyManager; |
| |
| /** |
| * Check if the received data packet or signed interest can escape from |
| * verification and be trusted as valid. |
| * Your derived class should override. |
| * |
| * @param {Data|Interest} dataOrInterest The received data packet or interest. |
| * @return {boolean} True if the data or interest does not need to be verified |
| * to be trusted as valid, otherwise false. |
| */ |
| PolicyManager.prototype.skipVerifyAndTrust = function(dataOrInterest) |
| { |
| throw new Error("PolicyManager.skipVerifyAndTrust is not implemented"); |
| }; |
| |
| /** |
| * Check if this PolicyManager has a verification rule for the received data |
| * packet or signed interest. |
| * Your derived class should override. |
| * |
| * @param {Data|Interest} dataOrInterest The received data packet or interest. |
| * @return {boolean} True if the data or interest must be verified, otherwise |
| * false. |
| */ |
| PolicyManager.prototype.requireVerify = function(dataOrInterest) |
| { |
| throw new Error("PolicyManager.requireVerify is not implemented"); |
| }; |
| |
| /** |
| * Check whether the received data or interest packet complies with the |
| * verification policy, and get the indication of the next verification step. |
| * Your derived class should override. |
| * |
| * @param {Data|Interest} dataOrInterest The Data object or interest with the |
| * signature to check. |
| * @param {number} stepCount The number of verification steps that have been |
| * done, used to track the verification progress. |
| * @param {function} onVerified If the signature is verified, this calls |
| * onVerified(dataOrInterest). |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onValidationFailed If the signature check fails, this calls |
| * onValidationFailed(dataOrInterest, reason). |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {WireFormat} wireFormat |
| * @return {ValidationRequest} The indication of next verification step, or |
| * null if there is no further step. |
| */ |
| PolicyManager.prototype.checkVerificationPolicy = function |
| (dataOrInterest, stepCount, onVerified, onValidationFailed, wireFormat) |
| { |
| throw new Error("PolicyManager.checkVerificationPolicy is not implemented"); |
| }; |
| |
| /** |
| * Check if the signing certificate name and data name satisfy the signing |
| * policy. |
| * Your derived class should override. |
| * |
| * @param {Name} dataName The name of data to be signed. |
| * @param {Name} certificateName The name of signing certificate. |
| * @return {boolean} True if the signing certificate can be used to sign the |
| * data, otherwise false. |
| */ |
| PolicyManager.prototype.checkSigningPolicy = function(dataName, certificateName) |
| { |
| throw new Error("PolicyManager.checkSigningPolicy is not implemented"); |
| }; |
| |
| /** |
| * Infer the signing identity name according to the policy. If the signing |
| * identity cannot be inferred, return an empty name. |
| * Your derived class should override. |
| * |
| * @param {Name} dataName The name of data to be signed. |
| * @return {Name} The signing identity or an empty name if cannot infer. |
| */ |
| PolicyManager.prototype.inferSigningIdentity = function(dataName) |
| { |
| throw new Error("PolicyManager.inferSigningIdentity is not implemented"); |
| }; |
| |
| // The first time verify is called, it sets this to determine if a signature |
| // buffer needs to be converted to a string for the crypto verifier. |
| PolicyManager.verifyUsesString_ = null; |
| PolicyManager.setVerifyUsesString_ = function() |
| { |
| var hashResult = Crypto.createHash('sha256').digest(); |
| // If the hash result is a string, we assume that this is a version of |
| // crypto where verify also uses a string signature. |
| PolicyManager.verifyUsesString_ = (typeof hashResult === 'string'); |
| }; |
| |
| /** |
| * Check the type of signature and use the publicKeyDer to verify the |
| * signedBlob using the appropriate signature algorithm. |
| * @param {Signature} signature An object of a subclass of Signature, e.g. |
| * Sha256WithRsaSignature. |
| * @param {SignedBlob} signedBlob the SignedBlob with the signed portion to |
| * verify. |
| * @param {Blob} publicKeyDer The DER-encoded public key used to verify the |
| * signature. |
| * @param {function} onComplete This calls onComplete(true) if the signature |
| * verifies, otherwise onComplete(false). |
| * @throws SecurityException if the signature type is not recognized or if |
| * publicKeyDer can't be decoded. |
| */ |
| PolicyManager.verifySignature = function |
| (signature, signedBlob, publicKeyDer, onComplete) |
| { |
| if (signature instanceof Sha256WithRsaSignature) { |
| if (publicKeyDer.isNull()) { |
| onComplete(false); |
| return; |
| } |
| PolicyManager.verifySha256WithRsaSignature |
| (signature.getSignature(), signedBlob, publicKeyDer, onComplete); |
| } |
| else if (signature instanceof Sha256WithEcdsaSignature) { |
| if (publicKeyDer.isNull()) { |
| onComplete(false); |
| return; |
| } |
| PolicyManager.verifySha256WithEcdsaSignature |
| (signature.getSignature(), signedBlob, publicKeyDer, onComplete); |
| } |
| else if (signature instanceof DigestSha256Signature) |
| PolicyManager.verifyDigestSha256Signature |
| (signature.getSignature(), signedBlob, onComplete); |
| else |
| // We don't expect this to happen. |
| throw new SecurityException(new Error |
| ("PolicyManager.verify: Signature type is unknown")); |
| }; |
| |
| /** |
| * Verify the RSA signature on the SignedBlob using the given public key. |
| * @param {Blob} signature The signature bits. |
| * @param {SignedBlob} signedBlob the SignedBlob with the signed portion to |
| * verify. |
| * @param {Blob} publicKeyDer The DER-encoded public key used to verify the |
| * signature. |
| * @param {function} onComplete This calls onComplete(true) if the signature |
| * verifies, otherwise onComplete(false). |
| */ |
| PolicyManager.verifySha256WithRsaSignature = function |
| (signature, signedBlob, publicKeyDer, onComplete) |
| { |
| if (UseSubtleCrypto()){ |
| var algo = {name:"RSASSA-PKCS1-v1_5",hash:{name:"SHA-256"}}; |
| |
| crypto.subtle.importKey("spki", publicKeyDer.buf().buffer, algo, true, ["verify"]).then(function(publicKey){ |
| return crypto.subtle.verify(algo, publicKey, signature.buf(), signedBlob.signedBuf()) |
| }).then(function(verified){ |
| onComplete(verified); |
| }); |
| } else { |
| if (PolicyManager.verifyUsesString_ === null) |
| PolicyManager.setVerifyUsesString_(); |
| |
| // The crypto verifier requires a PEM-encoded public key. |
| var keyBase64 = publicKeyDer.buf().toString('base64'); |
| var keyPem = "-----BEGIN PUBLIC KEY-----\n"; |
| for (var i = 0; i < keyBase64.length; i += 64) |
| keyPem += (keyBase64.substr(i, 64) + "\n"); |
| keyPem += "-----END PUBLIC KEY-----"; |
| |
| var verifier = Crypto.createVerify('RSA-SHA256'); |
| verifier.update(signedBlob.signedBuf()); |
| var signatureBytes = PolicyManager.verifyUsesString_ ? |
| DataUtils.toString(signature.buf()) : signature.buf(); |
| onComplete(verifier.verify(keyPem, signatureBytes)); |
| } |
| }; |
| |
| /** |
| * Verify the ECDSA signature on the SignedBlob using the given public key. |
| * @param {Blob} signature The signature bits. |
| * @param {SignedBlob} signedBlob the SignedBlob with the signed portion to |
| * verify. |
| * @param {Blob} publicKeyDer The DER-encoded public key used to verify the |
| * signature. |
| * @param {function} onComplete This calls onComplete(true) if the signature |
| * verifies, otherwise onComplete(false). |
| */ |
| PolicyManager.verifySha256WithEcdsaSignature = function |
| (signature, signedBlob, publicKeyDer, onComplete) |
| { |
| if (UseSubtleCrypto()) { |
| /* |
| var algo = {name:"RSASSA-PKCS1-v1_5",hash:{name:"SHA-256"}}; |
| |
| crypto.subtle.importKey("spki", publicKeyDer.buf().buffer, algo, true, ["verify"]).then(function(publicKey){ |
| return crypto.subtle.verify(algo, publicKey, signature.buf(), signedBlob.signedBuf()) |
| }).then(function(verified){ |
| onComplete(verified); |
| }); |
| */ onComplete(false); |
| } else { |
| if (PolicyManager.verifyUsesString_ === null) |
| PolicyManager.setVerifyUsesString_(); |
| |
| // The crypto verifier requires a PEM-encoded public key. |
| var keyBase64 = publicKeyDer.buf().toString("base64"); |
| var keyPem = "-----BEGIN PUBLIC KEY-----\n"; |
| for (var i = 0; i < keyBase64.length; i += 64) |
| keyPem += (keyBase64.substr(i, 64) + "\n"); |
| keyPem += "-----END PUBLIC KEY-----"; |
| |
| // Just create a "sha256". The Crypto library will infer ECDSA from the key. |
| var verifier = Crypto.createVerify("sha256"); |
| verifier.update(signedBlob.signedBuf()); |
| var signatureBytes = PolicyManager.verifyUsesString_ ? |
| DataUtils.toString(signature.buf()) : signature.buf(); |
| onComplete(verifier.verify(keyPem, signatureBytes)); |
| } |
| }; |
| |
| /** |
| * Verify the DigestSha256 signature on the SignedBlob by verifying that the |
| * digest of SignedBlob equals the signature. |
| * @param {Blob} signature The signature bits. |
| * @param {SignedBlob} signedBlob the SignedBlob with the signed portion to |
| * verify. |
| * @param {function} onComplete This calls onComplete(true) if the signature |
| * verifies, otherwise onComplete(false). |
| */ |
| PolicyManager.verifyDigestSha256Signature = function |
| (signature, signedBlob, onComplete) |
| { |
| // Set signedPortionDigest to the digest of the signed portion of the signedBlob. |
| var hash = Crypto.createHash('sha256'); |
| hash.update(signedBlob.signedBuf()); |
| var signedPortionDigest = new Blob(hash.digest(), false); |
| |
| onComplete(signedPortionDigest.equals(signature)); |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * From PyNDN certificate_cache.py by Adeola Bannis. |
| * Originally from Yingdi Yu <http://irl.cs.ucla.edu/~yingdi/>. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var IdentityCertificate = require('../certificate/identity-certificate.js').IdentityCertificate; |
| |
| /** |
| * A CertificateCache is used to save other users' certificate during |
| * verification. |
| * @constructor |
| */ |
| var CertificateCache = function CertificateCache() |
| { |
| // The key is the certificate name URI. The value is the wire encoding Blob. |
| this.cache = {}; |
| }; |
| |
| exports.CertificateCache = CertificateCache; |
| |
| /** |
| * Insert the certificate into the cache. Assumes the timestamp is not yet |
| * removed from the name. |
| * @param {IdentityCertificate} certificate The certificate to insert. |
| */ |
| CertificateCache.prototype.insertCertificate = function(certificate) |
| { |
| var certName = certificate.getName().getPrefix(-1); |
| this.cache[certName.toUri()] = certificate.wireEncode(); |
| }; |
| |
| /** |
| * Remove a certificate from the cache. This does nothing if it is not present. |
| * @param {Name} certificateName The name of the certificate to remove. This |
| * assumes there is no timestamp in the name. |
| */ |
| CertificateCache.prototype.deleteCertificate = function(certificateName) |
| { |
| delete this.cache[certificateName.toUri()]; |
| }; |
| |
| /** |
| * Fetch a certificate from the cache. |
| * @param {Name} certificateName The name of the certificate to remove. This |
| * assumes there is no timestamp in the name. |
| * @return {IdentityCertificate} A new copy of the IdentityCertificate, or null |
| * if not found. |
| */ |
| CertificateCache.prototype.getCertificate = function(certificateName) |
| { |
| var certData = this.cache[certificateName.toUri()]; |
| if (certData === undefined) |
| return null; |
| |
| var cert = new IdentityCertificate(); |
| cert.wireDecode(certData); |
| return cert; |
| }; |
| |
| /** |
| * Clear all certificates from the store. |
| */ |
| CertificateCache.prototype.reset = function() |
| { |
| this.cache = {}; |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * From PyNDN config_policy_manager.py by Adeola Bannis. |
| * Originally from Yingdi Yu <http://irl.cs.ucla.edu/~yingdi/>. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var fs = require('fs'); /** @ignore */ |
| var path = require('path'); /** @ignore */ |
| var Name = require('../../name.js').Name; /** @ignore */ |
| var Data = require('../../data.js').Data; /** @ignore */ |
| var Interest = require('../../interest.js').Interest; /** @ignore */ |
| var KeyLocator = require('../../key-locator.js').KeyLocator; /** @ignore */ |
| var KeyLocatorType = require('../../key-locator.js').KeyLocatorType; /** @ignore */ |
| var Blob = require('../../util/blob.js').Blob; /** @ignore */ |
| var IdentityCertificate = require('../certificate/identity-certificate.js').IdentityCertificate; /** @ignore */ |
| var BoostInfoParser = require('../../util/boost-info-parser.js').BoostInfoParser; /** @ignore */ |
| var NdnRegexMatcher = require('../../util/ndn-regex-matcher.js').NdnRegexMatcher; /** @ignore */ |
| var CertificateCache = require('./certificate-cache.js').CertificateCache; /** @ignore */ |
| var ValidationRequest = require('./validation-request.js').ValidationRequest; /** @ignore */ |
| var SecurityException = require('../security-exception.js').SecurityException; /** @ignore */ |
| var WireFormat = require('../../encoding/wire-format.js').WireFormat; /** @ignore */ |
| var PolicyManager = require('./policy-manager.js').PolicyManager; /** @ignore */ |
| var NdnCommon = require('../../util/ndn-common.js').NdnCommon; |
| |
| /** |
| * ConfigPolicyManager manages trust according to a configuration file in the |
| * Validator Configuration File Format |
| * (http://redmine.named-data.net/projects/ndn-cxx/wiki/CommandValidatorConf) |
| * |
| * Once a rule is matched, the ConfigPolicyManager looks in the |
| * CertificateCache for the IdentityCertificate matching the name in the KeyLocator |
| * and uses its public key to verify the data packet or signed interest. If the |
| * certificate can't be found, it is downloaded, verified and installed. A chain |
| * of certificates will be followed to a maximum depth. |
| * If the new certificate is accepted, it is used to complete the verification. |
| * |
| * The KeyLocators of data packets and signed interests MUST contain a name for |
| * verification to succeed. |
| * |
| * Create a new ConfigPolicyManager which will act on the rules specified in the |
| * configuration and download unknown certificates when necessary. |
| * |
| * @param {string} configFileName (optional) If not null or empty, the path to |
| * the configuration file containing verification rules. (This only works in |
| * Node.js since it reads files using the "fs" module.) Otherwise, you should |
| * separately call load(). |
| * @param {CertificateCache} certificateCache (optional) A CertificateCache to |
| * hold known certificates. If this is null or omitted, then create an internal |
| * CertificateCache. |
| * @param {number} searchDepth (optional) The maximum number of links to follow |
| * when verifying a certificate chain. If omitted, use a default. |
| * @param {number} graceInterval (optional) The window of time difference |
| * (in milliseconds) allowed between the timestamp of the first interest signed with |
| * a new public key and the validation time. If omitted, use a default value. |
| * @param {number} keyTimestampTtl (optional) How long a public key's last-used |
| * timestamp is kept in the store (milliseconds). If omitted, use a default value. |
| * @param {number} maxTrackedKeys The maximum number of public key use |
| * timestamps to track. If omitted, use a default. |
| * @constructor |
| */ |
| var ConfigPolicyManager = function ConfigPolicyManager |
| (configFileName, certificateCache, searchDepth, graceInterval, |
| keyTimestampTtl, maxTrackedKeys) |
| { |
| // Call the base constructor. |
| PolicyManager.call(this); |
| |
| if (certificateCache == undefined) |
| certificateCache = null; |
| if (searchDepth == undefined) |
| searchDepth = 5; |
| if (graceInterval == undefined) |
| graceInterval = 3000; |
| if (keyTimestampTtl == undefined) |
| keyTimestampTtl = 3600000; |
| if (maxTrackedKeys == undefined) |
| maxTrackedKeys = 1000; |
| |
| if (certificateCache == null) |
| this.certificateCache = new CertificateCache(); |
| else |
| this.certificateCache = certificateCache; |
| this.maxDepth = searchDepth; |
| this.keyGraceInterval = graceInterval; |
| this.keyTimestampTtl = keyTimestampTtl; |
| this.maxTrackedKeys = maxTrackedKeys; |
| |
| this.reset(); |
| |
| if (configFileName != null && configFileName != "") |
| this.load(configFileName); |
| }; |
| |
| ConfigPolicyManager.prototype = new PolicyManager(); |
| ConfigPolicyManager.prototype.name = "ConfigPolicyManager"; |
| |
| exports.ConfigPolicyManager = ConfigPolicyManager; |
| |
| /** |
| * Reset the certificate cache and other fields to the constructor state. |
| */ |
| ConfigPolicyManager.prototype.reset = function() |
| { |
| this.certificateCache.reset(); |
| |
| // Stores the fixed-signer certificate name associated with validation rules |
| // so we don't keep loading from files. |
| this.fixedCertificateCache = {}; |
| |
| // Stores the timestamps for each public key used in command interests to |
| // avoid replay attacks. |
| // Key is public key name, value is last timestamp. |
| this.keyTimestamps = {}; |
| |
| this.requiresVerification = true; |
| |
| this.config = new BoostInfoParser(); |
| this.refreshManager = new ConfigPolicyManager.TrustAnchorRefreshManager(); |
| }; |
| |
| /** |
| * Call reset() and load the configuration rules from the file name or the input |
| * string. There are two forms: |
| * load(configFileName) reads configFileName from the file system. (This only |
| * works in Node.js since it reads files using the "fs" module.) |
| * load(input, inputName) reads from the input, in which case inputName is used |
| * only for log messages, etc. |
| * @param {string} configFileName The path to the file containing configuration |
| * rules. |
| * @param {string} input The contents of the configuration rules, with lines |
| * separated by "\n" or "\r\n". |
| * @param {string} inputName Use with input for log messages, etc. |
| */ |
| ConfigPolicyManager.prototype.load = function(configFileNameOrInput, inputName) |
| { |
| this.reset(); |
| this.config.read(configFileNameOrInput, inputName); |
| this.loadTrustAnchorCertificates(); |
| } |
| |
| /** |
| * Check if this PolicyManager has a verification rule for the received data. |
| * If the configuration file contains the trust anchor 'any', nothing is |
| * verified. |
| * |
| * @param {Data|Interest} dataOrInterest The received data packet or interest. |
| * @return {boolean} true if the data must be verified, otherwise false. |
| */ |
| ConfigPolicyManager.prototype.requireVerify = function(dataOrInterest) |
| { |
| return this.requiresVerification; |
| }; |
| |
| /** |
| * Override to always indicate that the signing certificate name and data name |
| * satisfy the signing policy. |
| * |
| * @param {Name} dataName The name of data to be signed. |
| * @param {Name} certificateName The name of signing certificate. |
| * @return {boolean} True to indicate that the signing certificate can be used |
| * to sign the data. |
| */ |
| ConfigPolicyManager.prototype.checkSigningPolicy = function |
| (dataName, certificateName) |
| { |
| return true; |
| }; |
| |
| /** |
| * Check if the received signed interest can escape from verification and be |
| * trusted as valid. If the configuration file contains the trust anchor |
| * 'any', nothing is verified. |
| * |
| * @param {Data|Interest} dataOrInterest The received data packet or interest. |
| * @return {boolean} true if the data or interest does not need to be verified |
| * to be trusted as valid, otherwise false. |
| */ |
| ConfigPolicyManager.prototype.skipVerifyAndTrust = function(dataOrInterest) |
| { |
| return !this.requiresVerification; |
| }; |
| |
| /** |
| * Check whether the received data packet or interest complies with the |
| * verification policy, and get the indication of the next verification step. |
| * |
| * @param {Data|Interest} dataOrInterest The Data object or interest with the |
| * signature to check. |
| * @param {number} stepCount The number of verification steps that have been |
| * done, used to track the verification progress. |
| * @param {function} onVerified If the signature is verified, this calls |
| * onVerified(dataOrInterest). |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onValidationFailed If the signature check fails, this calls |
| * onValidationFailed(dataOrInterest, reason). |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {WireFormat} wireFormat |
| * @return {ValidationRequest} The indication of next verification step, or |
| * null if there is no further step. |
| */ |
| ConfigPolicyManager.prototype.checkVerificationPolicy = function |
| (dataOrInterest, stepCount, onVerified, onValidationFailed, wireFormat) |
| { |
| if (stepCount > this.maxDepth) { |
| try { |
| onValidationFailed |
| (dataOrInterest, "The verification stepCount " + stepCount + |
| " exceeded the maxDepth " + this.maxDepth); |
| } catch (ex) { |
| console.log("Error in onValidationFailed: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| return null; |
| } |
| |
| var signature = ConfigPolicyManager.extractSignature(dataOrInterest, wireFormat); |
| // No signature -> fail. |
| if (signature == null) { |
| try { |
| onValidationFailed |
| (dataOrInterest, "Cannot extract the signature from " + |
| dataOrInterest.getName().toUri()); |
| } catch (ex) { |
| console.log("Error in onValidationFailed: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| return null; |
| } |
| |
| if (!KeyLocator.canGetFromSignature(signature)) { |
| // We only support signature types with key locators. |
| try { |
| onValidationFailed |
| (dataOrInterest, "The signature type does not support a KeyLocator"); |
| } catch (ex) { |
| console.log("Error in onValidationFailed: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| return null; |
| } |
| |
| var keyLocator = null; |
| try { |
| keyLocator = KeyLocator.getFromSignature(signature); |
| } |
| catch (ex) { |
| // No key locator -> fail. |
| try { |
| onValidationFailed |
| (dataOrInterest, "Error in KeyLocator.getFromSignature: " + ex); |
| } catch (ex) { |
| console.log("Error in onValidationFailed: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| return null; |
| } |
| |
| var signatureName = keyLocator.getKeyName(); |
| // No key name in KeyLocator -> fail. |
| if (signatureName.size() == 0) { |
| try { |
| onValidationFailed |
| (dataOrInterest, "The signature KeyLocator doesn't have a key name"); |
| } catch (ex) { |
| console.log("Error in onValidationFailed: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| return null; |
| } |
| |
| var objectName = dataOrInterest.getName(); |
| var matchType = "data"; |
| |
| // For command interests, we need to ignore the last 4 components when |
| // matching the name. |
| if (dataOrInterest instanceof Interest) { |
| objectName = objectName.getPrefix(-4); |
| matchType = "interest"; |
| } |
| |
| // First see if we can find a rule to match this packet. |
| var matchedRule = this.findMatchingRule(objectName, matchType); |
| |
| // No matching rule -> fail. |
| if (matchedRule == null) { |
| try { |
| onValidationFailed |
| (dataOrInterest, "No matching rule found for " + objectName.toUri()); |
| } catch (ex) { |
| console.log("Error in onValidationFailed: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| return null; |
| } |
| |
| var failureReason = ["unknown"]; |
| var signatureMatches = this.checkSignatureMatch |
| (signatureName, objectName, matchedRule, failureReason); |
| if (!signatureMatches) { |
| try { |
| onValidationFailed(dataOrInterest, failureReason[0]); |
| } catch (ex) { |
| console.log("Error in onValidationFailed: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| return null; |
| } |
| |
| // Before we look up keys, refresh any certificate directories. |
| this.refreshManager.refreshAnchors(); |
| |
| // Now finally check that the data or interest was signed correctly. |
| // If we don't actually have the certificate yet, create a |
| // ValidationRequest for it. |
| var foundCert = this.refreshManager.getCertificate(signatureName); |
| if (foundCert == null) |
| foundCert = this.certificateCache.getCertificate(signatureName); |
| var thisManager = this; |
| if (foundCert == null) { |
| var certificateInterest = new Interest(signatureName); |
| var onCertificateDownloadComplete = function(data) { |
| var certificate; |
| try { |
| certificate = new IdentityCertificate(data); |
| } catch (ex) { |
| try { |
| onValidationFailed |
| (dataOrInterest, "Cannot decode certificate " + data.getName().toUri()); |
| } catch (ex) { |
| console.log("Error in onValidationFailed: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| return null; |
| } |
| thisManager.certificateCache.insertCertificate(certificate); |
| thisManager.checkVerificationPolicy |
| (dataOrInterest, stepCount + 1, onVerified, onValidationFailed); |
| }; |
| |
| var nextStep = new ValidationRequest |
| (certificateInterest, onCertificateDownloadComplete, onValidationFailed, |
| 2, stepCount + 1); |
| |
| return nextStep; |
| } |
| |
| // For interests, we must check that the timestamp is fresh enough. |
| // We do this after (possibly) downloading the certificate to avoid |
| // filling the cache with bad keys. |
| if (dataOrInterest instanceof Interest) { |
| var keyName = foundCert.getPublicKeyName(); |
| var timestamp = dataOrInterest.getName().get(-4).toNumber(); |
| |
| if (!this.interestTimestampIsFresh(keyName, timestamp, failureReason)) { |
| try { |
| onValidationFailed(dataOrInterest, failureReason[0]); |
| } catch (ex) { |
| console.log("Error in onValidationFailed: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| return null; |
| } |
| } |
| |
| // Certificate is known, so verify the signature. |
| this.verify(signature, dataOrInterest.wireEncode(), function (verified, reason) { |
| if (verified) { |
| try { |
| onVerified(dataOrInterest); |
| } catch (ex) { |
| console.log("Error in onVerified: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| if (dataOrInterest instanceof Interest) |
| thisManager.updateTimestampForKey(keyName, timestamp); |
| } |
| else { |
| try { |
| onValidationFailed(dataOrInterest, reason); |
| } catch (ex) { |
| console.log("Error in onValidationFailed: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| }); |
| }; |
| |
| /** |
| * The configuration file allows 'trust anchor' certificates to be preloaded. |
| * The certificates may also be loaded from a directory, and if the 'refresh' |
| * option is set to an interval, the certificates are reloaded at the specified |
| * interval. |
| */ |
| ConfigPolicyManager.prototype.loadTrustAnchorCertificates = function() |
| { |
| var anchors = this.config.getRoot().get("validator/trust-anchor"); |
| |
| for (var i = 0; i < anchors.length; ++i) { |
| var anchor = anchors[i]; |
| |
| var typeName = anchor.get("type")[0].getValue(); |
| var isPath = false; |
| var certID; |
| if (typeName == 'file') { |
| certID = anchor.get("file-name")[0].getValue(); |
| isPath = true; |
| } |
| else if (typeName == 'base64') { |
| certID = anchor.get("base64-string")[0].getValue(); |
| isPath = false; |
| } |
| else if (typeName == "dir") { |
| var dirName = anchor.get("dir")[0].getValue(); |
| |
| var refreshPeriod = 0; |
| var refreshTrees = anchor.get("refresh"); |
| if (refreshTrees.length >= 1) { |
| var refreshPeriodStr = refreshTrees[0].getValue(); |
| |
| var refreshMatch = refreshPeriodStr.match(/(\d+)([hms])/); |
| if (refreshMatch == null) |
| refreshPeriod = 0; |
| else { |
| refreshPeriod = parseInt(refreshMatch[1]); |
| if (refreshMatch[2] != 's') { |
| refreshPeriod *= 60; |
| if (refreshMatch[2] != 'm') |
| refreshPeriod *= 60; |
| } |
| } |
| } |
| |
| // Convert refreshPeriod from seconds to milliseconds. |
| this.refreshManager.addDirectory(dirName, refreshPeriod * 1000); |
| continue; |
| } |
| else if (typeName == "any") { |
| // This disables all security! |
| this.requiresVerification = false; |
| break; |
| } |
| |
| this.lookupCertificate(certID, isPath); |
| } |
| }; |
| |
| /** |
| * Once a rule is found to match data or a signed interest, the name in the |
| * KeyLocator must satisfy the condition in the 'checker' section of the rule, |
| * else the data or interest is rejected. |
| * @param {Name} signatureName The certificate name from the KeyLocator. |
| * @param {Name} objectName The name of the data packet or interest. In the case |
| * of signed interests, this excludes the timestamp, nonce and signature |
| * components. |
| * @param {BoostInfoTree} rule The rule from the configuration file that matches |
| * the data or interest. |
| * @param {Array<string>} failureReason If matching fails, set failureReason[0] |
| * to the failure reason. |
| * @return {boolean} True if matches. |
| */ |
| ConfigPolicyManager.prototype.checkSignatureMatch = function |
| (signatureName, objectName, rule, failureReason) |
| { |
| var checker = rule.get("checker")[0]; |
| var checkerType = checker.get("type")[0].getValue(); |
| if (checkerType == "fixed-signer") { |
| var signerInfo = checker.get("signer")[0]; |
| var signerType = signerInfo.get("type")[0].getValue(); |
| |
| var cert; |
| if (signerType == "file") { |
| cert = this.lookupCertificate |
| (signerInfo.get("file-name")[0].getValue(), true); |
| if (cert == null) { |
| failureReason[0] = "Can't find fixed-signer certificate file: " + |
| signerInfo.get("file-name")[0].getValue(); |
| return false; |
| } |
| } |
| else if (signerType == "base64") { |
| cert = this.lookupCertificate |
| (signerInfo.get("base64-string")[0].getValue(), false); |
| if (cert == null) { |
| failureReason[0] = "Can't find fixed-signer certificate base64: " + |
| signerInfo.get("base64-string")[0].getValue(); |
| return false; |
| } |
| } |
| else { |
| failureReason[0] = "Unrecognized fixed-signer signerType: " + signerType; |
| return false; |
| } |
| |
| if (cert.getName().equals(signatureName)) |
| return true; |
| else { |
| failureReason[0] = "fixed-signer cert name \"" + cert.getName().toUri() + |
| "\" does not equal signatureName \"" + signatureName.toUri() + "\""; |
| return false; |
| } |
| } |
| else if (checkerType == "hierarchical") { |
| // This just means the data/interest name has the signing identity as a prefix. |
| // That means everything before "ksk-?" in the key name. |
| var identityRegex = "^([^<KEY>]*)<KEY>(<>*)<ksk-.+><ID-CERT>"; |
| var identityMatch = NdnRegexMatcher.match(identityRegex, signatureName); |
| if (identityMatch != null) { |
| var identityPrefix = new Name(identityMatch[1]).append |
| (new Name(identityMatch[2])); |
| if (ConfigPolicyManager.matchesRelation |
| (objectName, identityPrefix, "is-prefix-of")) |
| return true; |
| else { |
| failureReason[0] = "The hierarchical objectName \"" + objectName.toUri() + |
| "\" is not a prefix of \"" + identityPrefix.toUri() + "\""; |
| return false; |
| } |
| } |
| else { |
| failureReason[0] = "The hierarchical identityRegex \"" + identityRegex + |
| "\" does not match signatureName \"" + signatureName.toUri() + "\""; |
| return false; |
| } |
| } |
| else if (checkerType == "customized") { |
| var keyLocatorInfo = checker.get("key-locator")[0]; |
| // Not checking type - only name is supported. |
| |
| // Is this a simple relation? |
| var relationType = keyLocatorInfo.getFirstValue("relation"); |
| if (relationType != null) { |
| var matchName = new Name(keyLocatorInfo.get("name")[0].getValue()); |
| if (ConfigPolicyManager.matchesRelation |
| (signatureName, matchName, relationType)) |
| return true; |
| else { |
| failureReason[0] = "The custom signatureName \"" + signatureName.toUri() + |
| "\" does not match matchName \"" + matchName.toUri() + |
| "\" using relation " + relationType; |
| return false; |
| } |
| } |
| |
| // Is this a simple regex? |
| var keyRegex = keyLocatorInfo.getFirstValue("regex"); |
| if (keyRegex != null) { |
| if (NdnRegexMatcher.match(keyRegex, signatureName) != null) |
| return true; |
| else { |
| failureReason[0] = "The custom signatureName \"" + signatureName.toUri() + |
| "\" does not regex match simpleKeyRegex \"" + keyRegex + "\""; |
| return false; |
| } |
| } |
| |
| // Is this a hyper-relation? |
| var hyperRelationList = keyLocatorInfo.get("hyper-relation"); |
| if (hyperRelationList.length >= 1) { |
| var hyperRelation = hyperRelationList[0]; |
| |
| var keyRegex = hyperRelation.getFirstValue("k-regex"); |
| var keyExpansion = hyperRelation.getFirstValue("k-expand"); |
| var nameRegex = hyperRelation.getFirstValue("p-regex"); |
| var nameExpansion = hyperRelation.getFirstValue("p-expand"); |
| var relationType = hyperRelation.getFirstValue("h-relation"); |
| if (keyRegex != null && keyExpansion != null && nameRegex != null && |
| nameExpansion != null && relationType != null) { |
| var keyMatch = NdnRegexMatcher.match(keyRegex, signatureName); |
| if (keyMatch == null || keyMatch[1] === undefined) { |
| failureReason[0] = "The custom hyper-relation signatureName \"" + |
| signatureName.toUri() + "\" does not match the keyRegex \"" + |
| keyRegex + "\""; |
| return false; |
| } |
| var keyMatchPrefix = ConfigPolicyManager.expand(keyMatch, keyExpansion); |
| |
| var nameMatch = NdnRegexMatcher.match(nameRegex, objectName); |
| if (nameMatch == null || nameMatch[1] === undefined) { |
| failureReason[0] = "The custom hyper-relation objectName \"" + |
| objectName.toUri() + "\" does not match the nameRegex \"" + |
| nameRegex + "\""; |
| return false; |
| } |
| var nameMatchStr = ConfigPolicyManager.expand(nameMatch, nameExpansion); |
| |
| if (ConfigPolicyManager.matchesRelation |
| (new Name(nameMatchStr), new Name(keyMatchPrefix), relationType)) |
| return true; |
| else { |
| failureReason[0] = "The custom hyper-relation nameMatch \"" + |
| nameMatchStr + "\" does not match the keyMatchPrefix \"" + |
| keyMatchPrefix + "\" using relation " + relationType; |
| return false; |
| } |
| } |
| } |
| } |
| |
| failureReason[0] = "Unrecognized checkerType: " + checkerType; |
| return false; |
| }; |
| |
| /** |
| * Similar to Python expand, return expansion where every \1, \2, etc. is |
| * replaced by match[1], match[2], etc. Note: Even though this is a general |
| * utility function, we define it locally because it is only tested to work in |
| * the cases used by this class. |
| * @param {Object} match The match object from String.match. |
| * @param {string} expansion The string with \1, \2, etc. to replace from match. |
| * @return {string} The expanded string. |
| */ |
| ConfigPolicyManager.expand = function(match, expansion) |
| { |
| return expansion.replace |
| (/\\(\d)/g, |
| function(fullMatch, n) { return match[parseInt(n)];}) |
| }; |
| |
| /** |
| * This looks up certificates specified as base64-encoded data or file names. |
| * These are cached by filename or encoding to avoid repeated reading of files |
| * or decoding. |
| * @param {string} certID |
| * @param {boolean} isPath |
| * @return {IdentityCertificate} The certificate object, or null if not found. |
| */ |
| ConfigPolicyManager.prototype.lookupCertificate = function(certID, isPath) |
| { |
| var cert; |
| |
| var cachedCertUri = this.fixedCertificateCache[certID]; |
| if (cachedCertUri === undefined) { |
| if (isPath) |
| // load the certificate data (base64 encoded IdentityCertificate) |
| cert = ConfigPolicyManager.TrustAnchorRefreshManager.loadIdentityCertificateFromFile |
| (certID); |
| else { |
| var certData = new Buffer(certID, 'base64'); |
| cert = new IdentityCertificate(); |
| cert.wireDecode(certData); |
| } |
| |
| var certUri = cert.getName().getPrefix(-1).toUri(); |
| this.fixedCertificateCache[certID] = certUri; |
| this.certificateCache.insertCertificate(cert); |
| } |
| else |
| cert = this.certificateCache.getCertificate(new Name(cachedCertUri)); |
| |
| return cert; |
| }; |
| |
| /** |
| * Search the configuration file for the first rule that matches the data or |
| * signed interest name. In the case of interests, the name to match should |
| * exclude the timestamp, nonce, and signature components. |
| * @param {Name} objName The name to be matched. |
| * @param {string} matchType The rule type to match, "data" or "interest". |
| * @return {BoostInfoTree} The matching rule, or null if not found. |
| */ |
| ConfigPolicyManager.prototype.findMatchingRule = function(objName, matchType) |
| { |
| var rules = this.config.getRoot().get("validator/rule"); |
| for (var iRule = 0; iRule < rules.length; ++iRule) { |
| var r = rules[iRule]; |
| |
| if (r.get('for')[0].getValue() == matchType) { |
| var passed = true; |
| var filters = r.get('filter'); |
| if (filters.length == 0) |
| // No filters means we pass! |
| return r; |
| else { |
| for (var iFilter = 0; iFilter < filters.length; ++iFilter) { |
| var f = filters[iFilter]; |
| |
| // Don't check the type - it can only be name for now. |
| // We need to see if this is a regex or a relation. |
| var regexPattern = f.getFirstValue("regex"); |
| if (regexPattern === null) { |
| var matchRelation = f.get('relation')[0].getValue(); |
| var matchUri = f.get('name')[0].getValue(); |
| var matchName = new Name(matchUri); |
| passed = ConfigPolicyManager.matchesRelation(objName, matchName, matchRelation); |
| } |
| else |
| passed = (NdnRegexMatcher.match(regexPattern, objName) !== null); |
| |
| if (!passed) |
| break; |
| } |
| |
| if (passed) |
| return r; |
| } |
| } |
| } |
| |
| return null; |
| }; |
| |
| /** |
| * Determines if a name satisfies the relation to matchName. |
| * @param {Name} name |
| * @param {Name} matchName |
| * @param {string} matchRelation Can be one of: |
| * 'is-prefix-of' - passes if the name is equal to or has the other |
| * name as a prefix |
| * 'is-strict-prefix-of' - passes if the name has the other name as a |
| * prefix, and is not equal |
| * 'equal' - passes if the two names are equal |
| * @return {boolean} |
| */ |
| ConfigPolicyManager.matchesRelation = function(name, matchName, matchRelation) |
| { |
| var passed = false; |
| if (matchRelation == 'is-strict-prefix-of') { |
| if (matchName.size() == name.size()) |
| passed = false; |
| else if (matchName.match(name)) |
| passed = true; |
| } |
| else if (matchRelation == 'is-prefix-of') { |
| if (matchName.match(name)) |
| passed = true; |
| } |
| else if (matchRelation == 'equal') { |
| if (matchName.equals(name)) |
| passed = true; |
| } |
| return passed; |
| }; |
| |
| /** |
| * Extract the signature information from the interest name or from the data |
| * packet or interest. |
| * @param {Data|Interest} dataOrInterest The object whose signature is needed. |
| * @param {WireFormat} wireFormat (optional) The wire format used to decode |
| * signature information from the interest name. |
| * @return {Signature} The object of a sublcass of Signature or null if can't |
| * decode. |
| */ |
| ConfigPolicyManager.extractSignature = function(dataOrInterest, wireFormat) |
| { |
| if (dataOrInterest instanceof Data) |
| return dataOrInterest.getSignature(); |
| else if (dataOrInterest instanceof Interest) { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| try { |
| var signature = wireFormat.decodeSignatureInfoAndValue |
| (dataOrInterest.getName().get(-2).getValue().buf(), |
| dataOrInterest.getName().get(-1).getValue().buf(), false); |
| } |
| catch (e) { |
| return null; |
| } |
| |
| return signature; |
| } |
| |
| return null; |
| }; |
| |
| /** |
| * Determine whether the timestamp from the interest is newer than the last use |
| * of this key, or within the grace interval on first use. |
| * @param {Name} keyName The name of the public key used to sign the interest. |
| * @param {number} timestamp The timestamp extracted from the interest name. |
| * @param {Array<string>} failureReason If matching fails, set failureReason[0] |
| * to the failure reason. |
| * @return {boolean} True if timestamp is fresh as described above. |
| */ |
| ConfigPolicyManager.prototype.interestTimestampIsFresh = function |
| (keyName, timestamp, failureReason) |
| { |
| var lastTimestamp = this.keyTimestamps[keyName.toUri()]; |
| if (lastTimestamp == undefined) { |
| var now = new Date().getTime(); |
| var notBefore = now - this.keyGraceInterval; |
| var notAfter = now + this.keyGraceInterval; |
| if (!(timestamp > notBefore && timestamp < notAfter)) { |
| failureReason[0] = |
| "The command interest timestamp is not within the first use grace period of " + |
| this.keyGraceInterval + " milliseconds."; |
| return false; |
| } |
| else |
| return true; |
| } |
| else { |
| if (timestamp <= lastTimestamp) { |
| failureReason[0] = |
| "The command interest timestamp is not newer than the previous timestamp"; |
| return false; |
| } |
| else |
| return true; |
| } |
| }; |
| |
| /** |
| * Trim the table size down if necessary, and insert/update the latest interest |
| * signing timestamp for the key. Any key which has not been used within the TTL |
| * period is purged. If the table is still too large, the oldest key is purged. |
| * @param {Name} keyName The name of the public key used to sign the interest. |
| * @param {number} timestamp The timestamp extracted from the interest name. |
| */ |
| ConfigPolicyManager.prototype.updateTimestampForKey = function |
| (keyName, timestamp) |
| { |
| this.keyTimestamps[keyName.toUri()] = timestamp; |
| |
| // JavaScript does have a direct way to get the number of entries, so first |
| // get the keysToErase while counting. |
| var keyTimestampsSize = 0; |
| var keysToErase = []; |
| |
| var now = new Date().getTime(); |
| var oldestTimestamp = now; |
| var oldestKey = null; |
| for (var keyUri in this.keyTimestamps) { |
| ++keyTimestampsSize; |
| var ts = this.keyTimestamps[keyUri]; |
| if (now - ts > this.keyTimestampTtl) |
| keysToErase.push(keyUri); |
| else if (ts < oldestTimestamp) { |
| oldestTimestamp = ts; |
| oldestKey = keyUri; |
| } |
| } |
| |
| if (keyTimestampsSize >= this.maxTrackedKeys) { |
| // Now delete the expired keys. |
| for (var i = 0; i < keysToErase.length; ++i) { |
| delete this.keyTimestamps[keysToErase[i]]; |
| --keyTimestampsSize; |
| } |
| |
| if (keyTimestampsSize > this.maxTrackedKeys) |
| // We have not removed enough. |
| delete this.keyTimestamps[oldestKey]; |
| } |
| }; |
| |
| /** |
| * Check the type of signatureInfo to get the KeyLocator. Look in the |
| * IdentityStorage for the public key with the name in the KeyLocator and use it |
| * to verify the signedBlob. If the public key can't be found, return false. |
| * (This is a generalized method which can verify both a data packet and an |
| * interest.) |
| * @param {Signature} signatureInfo An object of a subclass of Signature, e.g. |
| * Sha256WithRsaSignature. |
| * @param {SignedBlob} signedBlob The SignedBlob with the signed portion to |
| * verify. |
| * @param {function} onComplete This calls onComplete(true, undefined) if the |
| * signature verifies, otherwise onComplete(false, reason). |
| */ |
| ConfigPolicyManager.prototype.verify = function |
| (signatureInfo, signedBlob, onComplete) |
| { |
| // We have already checked once that there is a key locator. |
| var keyLocator = KeyLocator.getFromSignature(signatureInfo); |
| |
| if (keyLocator.getType() == KeyLocatorType.KEYNAME) { |
| // Assume the key name is a certificate name. |
| var signatureName = keyLocator.getKeyName(); |
| var certificate = this.refreshManager.getCertificate(signatureName); |
| if (certificate == null) |
| certificate = this.certificateCache.getCertificate(signatureName); |
| if (certificate == null) { |
| onComplete(false, "Cannot find a certificate with name " + |
| signatureName.toUri()); |
| return; |
| } |
| |
| var publicKeyDer = certificate.getPublicKeyInfo().getKeyDer(); |
| if (publicKeyDer.isNull()) { |
| // Can't find the public key with the name. |
| onComplete(false, "There is no public key in the certificate with name " + |
| certificate.getName().toUri()); |
| return; |
| } |
| |
| PolicyManager.verifySignature |
| (signatureInfo, signedBlob, publicKeyDer, function(verified) { |
| if (verified) |
| onComplete(true); |
| else |
| onComplete |
| (false, |
| "The signature did not verify with the given public key"); |
| }); |
| } |
| else |
| onComplete(false, "The KeyLocator does not have a key name"); |
| }; |
| |
| ConfigPolicyManager.TrustAnchorRefreshManager = |
| function ConfigPolicyManagerTrustAnchorRefreshManager() |
| { |
| this.certificateCache = new CertificateCache(); |
| // Maps the directory name to certificate names so they can be deleted when |
| // necessary. The key is the directory name string. The value is the object |
| // {certificateNames, // array of string |
| // nextRefresh, // number |
| // refreshPeriod // number |
| // }. |
| this.refreshDirectories = {}; |
| }; |
| |
| ConfigPolicyManager.TrustAnchorRefreshManager.loadIdentityCertificateFromFile = |
| function(fileName) |
| { |
| var encodedData = fs.readFileSync(fileName).toString(); |
| var decodedData = new Buffer(encodedData, 'base64'); |
| var cert = new IdentityCertificate(); |
| cert.wireDecode(new Blob(decodedData, false)); |
| return cert; |
| }; |
| |
| ConfigPolicyManager.TrustAnchorRefreshManager.prototype.getCertificate = function |
| (certificateName) |
| { |
| // This assumes the timestamp is already removed. |
| return this.certificateCache.getCertificate(certificateName); |
| }; |
| |
| // refreshPeriod in milliseconds. |
| ConfigPolicyManager.TrustAnchorRefreshManager.prototype.addDirectory = function |
| (directoryName, refreshPeriod) |
| { |
| var allFiles; |
| try { |
| allFiles = fs.readdirSync(directoryName); |
| } |
| catch (e) { |
| throw new SecurityException(new Error |
| ("Cannot list files in directory " + directoryName)); |
| } |
| |
| var certificateNames = []; |
| for (var i = 0; i < allFiles.length; ++i) { |
| var cert; |
| try { |
| var fullPath = path.join(directoryName, allFiles[i]); |
| cert = ConfigPolicyManager.TrustAnchorRefreshManager.loadIdentityCertificateFromFile |
| (fullPath); |
| } |
| catch (e) { |
| // Allow files that are not certificates. |
| continue; |
| } |
| |
| // Cut off the timestamp so it matches the KeyLocator Name format. |
| var certUri = cert.getName().getPrefix(-1).toUri(); |
| this.certificateCache.insertCertificate(cert); |
| certificateNames.push(certUri); |
| } |
| |
| this.refreshDirectories[directoryName] = { |
| certificates: certificateNames, |
| nextRefresh: new Date().getTime() + refreshPeriod, |
| refreshPeriod: refreshPeriod }; |
| }; |
| |
| ConfigPolicyManager.TrustAnchorRefreshManager.prototype.refreshAnchors = function() |
| { |
| var refreshTime = new Date().getTime(); |
| for (var directory in this.refreshDirectories) { |
| var info = this.refreshDirectories[directory]; |
| var nextRefreshTime = info.nextRefresh; |
| if (nextRefreshTime <= refreshTime) { |
| var certificateList = info.certificates.slice(0); |
| // Delete the certificates associated with this directory if possible |
| // then re-import. |
| // IdentityStorage subclasses may not support deletion. |
| for (var c in certificateList) |
| this.certificateCache.deleteCertificate(new Name(c)); |
| |
| this.addDirectory(directory, info.refreshPeriod); |
| } |
| } |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * From ndn-cxx security by Yingdi Yu <yingdi@cs.ucla.edu>. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Name = require('../../name.js').Name; /** @ignore */ |
| var PolicyManager = require('./policy-manager.js').PolicyManager; /** @ignore */ |
| var NdnCommon = require('../../util/ndn-common.js').NdnCommon; |
| |
| /** |
| * @constructor |
| */ |
| var NoVerifyPolicyManager = function NoVerifyPolicyManager() |
| { |
| // Call the base constructor. |
| PolicyManager.call(this); |
| }; |
| |
| NoVerifyPolicyManager.prototype = new PolicyManager(); |
| NoVerifyPolicyManager.prototype.name = "NoVerifyPolicyManager"; |
| |
| exports.NoVerifyPolicyManager = NoVerifyPolicyManager; |
| |
| /** |
| * Override to always skip verification and trust as valid. |
| * |
| * @param {Data|Interest} dataOrInterest The received data packet or interest. |
| * @return {boolean} True. |
| */ |
| NoVerifyPolicyManager.prototype.skipVerifyAndTrust = function(dataOrInterest) |
| { |
| return true; |
| }; |
| |
| /** |
| * Override to return false for no verification rule for the received data or |
| * signed interest. |
| * |
| * @param {Data|Interest} dataOrInterest The received data packet or interest. |
| * @return {boolean} False. |
| */ |
| NoVerifyPolicyManager.prototype.requireVerify = function(dataOrInterest) |
| { |
| return false; |
| }; |
| |
| /** |
| * Override to call onVerified(data) and to indicate no further verification |
| * step. |
| * |
| * @param {Data|Interest} dataOrInterest The Data object or interest with the |
| * signature to check. |
| * @param {number} stepCount The number of verification steps that have been |
| * done, used to track the verification progress. |
| * @param {function} onVerified This does override to call |
| * onVerified(dataOrInterest). |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onValidationFailed Override to ignore this. |
| * @param {WireFormat} wireFormat |
| * @return {ValidationRequest} null for no further step for looking up a |
| * certificate chain. |
| */ |
| NoVerifyPolicyManager.prototype.checkVerificationPolicy = function |
| (dataOrInterest, stepCount, onVerified, onValidationFailed, wireFormat) |
| { |
| try { |
| onVerified(dataOrInterest); |
| } catch (ex) { |
| console.log("Error in onVerified: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| return null; |
| }; |
| |
| /** |
| * Override to always indicate that the signing certificate name and data name |
| * satisfy the signing policy. |
| * |
| * @param {Name} dataName The name of data to be signed. |
| * @param {Name} certificateName The name of signing certificate. |
| * @return {boolean} True to indicate that the signing certificate can be used |
| * to sign the data. |
| */ |
| NoVerifyPolicyManager.prototype.checkSigningPolicy = function |
| (dataName, certificateName) |
| { |
| return true; |
| }; |
| |
| /** |
| * Override to indicate that the signing identity cannot be inferred. |
| * |
| * @param {Name} dataName The name of data to be signed. |
| * @return {Name} An empty name because cannot infer. |
| */ |
| NoVerifyPolicyManager.prototype.inferSigningIdentity = function(dataName) |
| { |
| return new Name(); |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * From ndn-cxx security by Yingdi Yu <yingdi@cs.ucla.edu>. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Name = require('../../name.js').Name; /** @ignore */ |
| var Interest = require('../../interest.js').Interest; /** @ignore */ |
| var Data = require('../../data.js').Data; /** @ignore */ |
| var Blob = require('../../util/blob.js').Blob; /** @ignore */ |
| var IdentityCertificate = require('../certificate/identity-certificate.js').IdentityCertificate; /** @ignore */ |
| var KeyLocator = require('../../key-locator.js').KeyLocator; /** @ignore */ |
| var KeyLocatorType = require('../../key-locator.js').KeyLocatorType; /** @ignore */ |
| var SecurityException = require('../security-exception.js').SecurityException; /** @ignore */ |
| var WireFormat = require('../../encoding/wire-format.js').WireFormat; /** @ignore */ |
| var SyncPromise = require('../../util/sync-promise.js').SyncPromise; /** @ignore */ |
| var PolicyManager = require('./policy-manager.js').PolicyManager; /** @ignore */ |
| var NdnCommon = require('../../util/ndn-common.js').NdnCommon; |
| |
| /** |
| * A SelfVerifyPolicyManager implements a PolicyManager to look in the |
| * IdentityStorage for the public key with the name in the KeyLocator (if |
| * available) and use it to verify the data packet, without searching a |
| * certificate chain. If the public key can't be found, the verification fails. |
| * |
| * @param {IdentityStorage} identityStorage (optional) The IdentityStorage for |
| * looking up the public key. This object must remain valid during the life of |
| * this SelfVerifyPolicyManager. If omitted, then don't look for a public key |
| * with the name in the KeyLocator and rely on the KeyLocator having the full |
| * public key DER. |
| * @constructor |
| */ |
| var SelfVerifyPolicyManager = function SelfVerifyPolicyManager(identityStorage) |
| { |
| // Call the base constructor. |
| PolicyManager.call(this); |
| |
| this.identityStorage = identityStorage; |
| }; |
| |
| SelfVerifyPolicyManager.prototype = new PolicyManager(); |
| SelfVerifyPolicyManager.prototype.name = "SelfVerifyPolicyManager"; |
| |
| exports.SelfVerifyPolicyManager = SelfVerifyPolicyManager; |
| |
| /** |
| * Never skip verification. |
| * |
| * @param {Data|Interest} dataOrInterest The received data packet or interest. |
| * @return {boolean} False. |
| */ |
| SelfVerifyPolicyManager.prototype.skipVerifyAndTrust = function(dataOrInterest) |
| { |
| return false; |
| }; |
| |
| /** |
| * Always return true to use the self-verification rule for the received data. |
| * |
| * @param {Data|Interest} dataOrInterest The received data packet or interest. |
| * @return {boolean} True. |
| */ |
| SelfVerifyPolicyManager.prototype.requireVerify = function(dataOrInterest) |
| { |
| return true; |
| }; |
| |
| /** |
| * Look in the IdentityStorage for the public key with the name in the |
| * KeyLocator (if available) and use it to verify the data packet. If the |
| * public key can't be found, call onValidationFailed. |
| * |
| * @param {Data|Interest} dataOrInterest The Data object or interest with the |
| * signature to check. |
| * @param {number} stepCount The number of verification steps that have been |
| * done, used to track the verification progress. |
| * @param {function} onVerified If the signature is verified, this calls |
| * onVerified(dataOrInterest). |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onValidationFailed If the signature check fails, this calls |
| * onValidationFailed(dataOrInterest, reason). |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {WireFormat} wireFormat |
| * @return {ValidationRequest} null for no further step for looking up a |
| * certificate chain. |
| */ |
| SelfVerifyPolicyManager.prototype.checkVerificationPolicy = function |
| (dataOrInterest, stepCount, onVerified, onValidationFailed, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| |
| if (dataOrInterest instanceof Data) { |
| var data = dataOrInterest; |
| // wireEncode returns the cached encoding if available. |
| this.verify(data.getSignature(), data.wireEncode(), function(verified, reason) { |
| if (verified) { |
| try { |
| onVerified(data); |
| } catch (ex) { |
| console.log("Error in onVerified: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| else { |
| try { |
| onValidationFailed(data, reason); |
| } catch (ex) { |
| console.log("Error in onValidationFailed: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| }); |
| } |
| else if (dataOrInterest instanceof Interest) { |
| var interest = dataOrInterest; |
| |
| if (interest.getName().size() < 2) { |
| try { |
| onValidationFailed |
| (interest, "The signed interest has less than 2 components: " + |
| interest.getName().toUri()); |
| } catch (ex) { |
| console.log("Error in onValidationFailed: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| return; |
| } |
| |
| // Decode the last two name components of the signed interest |
| var signature; |
| try { |
| signature = wireFormat.decodeSignatureInfoAndValue |
| (interest.getName().get(-2).getValue().buf(), |
| interest.getName().get(-1).getValue().buf(), false); |
| } catch (ex) { |
| try { |
| onValidationFailed |
| (interest, "Error decoding the signed interest signature: " + ex); |
| } catch (ex) { |
| console.log("Error in onValidationFailed: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| return; |
| } |
| |
| // wireEncode returns the cached encoding if available. |
| this.verify(signature, interest.wireEncode(), function(verified, reason) { |
| if (verified) { |
| try { |
| onVerified(interest); |
| } catch (ex) { |
| console.log("Error in onVerified: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| else { |
| try { |
| onValidationFailed(interest, reason); |
| } catch (ex) { |
| console.log("Error in onValidationFailed: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| }); |
| } |
| else |
| throw new SecurityException(new Error |
| ("checkVerificationPolicy: unrecognized type for dataOrInterest")); |
| |
| // No more steps, so return null. |
| return null; |
| }; |
| |
| /** |
| * Override to always indicate that the signing certificate name and data name |
| * satisfy the signing policy. |
| * |
| * @param {Name} dataName The name of data to be signed. |
| * @param {Name} certificateName The name of signing certificate. |
| * @return {boolean} True to indicate that the signing certificate can be used |
| * to sign the data. |
| */ |
| SelfVerifyPolicyManager.prototype.checkSigningPolicy = function |
| (dataName, certificateName) |
| { |
| return true; |
| }; |
| |
| /** |
| * Override to indicate that the signing identity cannot be inferred. |
| * |
| * @param {Name} dataName The name of data to be signed. |
| * @return {Name} An empty name because cannot infer. |
| */ |
| SelfVerifyPolicyManager.prototype.inferSigningIdentity = function(dataName) |
| { |
| return new Name(); |
| }; |
| |
| /** |
| * Check the type of signatureInfo to get the KeyLocator. Look in the |
| * IdentityStorage for the public key with the name in the KeyLocator (if |
| * available) and use it to verify the signedBlob. If the public key can't be |
| * found, return false. (This is a generalized method which can verify both a |
| * Data packet and an interest.) |
| * @param {Signature} signatureInfo An object of a subclass of Signature, e.g. |
| * Sha256WithRsaSignature. |
| * @param {SignedBlob} signedBlob the SignedBlob with the signed portion to |
| * verify. |
| * @param {function} onComplete This calls onComplete(true, undefined) if the |
| * signature verifies, otherwise onComplete(false, reason). |
| */ |
| SelfVerifyPolicyManager.prototype.verify = function |
| (signatureInfo, signedBlob, onComplete) |
| { |
| if (KeyLocator.canGetFromSignature(signatureInfo)) { |
| this.getPublicKeyDer |
| (KeyLocator.getFromSignature(signatureInfo), function(publicKeyDer, reason) { |
| if (publicKeyDer.isNull()) |
| onComplete(false, reason); |
| else { |
| try { |
| PolicyManager.verifySignature |
| (signatureInfo, signedBlob, publicKeyDer, function(verified) { |
| if (verified) |
| onComplete(true); |
| else |
| onComplete |
| (false, |
| "The signature did not verify with the given public key"); |
| }); |
| } catch (ex) { |
| onComplete(false, "Error in verifySignature: " + ex); |
| } |
| } |
| }); |
| } |
| else { |
| try { |
| // Assume that the signature type does not require a public key. |
| PolicyManager.verifySignature |
| (signatureInfo, signedBlob, null, function(verified) { |
| if (verified) |
| onComplete(true); |
| else |
| onComplete |
| (false, "The signature did not verify with the given public key"); |
| }); |
| } catch (ex) { |
| onComplete(false, "Error in verifySignature: " + ex); |
| } |
| } |
| }; |
| |
| /** |
| * Look in the IdentityStorage for the public key with the name in the |
| * KeyLocator (if available). If the public key can't be found, return and empty |
| * Blob. |
| * @param {KeyLocator} keyLocator The KeyLocator. |
| * @param {function} onComplete This calls |
| * onComplete(publicKeyDer, reason) where publicKeyDer is the public key |
| * DER Blob or an isNull Blob if not found and reason is the reason |
| * string if not found. |
| */ |
| SelfVerifyPolicyManager.prototype.getPublicKeyDer = function |
| (keyLocator, onComplete) |
| { |
| if (keyLocator.getType() == KeyLocatorType.KEYNAME && |
| this.identityStorage != null) { |
| var keyName; |
| try { |
| // Assume the key name is a certificate name. |
| keyName = IdentityCertificate.certificateNameToPublicKeyName |
| (keyLocator.getKeyName()); |
| } catch (ex) { |
| onComplete |
| (new Blob(), "Cannot get a public key name from the certificate named: " + |
| keyLocator.getKeyName().toUri()); |
| return; |
| } |
| SyncPromise.complete |
| (onComplete, |
| function(err) { |
| // The storage doesn't have the key. |
| onComplete |
| (new Blob(), "The identityStorage doesn't have the key named " + |
| keyName.toUri()); |
| }, |
| this.identityStorage.getKeyPromise(keyName)); |
| } |
| else |
| // Can't find a key to verify. |
| onComplete(new Blob(), "The signature KeyLocator doesn't have a key name"); |
| }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * From ndn-cxx security by Yingdi Yu <yingdi@cs.ucla.edu>. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Crypto = require('../crypto.js'); /** @ignore */ |
| var Name = require('../name.js').Name; /** @ignore */ |
| var Interest = require('../interest.js').Interest; /** @ignore */ |
| var Data = require('../data.js').Data; /** @ignore */ |
| var Blob = require('../util/blob.js').Blob; /** @ignore */ |
| var WireFormat = require('../encoding/wire-format.js').WireFormat; /** @ignore */ |
| var SecurityException = require('./security-exception.js').SecurityException; /** @ignore */ |
| var RsaKeyParams = require('./key-params.js').RsaKeyParams; /** @ignore */ |
| var IdentityCertificate = require('./certificate/identity-certificate.js').IdentityCertificate; /** @ignore */ |
| var SyncPromise = require('../util/sync-promise.js').SyncPromise; /** @ignore */ |
| var NdnCommon = require('../util/ndn-common.js').NdnCommon; /** @ignore */ |
| var IdentityManager = require('./identity/identity-manager.js').IdentityManager; /** @ignore */ |
| var NoVerifyPolicyManager = require('./policy/no-verify-policy-manager.js').NoVerifyPolicyManager; |
| |
| /** |
| * A KeyChain provides a set of interfaces to the security library such as |
| * identity management, policy configuration and packet signing and verification. |
| * Note: This class is an experimental feature. See the API docs for more detail at |
| * http://named-data.net/doc/ndn-ccl-api/key-chain.html . |
| * |
| * Create a new KeyChain with the identityManager and policyManager. |
| * @param {IdentityManager} identityManager (optional) The identity manager as a |
| * subclass of IdentityManager. If omitted, use the default IdentityManager |
| * constructor. |
| * @param {PolicyManager} policyManager (optional) The policy manager as a |
| * subclass of PolicyManager. If omitted, use NoVerifyPolicyManager. |
| * @throws SecurityException if this is not in Node.js and this uses the default |
| * IdentityManager constructor. (See IdentityManager for details.) |
| * @constructor |
| */ |
| var KeyChain = function KeyChain(identityManager, policyManager) |
| { |
| if (!identityManager) |
| identityManager = new IdentityManager(); |
| if (!policyManager) |
| policyManager = new NoVerifyPolicyManager(); |
| |
| this.identityManager = identityManager; |
| this.policyManager = policyManager; |
| this.face = null; |
| }; |
| |
| exports.KeyChain = KeyChain; |
| |
| /***************************************** |
| * Identity Management * |
| *****************************************/ |
| |
| /** |
| * Create an identity by creating a pair of Key-Signing-Key (KSK) for this |
| * identity and a self-signed certificate of the KSK. If a key pair or |
| * certificate for the identity already exists, use it. |
| * @param {Name} identityName The name of the identity. |
| * @param {KeyParams} params (optional) The key parameters if a key needs to be |
| * generated for the identity. If omitted, use KeyChain.DEFAULT_KEY_PARAMS. |
| * @param {function} onComplete (optional) This calls onComplete(certificateName) |
| * with name of the default certificate of the identity. If omitted, the return |
| * value is described below. (Some crypto libraries only use a callback, so |
| * onComplete is required to use these.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @return {Name} If onComplete is omitted, return the name of the default |
| * certificate of the identity. Otherwise, if onComplete is supplied then return |
| * undefined and use onComplete as described above. |
| */ |
| KeyChain.prototype.createIdentityAndCertificate = function |
| (identityName, params, onComplete, onError) |
| { |
| onError = (typeof params === "function") ? onComplete : onError; |
| onComplete = (typeof params === "function") ? params : onComplete; |
| params = (typeof params === "function" || !params) ? |
| KeyChain.DEFAULT_KEY_PARAMS : params; |
| |
| return this.identityManager.createIdentityAndCertificate |
| (identityName, params, onComplete, onError); |
| }; |
| |
| /** |
| * Create an identity by creating a pair of Key-Signing-Key (KSK) for this |
| * identity and a self-signed certificate of the KSK. If a key pair or |
| * certificate for the identity already exists, use it. |
| * @deprecated Use createIdentityAndCertificate which returns the |
| * certificate name instead of the key name. You can use |
| * IdentityCertificate.certificateNameToPublicKeyName to convert the |
| * certificate name to the key name. |
| * @param {Name} identityName The name of the identity. |
| * @param {KeyParams} params (optional) The key parameters if a key needs to be |
| * generated for the identity. If omitted, use KeyChain.DEFAULT_KEY_PARAMS. |
| * @return {Name} The key name of the auto-generated KSK of the identity. |
| */ |
| KeyChain.prototype.createIdentity = function(identityName, params) |
| { |
| return IdentityCertificate.certificateNameToPublicKeyName |
| (this.createIdentityAndCertificate(identityName, params)); |
| }; |
| |
| /** |
| * Delete the identity from the public and private key storage. If the |
| * identity to be deleted is the current default system default, this will not |
| * delete the identity and will return immediately. |
| * @param {Name} identityName The name of the identity. |
| * @param {function} onComplete (optional) This calls onComplete() when the |
| * operation is complete. If omitted, do not use it. (Some database libraries |
| * only use a callback, so onComplete is required to use these.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| */ |
| KeyChain.prototype.deleteIdentity = function |
| (identityName, onComplete, onError) |
| { |
| this.identityManager.deleteIdentity(identityName, onComplete, onError); |
| }; |
| |
| /** |
| * Get the default identity. |
| * @param {function} onComplete (optional) This calls onComplete(identityName) |
| * with name of the default identity. If omitted, the return value is described |
| * below. (Some crypto libraries only use a callback, so onComplete is required |
| * to use these.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * @return {Name} If onComplete is omitted, return the name of the default |
| * identity. Otherwise, if onComplete is supplied then return undefined and use |
| * onComplete as described above. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @throws SecurityException if the default identity is not set. However, if |
| * onComplete and onError are defined, then if there is an exception return |
| * undefined and call onError(exception). |
| */ |
| KeyChain.prototype.getDefaultIdentity = function(onComplete, onError) |
| { |
| return this.identityManager.getDefaultIdentity(onComplete, onError); |
| }; |
| |
| /** |
| * Get the default certificate name of the default identity, which will be used |
| * when signing is based on identity and the identity is not specified. |
| * @param {function} onComplete (optional) This calls onComplete(certificateName) |
| * with name of the default certificate. If omitted, the return value is described |
| * below. (Some crypto libraries only use a callback, so onComplete is required |
| * to use these.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @return {Name} If onComplete is omitted, return the default certificate name. |
| * Otherwise, if onComplete is supplied then return undefined and use onComplete |
| * as described above. |
| * @throws SecurityException if the default identity is not set or the default |
| * key name for the identity is not set or the default certificate name for |
| * the key name is not set. However, if onComplete and onError are defined, then |
| * if there is an exception return undefined and call onError(exception). |
| */ |
| KeyChain.prototype.getDefaultCertificateName = function(onComplete, onError) |
| { |
| return this.identityManager.getDefaultCertificateName(onComplete, onError); |
| }; |
| |
| /** |
| * Generate a pair of RSA keys for the specified identity. |
| * @param {Name} identityName The name of the identity. |
| * @param {boolean} isKsk (optional) true for generating a Key-Signing-Key (KSK), |
| * false for a Data-Signing-Key (DSK). If omitted, generate a Data-Signing-Key. |
| * @param {number} keySize (optional) The size of the key. If omitted, use a |
| * default secure key size. |
| * @return {Name} The generated key name. |
| */ |
| KeyChain.prototype.generateRSAKeyPair = function(identityName, isKsk, keySize) |
| { |
| keySize = (typeof isKsk === "boolean") ? isKsk : keySize; |
| isKsk = (typeof isKsk === "boolean") ? isKsk : false; |
| |
| if (!keySize) |
| keySize = 2048; |
| |
| return this.identityManager.generateRSAKeyPair(identityName, isKsk, keySize); |
| }; |
| |
| /** |
| * Set a key as the default key of an identity. The identity name is inferred |
| * from keyName. |
| * @param {Name} keyName The name of the key. |
| * @param {Name} identityNameCheck (optional) The identity name to check that the |
| * keyName contains the same identity name. If an empty name, it is ignored. |
| * @param {function} onComplete (optional) This calls onComplete() when complete. |
| * (Some database libraries only use a callback, so onComplete is required to |
| * use these.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| */ |
| KeyChain.prototype.setDefaultKeyForIdentity = function |
| (keyName, identityNameCheck, onComplete, onError) |
| { |
| return this.identityManager.setDefaultKeyForIdentity |
| (keyName, identityNameCheck, onComplete, onError); |
| }; |
| |
| /** |
| * Generate a pair of RSA keys for the specified identity and set it as default |
| * key for the identity. |
| * @param {Name} identityName The name of the identity. |
| * @param {boolean} isKsk (optional) true for generating a Key-Signing-Key (KSK), |
| * false for a Data-Signing-Key (DSK). If omitted, generate a Data-Signing-Key. |
| * @param {number} keySize (optional) The size of the key. If omitted, use a |
| * default secure key size. |
| * @return {Name} The generated key name. |
| */ |
| KeyChain.prototype.generateRSAKeyPairAsDefault = function |
| (identityName, isKsk, keySize) |
| { |
| return this.identityManager.generateRSAKeyPairAsDefault |
| (identityName, isKsk, keySize); |
| }; |
| |
| /** |
| * Create a public key signing request. |
| * @param {Name} keyName The name of the key. |
| * @return {Blob} The signing request data. |
| */ |
| KeyChain.prototype.createSigningRequest = function(keyName) |
| { |
| return this.identityManager.getPublicKey(keyName).getKeyDer(); |
| }; |
| |
| /** |
| * Install an identity certificate into the public key identity storage. |
| * @param {IdentityCertificate} certificate The certificate to to added. |
| * @param {function} onComplete (optional) This calls onComplete() when complete. |
| * (Some database libraries only use a callback, so onComplete is required to |
| * use these.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| */ |
| KeyChain.prototype.installIdentityCertificate = function |
| (certificate, onComplete, onError) |
| { |
| this.identityManager.addCertificate(certificate, onComplete, onError); |
| }; |
| |
| /** |
| * Set the certificate as the default for its corresponding key. |
| * @param {IdentityCertificate} certificate The certificate. |
| * @param {function} onComplete (optional) This calls onComplete() when complete. |
| * (Some database libraries only use a callback, so onComplete is required to |
| * use these.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| */ |
| KeyChain.prototype.setDefaultCertificateForKey = function |
| (certificate, onComplete, onError) |
| { |
| this.identityManager.setDefaultCertificateForKey |
| (certificate, onComplete, onError); |
| }; |
| |
| /** |
| * Get a certificate which is still valid with the specified name. |
| * @param {Name} certificateName The name of the requested certificate. |
| * @param {function} onComplete (optional) This calls onComplete(certificate) |
| * with the requested IdentityCertificate. If omitted, the return value is |
| * described below. (Some crypto libraries only use a callback, so onComplete is |
| * required to use these.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @return {IdentityCertificate} If onComplete is omitted, return the requested |
| * certificate. Otherwise, if onComplete is supplied then return undefined and |
| * use onComplete as described above. |
| */ |
| KeyChain.prototype.getCertificate = function |
| (certificateName, onComplete, onError) |
| { |
| return this.identityManager.getCertificate |
| (certificateName, onComplete, onError); |
| }; |
| |
| /** |
| * @deprecated Use getCertificate. |
| */ |
| KeyChain.prototype.getIdentityCertificate = function |
| (certificateName, onComplete, onError) |
| { |
| return this.identityManager.getCertificate |
| (certificateName, onComplete, onError); |
| }; |
| |
| /** |
| * Revoke a key. |
| * @param {Name} keyName The name of the key that will be revoked. |
| */ |
| KeyChain.prototype.revokeKey = function(keyName) |
| { |
| //TODO: Implement |
| }; |
| |
| /** |
| * Revoke a certificate. |
| * @param {Name} certificateName The name of the certificate that will be |
| * revoked. |
| */ |
| KeyChain.prototype.revokeCertificate = function(certificateName) |
| { |
| //TODO: Implement |
| }; |
| |
| /** |
| * Get the identity manager given to or created by the constructor. |
| * @return {IdentityManager} The identity manager. |
| */ |
| KeyChain.prototype.getIdentityManager = function() |
| { |
| return this.identityManager; |
| }; |
| |
| /***************************************** |
| * Policy Management * |
| *****************************************/ |
| |
| /** |
| * Get the policy manager given to or created by the constructor. |
| * @return {PolicyManager} The policy manager. |
| */ |
| KeyChain.prototype.getPolicyManager = function() |
| { |
| return this.policyManager; |
| }; |
| |
| /***************************************** |
| * Sign/Verify * |
| *****************************************/ |
| |
| /** |
| * Sign the target. If it is a Data or Interest object, set its signature. If it |
| * is an array, produce a Signature object. There are two forms of sign: |
| * sign(target, certificateName [, wireFormat] [, onComplete] [, onError]). |
| * sign(target [, wireFormat] [, onComplete] [, onError]). |
| * @param {Data|Interest|Buffer} target If this is a Data object, wire encode for |
| * signing, update its signature and key locator field and wireEncoding. If this |
| * is an Interest object, wire encode for signing, append a SignatureInfo to the |
| * Interest name, sign the name components and append a final name component |
| * with the signature bits. If it is an array, sign it and produce a Signature |
| * object. |
| * @param {Name} certificateName (optional) The certificate name of the key to |
| * use for signing. If omitted, use the default identity in the identity storage. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * the input. If omitted, use WireFormat getDefaultWireFormat(). |
| * @param {function} onComplete (optional) If target is a Data object, this calls |
| * onComplete(data) with the supplied Data object which has been modified to set |
| * its signature. If target is an Interest object, this calls |
| * onComplete(interest) with the supplied Interest object which has been |
| * modified to set its signature. If target is a Buffer, this calls |
| * onComplete(signature) where signature is the produced Signature object. If |
| * omitted, the return value is described below. (Some crypto libraries only use |
| * a callback, so onComplete is required to use these.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @return {Signature} If onComplete is omitted, return the generated Signature |
| * object (if target is a Buffer) or the target (if target is Data or Interest). |
| * Otherwise, if onComplete is supplied then return undefined and use onComplete as |
| * described above. |
| */ |
| KeyChain.prototype.sign = function |
| (target, certificateName, wireFormat, onComplete, onError) |
| { |
| var arg2 = certificateName; |
| var arg3 = wireFormat; |
| var arg4 = onComplete; |
| var arg5 = onError; |
| // arg2, arg3, arg4, arg5 |
| // certificateName, wireFormat, onComplete, onError |
| // certificateName, wireFormat, null, null |
| // certificateName, onComplete, onError, null |
| // certificateName, null, null, null |
| // wireFormat, onComplete, onError, null |
| // wireFormat, null, null, null |
| // onComplete, onError, null, null |
| // null, null, null, null |
| if (arg2 instanceof Name) |
| certificateName = arg2; |
| else |
| certificateName = null; |
| |
| if (arg2 instanceof WireFormat) |
| wireFormat = arg2; |
| else if (arg3 instanceof WireFormat) |
| wireFormat = arg3; |
| else |
| wireFormat = null; |
| |
| if (typeof arg2 === "function") { |
| onComplete = arg2; |
| onError = arg3; |
| } |
| else if (typeof arg3 === "function") { |
| onComplete = arg3; |
| onError = arg4; |
| } |
| else if (typeof arg4 === "function") { |
| onComplete = arg4; |
| onError = arg5; |
| } |
| else { |
| onComplete = null; |
| onError = null; |
| } |
| |
| return SyncPromise.complete(onComplete, onError, |
| this.signPromise(target, certificateName, wireFormat, !onComplete)); |
| }; |
| |
| /** |
| * Sign the target. If it is a Data or Interest object, set its signature. If it |
| * is an array, produce a Signature object. There are two forms of signPromise: |
| * signPromise(target, certificateName [, wireFormat] [, useSync]). |
| * sign(target [, wireFormat] [, useSync]). |
| * @param {Data|Interest|Buffer} target If this is a Data object, wire encode for |
| * signing, update its signature and key locator field and wireEncoding. If this |
| * is an Interest object, wire encode for signing, append a SignatureInfo to the |
| * Interest name, sign the name components and append a final name component |
| * with the signature bits. If it is an array, sign it and produce a Signature |
| * object. |
| * @param {Name} certificateName (optional) The certificate name of the key to |
| * use for signing. If omitted, use the default identity in the identity storage. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * the input. If omitted, use WireFormat getDefaultWireFormat(). |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns the generated Signature |
| * object (if target is a Buffer) or the target (if target is Data or Interest). |
| */ |
| KeyChain.prototype.signPromise = function |
| (target, certificateName, wireFormat, useSync) |
| { |
| var arg2 = certificateName; |
| var arg3 = wireFormat; |
| var arg4 = useSync; |
| // arg2, arg3, arg4 |
| // certificateName, wireFormat, useSync |
| // certificateName, wireFormat, null |
| // certificateName, useSync, null |
| // certificateName, null, null |
| // wireFormat, useSync, null |
| // wireFormat, null, null |
| // useSync, null, null |
| // null, null, null |
| if (arg2 instanceof Name) |
| certificateName = arg2; |
| else |
| certificateName = null; |
| |
| if (arg2 instanceof WireFormat) |
| wireFormat = arg2; |
| else if (arg3 instanceof WireFormat) |
| wireFormat = arg3; |
| else |
| wireFormat = null; |
| |
| if (typeof arg2 === 'boolean') |
| useSync = arg2; |
| else if (typeof arg3 === 'boolean') |
| useSync = arg3; |
| else if (typeof arg4 === 'boolean') |
| useSync = arg4; |
| else |
| useSync = false; |
| |
| var thisKeyChain = this; |
| return SyncPromise.resolve() |
| .then(function() { |
| if (certificateName != null) |
| return SyncPromise.resolve(); |
| |
| // Get the default certificate name. |
| return thisKeyChain.identityManager.getDefaultCertificatePromise(useSync) |
| .then(function(signingCertificate) { |
| if (signingCertificate != null) { |
| certificateName = signingCertificate.getName(); |
| return SyncPromise.resolve(); |
| } |
| |
| // Set the default certificate and default certificate name again. |
| return thisKeyChain.prepareDefaultCertificateNamePromise_(useSync) |
| .then(function(localCertificateName) { |
| certificateName =localCertificateName; |
| return SyncPromise.resolve(); |
| }); |
| }); |
| }) |
| .then(function() { |
| // certificateName is now set. Do the actual signing. |
| if (target instanceof Interest) |
| return thisKeyChain.identityManager.signInterestByCertificatePromise |
| (target, certificateName, wireFormat, useSync); |
| else if (target instanceof Data) |
| return thisKeyChain.identityManager.signByCertificatePromise |
| (target, certificateName, wireFormat, useSync); |
| else |
| return thisKeyChain.identityManager.signByCertificatePromise |
| (target, certificateName, useSync); |
| }); |
| }; |
| |
| /** |
| * Sign the target. If it is a Data object, set its signature. If it is an |
| * array, produce a signature object. |
| * @param {Data|Buffer} target If this is a Data object, wire encode for |
| * signing, update its signature and key locator field and wireEncoding. If it |
| * is an array, sign it and return a Signature object. |
| * @param {Name} identityName (optional) The identity name for the key to use for |
| * signing. If omitted, infer the signing identity from the data packet name. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * the input. If omitted, use WireFormat getDefaultWireFormat(). |
| * @param {function} onComplete (optional) If target is a Data object, this calls |
| * onComplete(data) with the supplied Data object which has been modified to set |
| * its signature. If target is a Buffer, this calls |
| * onComplete(signature) where signature is the produced Signature object. If |
| * omitted, the return value is described below. (Some crypto libraries only use |
| * a callback, so onComplete is required to use these.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * with the exception. If onComplete is defined but onError is undefined, then |
| * this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @return {Signature} If onComplete is omitted, return the generated Signature |
| * object (if target is a Buffer) or undefined (if target is Data). |
| * Otherwise, if onComplete is supplied then return undefined and use onComplete |
| * as described above. |
| */ |
| KeyChain.prototype.signByIdentity = function |
| (target, identityName, wireFormat, onComplete, onError) |
| { |
| onError = (typeof wireFormat === "function") ? onComplete : onError; |
| onComplete = (typeof wireFormat === "function") ? wireFormat : onComplete; |
| wireFormat = (typeof wireFormat === "function" || !wireFormat) ? WireFormat.getDefaultWireFormat() : wireFormat; |
| |
| var useSync = !onComplete; |
| var thisKeyChain = this; |
| |
| if (identityName == null) |
| identityName = new Name(); |
| |
| if (target instanceof Data) { |
| var data = target; |
| |
| var mainPromise = SyncPromise.resolve() |
| .then(function() { |
| if (identityName.size() == 0) { |
| var inferredIdentity = thisKeyChain.policyManager.inferSigningIdentity |
| (data.getName()); |
| if (inferredIdentity.size() == 0) |
| return thisKeyChain.identityManager.getDefaultCertificateNamePromise |
| (useSync); |
| else |
| return thisKeyChain.identityManager.getDefaultCertificateNameForIdentityPromise |
| (inferredIdentity, useSync); |
| } |
| else |
| return thisKeyChain.identityManager.getDefaultCertificateNameForIdentityPromise |
| (identityName, useSync); |
| }) |
| .then(function(signingCertificateName) { |
| if (signingCertificateName.size() == 0) |
| throw new SecurityException(new Error |
| ("No qualified certificate name found!")); |
| |
| if (!thisKeyChain.policyManager.checkSigningPolicy |
| (data.getName(), signingCertificateName)) |
| throw new SecurityException(new Error |
| ("Signing Cert name does not comply with signing policy")); |
| |
| return thisKeyChain.identityManager.signByCertificatePromise |
| (data, signingCertificateName, wireFormat, useSync); |
| }); |
| |
| return SyncPromise.complete(onComplete, onError, mainPromise); |
| } |
| else { |
| var array = target; |
| |
| return SyncPromise.complete(onComplete, onError, |
| this.identityManager.getDefaultCertificateNameForIdentityPromise |
| (identityName, useSync) |
| .then(function(signingCertificateName) { |
| if (signingCertificateName.size() == 0) |
| throw new SecurityException(new Error |
| ("No qualified certificate name found!")); |
| |
| return thisKeyChain.identityManager.signByCertificatePromise |
| (array, signingCertificateName, wireFormat, useSync); |
| })); |
| } |
| }; |
| |
| /** |
| * Sign the target using DigestSha256. |
| * @param {Data|Interest} target If this is a Data object, wire encode for |
| * signing, digest it and set its SignatureInfo to a DigestSha256, updating its |
| * signature and wireEncoding. If this is an Interest object, wire encode for |
| * signing, append a SignatureInfo for DigestSha256 to the Interest name, digest |
| * the name components and append a final name component with the signature bits. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * the input. If omitted, use WireFormat getDefaultWireFormat(). |
| */ |
| KeyChain.prototype.signWithSha256 = function(target, wireFormat) |
| { |
| if (target instanceof Interest) |
| this.identityManager.signInterestWithSha256(target, wireFormat); |
| else |
| this.identityManager.signWithSha256(target, wireFormat); |
| }; |
| |
| /** |
| * Check the signature on the Data object and call either onVerify or |
| * onValidationFailed. We use callback functions because verify may fetch |
| * information to check the signature. |
| * @param {Data} data The Data object with the signature to check. |
| * @param {function} onVerified If the signature is verified, this calls |
| * onVerified(data). |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onValidationFailed If the signature check fails, this calls |
| * onValidationFailed(data, reason) with the Data object and reason string. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {number} stepCount |
| */ |
| KeyChain.prototype.verifyData = function |
| (data, onVerified, onValidationFailed, stepCount) |
| { |
| if (stepCount == null) |
| stepCount = 0; |
| |
| if (this.policyManager.requireVerify(data)) { |
| var nextStep = this.policyManager.checkVerificationPolicy |
| (data, stepCount, onVerified, onValidationFailed); |
| if (nextStep != null) { |
| var thisKeyChain = this; |
| this.face.expressInterest |
| (nextStep.interest, |
| function(callbackInterest, callbackData) { |
| thisKeyChain.onCertificateData(callbackInterest, callbackData, nextStep); |
| }, |
| function(callbackInterest) { |
| thisKeyChain.onCertificateInterestTimeout |
| (callbackInterest, nextStep.retry, onValidationFailed, data, nextStep); |
| }); |
| } |
| } |
| else if (this.policyManager.skipVerifyAndTrust(data)) { |
| try { |
| onVerified(data); |
| } catch (ex) { |
| console.log("Error in onVerified: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| else { |
| try { |
| onValidationFailed |
| (data, "The packet has no verify rule but skipVerifyAndTrust is false"); |
| } catch (ex) { |
| console.log("Error in onValidationFailed: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| }; |
| |
| /** |
| * Check the signature on the signed interest and call either onVerify or |
| * onValidationFailed. We use callback functions because verify may fetch |
| * information to check the signature. |
| * @param {Interest} interest The interest with the signature to check. |
| * @param {function} onVerified If the signature is verified, this calls |
| * onVerified(interest). |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onValidationFailed If the signature check fails, this calls |
| * onValidationFailed(interest, reason) with the Interest object and reason |
| * string. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| */ |
| KeyChain.prototype.verifyInterest = function |
| (interest, onVerified, onValidationFailed, stepCount, wireFormat) |
| { |
| if (stepCount == null) |
| stepCount = 0; |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| |
| if (this.policyManager.requireVerify(interest)) { |
| var nextStep = this.policyManager.checkVerificationPolicy |
| (interest, stepCount, onVerified, onValidationFailed, wireFormat); |
| if (nextStep != null) { |
| var thisKeyChain = this; |
| this.face.expressInterest |
| (nextStep.interest, |
| function(callbackInterest, callbackData) { |
| thisKeyChain.onCertificateData(callbackInterest, callbackData, nextStep); |
| }, |
| function(callbackInterest) { |
| thisKeyChain.onCertificateInterestTimeout |
| (callbackInterest, nextStep.retry, onValidationFailed, interest, |
| nextStep); |
| }); |
| } |
| } |
| else if (this.policyManager.skipVerifyAndTrust(interest)) { |
| try { |
| onVerified(interest); |
| } catch (ex) { |
| console.log("Error in onVerified: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| else { |
| try { |
| onValidationFailed |
| (interest, |
| "The packet has no verify rule but skipVerifyAndTrust is false"); |
| } catch (ex) { |
| console.log("Error in onValidationFailed: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| }; |
| |
| /** |
| * Set the Face which will be used to fetch required certificates. |
| * @param {Face} face A pointer to the Face object. |
| */ |
| KeyChain.prototype.setFace = function(face) |
| { |
| this.face = face; |
| }; |
| |
| /** |
| * Wire encode the target, compute an HmacWithSha256 and update the signature |
| * value. |
| * Note: This method is an experimental feature. The API may change. |
| * @param {Data} target If this is a Data object, update its signature and wire |
| * encoding. |
| * @param {Blob} key The key for the HmacWithSha256. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * the target. If omitted, use WireFormat getDefaultWireFormat(). |
| */ |
| KeyChain.signWithHmacWithSha256 = function(target, key, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| |
| if (target instanceof Data) { |
| var data = target; |
| // Encode once to get the signed portion. |
| var encoding = data.wireEncode(wireFormat); |
| |
| var signer = Crypto.createHmac('sha256', key.buf()); |
| signer.update(encoding.signedBuf()); |
| data.getSignature().setSignature( |
| new Blob(signer.digest(), false)); |
| } |
| else |
| throw new SecurityException(new Error |
| ("signWithHmacWithSha256: Unrecognized target type")); |
| }; |
| |
| /** |
| * Compute a new HmacWithSha256 for the target and verify it against the |
| * signature value. |
| * Note: This method is an experimental feature. The API may change. |
| * @param {Data} target The Data object to verify. |
| * @param {Blob} key The key for the HmacWithSha256. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * the target. If omitted, use WireFormat getDefaultWireFormat(). |
| * @return {boolean} True if the signature verifies, otherwise false. |
| */ |
| KeyChain.verifyDataWithHmacWithSha256 = function(data, key, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| |
| // wireEncode returns the cached encoding if available. |
| var encoding = data.wireEncode(wireFormat); |
| |
| var signer = Crypto.createHmac('sha256', key.buf()); |
| signer.update(encoding.signedBuf()); |
| var newSignatureBits = new Blob(signer.digest(), false); |
| |
| // Use the flexible Blob.equals operator. |
| return newSignatureBits.equals(data.getSignature().getSignature()); |
| }; |
| |
| KeyChain.DEFAULT_KEY_PARAMS = new RsaKeyParams(); |
| |
| KeyChain.prototype.onCertificateData = function(interest, data, nextStep) |
| { |
| // Try to verify the certificate (data) according to the parameters in nextStep. |
| this.verifyData |
| (data, nextStep.onVerified, nextStep.onValidationFailed, nextStep.stepCount); |
| }; |
| |
| KeyChain.prototype.onCertificateInterestTimeout = function |
| (interest, retry, onValidationFailed, originalDataOrInterest, nextStep) |
| { |
| if (retry > 0) { |
| // Issue the same expressInterest as in verifyData except decrement retry. |
| var thisKeyChain = this; |
| this.face.expressInterest |
| (interest, |
| function(callbackInterest, callbackData) { |
| thisKeyChain.onCertificateData(callbackInterest, callbackData, nextStep); |
| }, |
| function(callbackInterest) { |
| thisKeyChain.onCertificateInterestTimeout |
| (callbackInterest, retry - 1, onValidationFailed, |
| originalDataOrInterest, nextStep); |
| }); |
| } |
| else { |
| try { |
| onValidationFailed |
| (originalDataOrInterest, "The retry count is zero after timeout for fetching " + |
| interest.getName().toUri()); |
| } catch (ex) { |
| console.log("Error in onValidationFailed: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| }; |
| |
| /** |
| * Get the default certificate from the identity storage and return its name. |
| * If there is no default identity or default certificate, then create one. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns the default certificate |
| * name. |
| */ |
| KeyChain.prototype.prepareDefaultCertificateNamePromise_ = function(useSync) |
| { |
| var signingCertificate; |
| var thisKeyChain = this; |
| return this.identityManager.getDefaultCertificatePromise(useSync) |
| .then(function(localCertificate) { |
| signingCertificate = localCertificate; |
| if (signingCertificate != null) |
| return SyncPromise.resolve(); |
| |
| // Set the default certificate and get the certificate again. |
| return thisKeyChain.setDefaultCertificatePromise_(useSync) |
| .then(function() { |
| return thisKeyChain.identityManager.getDefaultCertificatePromise(useSync); |
| }) |
| .then(function(localCertificate) { |
| signingCertificate = localCertificate; |
| return SyncPromise.resolve(); |
| }); |
| }) |
| .then(function() { |
| return SyncPromise.resolve(signingCertificate.getName()); |
| }); |
| } |
| |
| /** |
| * Create the default certificate if it is not initialized. If there is no |
| * default identity yet, creating a new tmp-identity. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that resolves when the default |
| * certificate is set. |
| */ |
| KeyChain.prototype.setDefaultCertificatePromise_ = function(useSync) |
| { |
| var thisKeyChain = this; |
| |
| return this.identityManager.getDefaultCertificatePromise(useSync) |
| .then(function(certificate) { |
| if (certificate != null) |
| // We already have a default certificate. |
| return SyncPromise.resolve(); |
| |
| var defaultIdentity; |
| return thisKeyChain.identityManager.getDefaultIdentityPromise(useSync) |
| .then(function(localDefaultIdentity) { |
| defaultIdentity = localDefaultIdentity; |
| return SyncPromise.resolve(); |
| }, function(ex) { |
| // Create a default identity name. |
| randomComponent = Crypto.randomBytes(4); |
| defaultIdentity = new Name().append("tmp-identity") |
| .append(new Blob(randomComponent, false)); |
| |
| return SyncPromise.resolve(); |
| }) |
| .then(function() { |
| return thisKeyChain.identityManager.createIdentityAndCertificatePromise |
| (defaultIdentity, KeyChain.DEFAULT_KEY_PARAMS, useSync); |
| }) |
| .then(function() { |
| return thisKeyChain.identityManager.setDefaultIdentityPromise |
| (defaultIdentity, useSync); |
| }); |
| }); |
| }; |
| /** |
| * This class represents an Interest Exclude. |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Meki Cheraoui |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Name = require('./name.js').Name; /** @ignore */ |
| var DataUtils = require('./encoding/data-utils.js').DataUtils; /** @ignore */ |
| var Blob = require('./util/blob.js').Blob; |
| |
| /** |
| * Create a new Exclude. |
| * @constructor |
| * @param {Array<Name.Component|Buffer|Exclude.ANY>} values (optional) An array where each element is either a Name.Component, Buffer component or Exclude.ANY. |
| */ |
| var Exclude = function Exclude(values) |
| { |
| this.values = []; |
| |
| if (typeof values === 'object' && values instanceof Exclude) |
| // Copy the exclude. |
| this.values = values.values.slice(0); |
| else if (values) { |
| // Set the changeCount now since append expects it. |
| this.changeCount = 0; |
| for (var i = 0; i < values.length; ++i) { |
| if (values[i] == Exclude.ANY) |
| this.appendAny(); |
| else |
| this.appendComponent(values[i]); |
| } |
| } |
| |
| this.changeCount = 0; |
| }; |
| |
| exports.Exclude = Exclude; |
| |
| Exclude.ANY = "*"; |
| |
| /** |
| * Get the number of entries. |
| * @return {number} The number of entries. |
| */ |
| Exclude.prototype.size = function() { return this.values.length; }; |
| |
| /** |
| * Get the entry at the given index. |
| * @param {number} i The index of the entry, starting from 0. |
| * @return {Exclude.ANY|Name.Component} Exclude.ANY or a Name.Component. |
| */ |
| Exclude.prototype.get = function(i) { return this.values[i]; }; |
| |
| /** |
| * Append an Exclude.ANY element. |
| * @return This Exclude so that you can chain calls to append. |
| */ |
| Exclude.prototype.appendAny = function() |
| { |
| this.values.push(Exclude.ANY); |
| ++this.changeCount; |
| return this; |
| }; |
| |
| /** |
| * Append a component entry, copying from component. |
| * @param {Name.Component|Buffer} component |
| * @return This Exclude so that you can chain calls to append. |
| */ |
| Exclude.prototype.appendComponent = function(component) |
| { |
| this.values.push(new Name.Component(component)); |
| ++this.changeCount; |
| return this; |
| }; |
| |
| /** |
| * Clear all the entries. |
| */ |
| Exclude.prototype.clear = function() |
| { |
| ++this.changeCount; |
| this.values = []; |
| }; |
| |
| /** |
| * Return a string with elements separated by "," and Exclude.ANY shown as "*". |
| */ |
| Exclude.prototype.toUri = function() |
| { |
| if (this.values == null || this.values.length == 0) |
| return ""; |
| |
| var result = ""; |
| for (var i = 0; i < this.values.length; ++i) { |
| if (i > 0) |
| result += ","; |
| |
| if (this.values[i] == Exclude.ANY) |
| result += "*"; |
| else |
| result += this.values[i].toEscapedString(); |
| } |
| return result; |
| }; |
| |
| /** |
| * Return true if the component matches any of the exclude criteria. |
| */ |
| Exclude.prototype.matches = function(component) |
| { |
| if (!(typeof component == 'object' && component instanceof Name.Component)) |
| component = new Name.Component(component); |
| |
| for (var i = 0; i < this.values.length; ++i) { |
| if (this.values[i] == Exclude.ANY) { |
| var lowerBound = null; |
| if (i > 0) |
| lowerBound = this.values[i - 1]; |
| |
| // Find the upper bound, possibly skipping over multiple ANY in a row. |
| var iUpperBound; |
| var upperBound = null; |
| for (iUpperBound = i + 1; iUpperBound < this.values.length; ++iUpperBound) { |
| if (this.values[iUpperBound] != Exclude.ANY) { |
| upperBound = this.values[iUpperBound]; |
| break; |
| } |
| } |
| |
| // If lowerBound != null, we already checked component equals lowerBound on the last pass. |
| // If upperBound != null, we will check component equals upperBound on the next pass. |
| if (upperBound != null) { |
| if (lowerBound != null) { |
| if (component.compare(lowerBound) > 0 && |
| component.compare(upperBound) < 0) |
| return true; |
| } |
| else { |
| if (component.compare(upperBound) < 0) |
| return true; |
| } |
| |
| // Make i equal iUpperBound on the next pass. |
| i = iUpperBound - 1; |
| } |
| else { |
| if (lowerBound != null) { |
| if (component.compare(lowerBound) > 0) |
| return true; |
| } |
| else |
| // this.values has only ANY. |
| return true; |
| } |
| } |
| else { |
| if (component.equals(this.values[i])) |
| return true; |
| } |
| } |
| |
| return false; |
| }; |
| |
| /** |
| * Return -1 if component1 is less than component2, 1 if greater or 0 if equal. |
| * A component is less if it is shorter, otherwise if equal length do a byte comparison. |
| */ |
| Exclude.compareComponents = function(component1, component2) |
| { |
| if (typeof component1 == 'object' && component1 instanceof Name.Component) |
| component1 = component1.getValue().buf(); |
| if (typeof component2 == 'object' && component2 instanceof Name.Component) |
| component2 = component2.getValue().buf(); |
| |
| return Name.Component.compareBuffers(component1, component2); |
| }; |
| |
| /** |
| * Get the change count, which is incremented each time this object is changed. |
| * @return {number} The change count. |
| */ |
| Exclude.prototype.getChangeCount = function() |
| { |
| return this.changeCount; |
| }; |
| /** |
| * This class represents Interest Objects |
| * Copyright (C) 2013-2016 Regents of the University of California. |
| * @author: Meki Cheraoui |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Crypto = require('./crypto.js'); /** @ignore */ |
| var Blob = require('./util/blob.js').Blob; /** @ignore */ |
| var SignedBlob = require('./util/signed-blob.js').SignedBlob; /** @ignore */ |
| var ChangeCounter = require('./util/change-counter.js').ChangeCounter; /** @ignore */ |
| var Name = require('./name.js').Name; /** @ignore */ |
| var Exclude = require('./exclude.js').Exclude; /** @ignore */ |
| var Link = require('./link.js').Link; /** @ignore */ |
| var KeyLocator = require('./key-locator.js').KeyLocator; /** @ignore */ |
| var IncomingFaceId = require('./lp/incoming-face-id.js').IncomingFaceId; /** @ignore */ |
| var WireFormat = require('./encoding/wire-format.js').WireFormat; |
| |
| /** |
| * Create a new Interest with the optional values. |
| * |
| * @constructor |
| * @param {Name|Interest} nameOrInterest If this is an Interest, copy values from the interest and ignore the |
| * other arguments. Otherwise this is the optional name for the new Interest. |
| * @param {number} minSuffixComponents |
| * @param {number} maxSuffixComponents |
| */ |
| var Interest = function Interest |
| (nameOrInterest, minSuffixComponents, maxSuffixComponents, |
| publisherPublicKeyDigest, exclude, childSelector, answerOriginKind, scope, |
| interestLifetimeMilliseconds, nonce) |
| { |
| if (publisherPublicKeyDigest) |
| throw new Error |
| ("Interest constructor: PublisherPublicKeyDigest support has been removed."); |
| if (answerOriginKind) |
| throw new Error |
| ("Interest constructor: answerOriginKind support has been removed. Use setMustBeFresh()."); |
| if (scope) |
| throw new Error("Interest constructor: scope support has been removed."); |
| |
| if (typeof nameOrInterest === 'object' && nameOrInterest instanceof Interest) { |
| // Special case: this is a copy constructor. Ignore all but the first argument. |
| var interest = nameOrInterest; |
| // Copy the name. |
| this.name_ = new ChangeCounter(new Name(interest.getName())); |
| this.maxSuffixComponents_ = interest.maxSuffixComponents_; |
| this.minSuffixComponents_ = interest.minSuffixComponents_; |
| |
| this.keyLocator_ = new ChangeCounter(new KeyLocator(interest.getKeyLocator())); |
| this.exclude_ = new ChangeCounter(new Exclude(interest.getExclude())); |
| this.childSelector_ = interest.childSelector_; |
| this.mustBeFresh_ = interest.mustBeFresh_; |
| this.interestLifetimeMilliseconds_ = interest.interestLifetimeMilliseconds_; |
| this.nonce_ = interest.nonce_; |
| this.linkWireEncoding_ = interest.linkWireEncoding_; |
| this.linkWireEncodingFormat_ = interest.linkWireEncodingFormat_; |
| this.link_ = new ChangeCounter(null); |
| if (interest.link_.get() != null) |
| this.link_.set(new Link(interest.link_.get())); |
| this.selectedDelegationIndex_ = interest.selectedDelegationIndex_; |
| this.defaultWireEncoding_ = interest.getDefaultWireEncoding(); |
| this.defaultWireEncodingFormat_ = interest.defaultWireEncodingFormat_; |
| } |
| else { |
| this.name_ = new ChangeCounter(typeof nameOrInterest === 'object' && |
| nameOrInterest instanceof Name ? |
| new Name(nameOrInterest) : new Name()); |
| this.maxSuffixComponents_ = maxSuffixComponents; |
| this.minSuffixComponents_ = minSuffixComponents; |
| |
| this.keyLocator_ = new ChangeCounter(new KeyLocator()); |
| this.exclude_ = new ChangeCounter(typeof exclude === 'object' && exclude instanceof Exclude ? |
| new Exclude(exclude) : new Exclude()); |
| this.childSelector_ = childSelector; |
| this.mustBeFresh_ = true; |
| this.interestLifetimeMilliseconds_ = interestLifetimeMilliseconds; |
| this.nonce_ = typeof nonce === 'object' && nonce instanceof Blob ? |
| nonce : new Blob(nonce, true); |
| this.linkWireEncoding_ = new Blob(); |
| this.linkWireEncodingFormat_ = null; |
| this.link_ = new ChangeCounter(null); |
| this.selectedDelegationIndex_ = null; |
| this.defaultWireEncoding_ = new SignedBlob(); |
| this.defaultWireEncodingFormat_ = null; |
| } |
| |
| this.getNonceChangeCount_ = 0; |
| this.getDefaultWireEncodingChangeCount_ = 0; |
| this.changeCount_ = 0; |
| this.lpPacket_ = null; |
| }; |
| |
| exports.Interest = Interest; |
| |
| Interest.RECURSIVE_POSTFIX = "*"; |
| |
| Interest.CHILD_SELECTOR_LEFT = 0; |
| Interest.CHILD_SELECTOR_RIGHT = 1; |
| |
| /** |
| * Check if this interest's name matches the given name (using Name.match) and |
| * the given name also conforms to the interest selectors. |
| * @param {Name} name The name to check. |
| * @return {boolean} True if the name and interest selectors match, False otherwise. |
| */ |
| Interest.prototype.matchesName = function(name) |
| { |
| if (!this.getName().match(name)) |
| return false; |
| |
| if (this.minSuffixComponents_ != null && |
| // Add 1 for the implicit digest. |
| !(name.size() + 1 - this.getName().size() >= this.minSuffixComponents_)) |
| return false; |
| if (this.maxSuffixComponents_ != null && |
| // Add 1 for the implicit digest. |
| !(name.size() + 1 - this.getName().size() <= this.maxSuffixComponents_)) |
| return false; |
| if (this.getExclude() != null && name.size() > this.getName().size() && |
| this.getExclude().matches(name.get(this.getName().size()))) |
| return false; |
| |
| return true; |
| }; |
| |
| /** |
| * @deprecated Use matchesName. |
| */ |
| Interest.prototype.matches_name = function(/*Name*/ name) |
| { |
| return this.matchesName(name); |
| }; |
| |
| /** |
| * Check if the given Data packet can satisfy this Interest. This method |
| * considers the Name, MinSuffixComponents, MaxSuffixComponents, |
| * PublisherPublicKeyLocator, and Exclude. It does not consider the |
| * ChildSelector or MustBeFresh. This uses the given wireFormat to get the |
| * Data packet encoding for the full Name. |
| * @param {Data} data The Data packet to check. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * the Data packet to get its full Name. If omitted, use |
| * WireFormat.getDefaultWireFormat(). |
| * @return {boolean} True if the given Data packet can satisfy this Interest. |
| */ |
| Interest.prototype.matchesData = function(data, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| |
| // Imitate ndn-cxx Interest::matchesData. |
| var interestNameLength = this.getName().size(); |
| var dataName = data.getName(); |
| var fullNameLength = dataName.size() + 1; |
| |
| // Check MinSuffixComponents. |
| var hasMinSuffixComponents = (this.getMinSuffixComponents() != null); |
| var minSuffixComponents = |
| hasMinSuffixComponents ? this.getMinSuffixComponents() : 0; |
| if (!(interestNameLength + minSuffixComponents <= fullNameLength)) |
| return false; |
| |
| // Check MaxSuffixComponents. |
| var hasMaxSuffixComponents = (this.getMaxSuffixComponents() != null); |
| if (hasMaxSuffixComponents && |
| !(interestNameLength + this.getMaxSuffixComponents() >= fullNameLength)) |
| return false; |
| |
| // Check the prefix. |
| if (interestNameLength === fullNameLength) { |
| if (this.getName().get(-1).isImplicitSha256Digest()) { |
| if (!this.getName().equals(data.getFullName(wireFormat))) |
| return false; |
| } |
| else |
| // The Interest Name is the same length as the Data full Name, but the |
| // last component isn't a digest so there's no possibility of matching. |
| return false; |
| } |
| else { |
| // The Interest Name should be a strict prefix of the Data full Name. |
| if (!this.getName().isPrefixOf(dataName)) |
| return false; |
| } |
| |
| // Check the Exclude. |
| // The Exclude won't be violated if the Interest Name is the same as the |
| // Data full Name. |
| if (this.getExclude().size() > 0 && fullNameLength > interestNameLength) { |
| if (interestNameLength == fullNameLength - 1) { |
| // The component to exclude is the digest. |
| if (this.getExclude().matches |
| (data.getFullName(wireFormat).get(interestNameLength))) |
| return false; |
| } |
| else { |
| // The component to exclude is not the digest. |
| if (this.getExclude().matches(dataName.get(interestNameLength))) |
| return false; |
| } |
| } |
| |
| // Check the KeyLocator. |
| var publisherPublicKeyLocator = this.getKeyLocator(); |
| if (publisherPublicKeyLocator.getType()) { |
| var signature = data.getSignature(); |
| if (!KeyLocator.canGetFromSignature(signature)) |
| // No KeyLocator in the Data packet. |
| return false; |
| if (!publisherPublicKeyLocator.equals |
| (KeyLocator.getFromSignature(signature))) |
| return false; |
| } |
| |
| return true; |
| }; |
| |
| /** |
| * Return a new Interest with the same fields as this Interest. |
| */ |
| Interest.prototype.clone = function() |
| { |
| return new Interest(this); |
| }; |
| |
| /** |
| * Get the interest Name. |
| * @return {Name} The name. The name size() may be 0 if not specified. |
| */ |
| Interest.prototype.getName = function() { return this.name_.get(); }; |
| |
| /** |
| * Get the min suffix components. |
| * @return {number} The min suffix components, or null if not specified. |
| */ |
| Interest.prototype.getMinSuffixComponents = function() |
| { |
| return this.minSuffixComponents_; |
| }; |
| |
| /** |
| * Get the max suffix components. |
| * @return {number} The max suffix components, or null if not specified. |
| */ |
| Interest.prototype.getMaxSuffixComponents = function() |
| { |
| return this.maxSuffixComponents_; |
| }; |
| |
| /** |
| * Get the interest key locator. |
| * @return {KeyLocator} The key locator. If its getType() is null, |
| * then the key locator is not specified. |
| */ |
| Interest.prototype.getKeyLocator = function() |
| { |
| return this.keyLocator_.get(); |
| }; |
| |
| /** |
| * Get the exclude object. |
| * @return {Exclude} The exclude object. If the exclude size() is zero, then |
| * the exclude is not specified. |
| */ |
| Interest.prototype.getExclude = function() { return this.exclude_.get(); }; |
| |
| /** |
| * Get the child selector. |
| * @return {number} The child selector, or null if not specified. |
| */ |
| Interest.prototype.getChildSelector = function() |
| { |
| return this.childSelector_; |
| }; |
| |
| /** |
| * Get the must be fresh flag. If not specified, the default is true. |
| * @return {boolean} The must be fresh flag. |
| */ |
| Interest.prototype.getMustBeFresh = function() |
| { |
| return this.mustBeFresh_; |
| }; |
| |
| /** |
| * Return the nonce value from the incoming interest. If you change any of the |
| * fields in this Interest object, then the nonce value is cleared. |
| * @return {Blob} The nonce. If not specified, the value isNull(). |
| */ |
| Interest.prototype.getNonce = function() |
| { |
| if (this.getNonceChangeCount_ != this.getChangeCount()) { |
| // The values have changed, so the existing nonce is invalidated. |
| this.nonce_ = new Blob(); |
| this.getNonceChangeCount_ = this.getChangeCount(); |
| } |
| |
| return this.nonce_; |
| }; |
| |
| /** |
| * @deprecated Use getNonce. This method returns a Buffer which is the former |
| * behavior of getNonce, and should only be used while updating your code. |
| */ |
| Interest.prototype.getNonceAsBuffer = function() |
| { |
| return this.getNonce().buf(); |
| }; |
| |
| /** |
| * Check if this interest has a link object (or a link wire encoding which |
| * can be decoded to make the link object). |
| * @return {boolean} True if this interest has a link object, false if not. |
| */ |
| Interest.prototype.hasLink = function() |
| { |
| return this.link_.get() != null || !this.linkWireEncoding_.isNull(); |
| }; |
| |
| /** |
| * Get the link object. If necessary, decode it from the link wire encoding. |
| * @return {Link} The link object, or null if not specified. |
| * @throws DecodingException For error decoding the link wire encoding (if |
| * necessary). |
| */ |
| Interest.prototype.getLink = function() |
| { |
| if (this.link_.get() != null) |
| return this.link_.get(); |
| else if (!this.linkWireEncoding_.isNull()) { |
| // Decode the link object from linkWireEncoding_. |
| var link = new Link(); |
| link.wireDecode(this.linkWireEncoding_, this.linkWireEncodingFormat_); |
| this.link_.set(link); |
| |
| // Clear linkWireEncoding_ since it is now managed by the link object. |
| this.linkWireEncoding_ = new Blob(); |
| this.linkWireEncodingFormat_ = null; |
| |
| return link; |
| } |
| else |
| return null; |
| }; |
| |
| /** |
| * Get the wire encoding of the link object. If there is already a wire |
| * encoding then return it. Otherwise encode from the link object (if |
| * available). |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * the Link. If omitted, use WireFormat.getDefaultWireFormat(). |
| * @return {Blob} The wire encoding, or an isNull Blob if the link is not |
| * specified. |
| */ |
| Interest.prototype.getLinkWireEncoding = function(wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| |
| if (!this.linkWireEncoding_.isNull() && this.linkWireEncodingFormat_ == wireFormat) |
| return this.linkWireEncoding_; |
| |
| var link = this.getLink(); |
| if (link != null) |
| return link.wireEncode(wireFormat); |
| else |
| return new Blob(); |
| }; |
| |
| /** |
| * Get the selected delegation index. |
| * @return {number} The selected delegation index. If not specified, return null. |
| */ |
| Interest.prototype.getSelectedDelegationIndex = function() |
| { |
| return this.selectedDelegationIndex_; |
| }; |
| |
| /** |
| * Get the interest lifetime. |
| * @return {number} The interest lifetime in milliseconds, or null if not |
| * specified. |
| */ |
| Interest.prototype.getInterestLifetimeMilliseconds = function() |
| { |
| return this.interestLifetimeMilliseconds_; |
| }; |
| |
| /** |
| * Return the default wire encoding, which was encoded with |
| * getDefaultWireEncodingFormat(). |
| * @return {SignedBlob} The default wire encoding, whose isNull() may be true |
| * if there is no default wire encoding. |
| */ |
| Interest.prototype.getDefaultWireEncoding = function() |
| { |
| if (this.getDefaultWireEncodingChangeCount_ != this.getChangeCount()) { |
| // The values have changed, so the default wire encoding is invalidated. |
| this.defaultWireEncoding_ = new SignedBlob(); |
| this.defaultWireEncodingFormat_ = null; |
| this.getDefaultWireEncodingChangeCount_ = this.getChangeCount(); |
| } |
| |
| return this.defaultWireEncoding_; |
| }; |
| |
| /** |
| * Get the WireFormat which is used by getDefaultWireEncoding(). |
| * @return {WireFormat} The WireFormat, which is only meaningful if the |
| * getDefaultWireEncoding() is not isNull(). |
| */ |
| Interest.prototype.getDefaultWireEncodingFormat = function() |
| { |
| return this.defaultWireEncodingFormat_; |
| }; |
| |
| /** |
| * Get the incoming face ID according to the incoming packet header. |
| * @return {number} The incoming face ID. If not specified, return null. |
| */ |
| Interest.prototype.getIncomingFaceId = function() |
| { |
| var field = |
| this.lpPacket_ === null ? null : IncomingFaceId.getFirstHeader(this.lpPacket_); |
| return field === null ? null : field.getFaceId(); |
| }; |
| |
| /** |
| * Set the interest name. |
| * Note: You can also call getName and change the name values directly. |
| * @param {Name} name The interest name. This makes a copy of the name. |
| * @return {Interest} This Interest so that you can chain calls to update values. |
| */ |
| Interest.prototype.setName = function(name) |
| { |
| this.name_.set(typeof name === 'object' && name instanceof Name ? |
| new Name(name) : new Name()); |
| ++this.changeCount_; |
| return this; |
| }; |
| |
| /** |
| * Set the min suffix components count. |
| * @param {number} minSuffixComponents The min suffix components count. If not |
| * specified, set to undefined. |
| * @return {Interest} This Interest so that you can chain calls to update values. |
| */ |
| Interest.prototype.setMinSuffixComponents = function(minSuffixComponents) |
| { |
| this.minSuffixComponents_ = minSuffixComponents; |
| ++this.changeCount_; |
| return this; |
| }; |
| |
| /** |
| * Set the max suffix components count. |
| * @param {number} maxSuffixComponents The max suffix components count. If not |
| * specified, set to undefined. |
| * @return {Interest} This Interest so that you can chain calls to update values. |
| */ |
| Interest.prototype.setMaxSuffixComponents = function(maxSuffixComponents) |
| { |
| this.maxSuffixComponents_ = maxSuffixComponents; |
| ++this.changeCount_; |
| return this; |
| }; |
| |
| /** |
| * Set this interest to use a copy of the given KeyLocator object. |
| * Note: You can also call getKeyLocator and change the key locator directly. |
| * @param {KeyLocator} keyLocator The KeyLocator object. This makes a copy of the object. |
| * If no key locator is specified, set to a new default KeyLocator(), or to a |
| * KeyLocator with an unspecified type. |
| * @return {Interest} This Interest so that you can chain calls to update values. |
| */ |
| Interest.prototype.setKeyLocator = function(keyLocator) |
| { |
| this.keyLocator_.set |
| (typeof keyLocator === 'object' && keyLocator instanceof KeyLocator ? |
| new KeyLocator(keyLocator) : new KeyLocator()); |
| ++this.changeCount_; |
| return this; |
| }; |
| |
| /** |
| * Set this interest to use a copy of the given exclude object. Note: You can |
| * also call getExclude and change the exclude entries directly. |
| * @param {Exclude} exclude The Exclude object. This makes a copy of the object. |
| * If no exclude is specified, set to a new default Exclude(), or to an Exclude |
| * with size() 0. |
| * @return {Interest} This Interest so that you can chain calls to update values. |
| */ |
| Interest.prototype.setExclude = function(exclude) |
| { |
| this.exclude_.set(typeof exclude === 'object' && exclude instanceof Exclude ? |
| new Exclude(exclude) : new Exclude()); |
| ++this.changeCount_; |
| return this; |
| }; |
| |
| /** |
| * Set the link wire encoding bytes, without decoding them. If there is |
| * a link object, set it to null. If you later call getLink(), it will |
| * decode the wireEncoding to create the link object. |
| * @param {Blob} encoding The Blob with the bytes of the link wire encoding. |
| * If no link is specified, set to an empty Blob() or call unsetLink(). |
| * @param {WireFormat} wireFormat The wire format of the encoding, to be used |
| * later if necessary to decode. If omitted, use WireFormat.getDefaultWireFormat(). |
| * @return {Interest} This Interest so that you can chain calls to update values. |
| */ |
| Interest.prototype.setLinkWireEncoding = function(encoding, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| |
| this.linkWireEncoding_ = encoding; |
| this.linkWireEncodingFormat_ = wireFormat; |
| |
| // Clear the link object, assuming that it has a different encoding. |
| this.link_.set(null); |
| |
| ++this.changeCount_; |
| return this; |
| }; |
| |
| /** |
| * Clear the link wire encoding and link object so that getLink() returns null. |
| * @return {Interest} This Interest so that you can chain calls to update values. |
| */ |
| Interest.prototype.unsetLink = function() |
| { |
| return this.setLinkWireEncoding(new Blob(), null); |
| }; |
| |
| /** |
| * Set the selected delegation index. |
| * @param {number} selectedDelegationIndex The selected delegation index. If not |
| * specified, set to null. |
| * @return {Interest} This Interest so that you can chain calls to update values. |
| */ |
| Interest.prototype.setSelectedDelegationIndex = function(selectedDelegationIndex) |
| { |
| this.selectedDelegationIndex_ = selectedDelegationIndex; |
| ++this.changeCount_; |
| return this; |
| }; |
| |
| /** |
| * Set the child selector. |
| * @param {number} childSelector The child selector. If not specified, set to |
| * undefined. |
| * @return {Interest} This Interest so that you can chain calls to update values. |
| */ |
| Interest.prototype.setChildSelector = function(childSelector) |
| { |
| this.childSelector_ = childSelector; |
| ++this.changeCount_; |
| return this; |
| }; |
| |
| /** |
| * Set the MustBeFresh flag. |
| * @param {boolean} mustBeFresh True if the content must be fresh, otherwise |
| * false. If you do not set this flag, the default value is true. |
| * @return {Interest} This Interest so that you can chain calls to update values. |
| */ |
| Interest.prototype.setMustBeFresh = function(mustBeFresh) |
| { |
| this.mustBeFresh_ = (mustBeFresh ? true : false); |
| ++this.changeCount_; |
| return this; |
| }; |
| |
| /** |
| * Set the interest lifetime. |
| * @param {number} interestLifetimeMilliseconds The interest lifetime in |
| * milliseconds. If not specified, set to undefined. |
| * @return {Interest} This Interest so that you can chain calls to update values. |
| */ |
| Interest.prototype.setInterestLifetimeMilliseconds = function(interestLifetimeMilliseconds) |
| { |
| this.interestLifetimeMilliseconds_ = interestLifetimeMilliseconds; |
| ++this.changeCount_; |
| return this; |
| }; |
| |
| /** |
| * @deprecated You should let the wire encoder generate a random nonce |
| * internally before sending the interest. |
| */ |
| Interest.prototype.setNonce = function(nonce) |
| { |
| this.nonce_ = typeof nonce === 'object' && nonce instanceof Blob ? |
| nonce : new Blob(nonce, true); |
| // Set getNonceChangeCount_ so that the next call to getNonce() won't clear |
| // this.nonce_. |
| ++this.changeCount_; |
| this.getNonceChangeCount_ = this.getChangeCount(); |
| return this; |
| }; |
| |
| /** |
| * Encode the name according to the "NDN URI Scheme". If there are interest selectors, append "?" and |
| * added the selectors as a query string. For example "/test/name?ndn.ChildSelector=1". |
| * Note: This is an experimental feature. See the API docs for more detail at |
| * http://named-data.net/doc/ndn-ccl-api/interest.html#interest-touri-method . |
| * @return {string} The URI string. |
| */ |
| Interest.prototype.toUri = function() |
| { |
| var selectors = ""; |
| |
| if (this.minSuffixComponents_ != null) |
| selectors += "&ndn.MinSuffixComponents=" + this.minSuffixComponents_; |
| if (this.maxSuffixComponents_ != null) |
| selectors += "&ndn.MaxSuffixComponents=" + this.maxSuffixComponents_; |
| if (this.childSelector_ != null) |
| selectors += "&ndn.ChildSelector=" + this.childSelector_; |
| selectors += "&ndn.MustBeFresh=" + (this.mustBeFresh_ ? 1 : 0); |
| if (this.interestLifetimeMilliseconds_ != null) |
| selectors += "&ndn.InterestLifetime=" + this.interestLifetimeMilliseconds_; |
| if (this.getNonce().size() > 0) |
| selectors += "&ndn.Nonce=" + Name.toEscapedString(this.getNonce().buf()); |
| if (this.getExclude() != null && this.getExclude().size() > 0) |
| selectors += "&ndn.Exclude=" + this.getExclude().toUri(); |
| |
| var result = this.getName().toUri(); |
| if (selectors != "") |
| // Replace the first & with ?. |
| result += "?" + selectors.substr(1); |
| |
| return result; |
| }; |
| |
| /** |
| * Encode this Interest for a particular wire format. If wireFormat is the |
| * default wire format, also set the defaultWireEncoding field to the encoded |
| * result. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * this object. If omitted, use WireFormat.getDefaultWireFormat(). |
| * @return {SignedBlob} The encoded buffer in a SignedBlob object. |
| */ |
| Interest.prototype.wireEncode = function(wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| |
| if (!this.getDefaultWireEncoding().isNull() && |
| this.getDefaultWireEncodingFormat() == wireFormat) |
| // We already have an encoding in the desired format. |
| return this.getDefaultWireEncoding(); |
| |
| var result = wireFormat.encodeInterest(this); |
| var wireEncoding = new SignedBlob |
| (result.encoding, result.signedPortionBeginOffset, |
| result.signedPortionEndOffset); |
| |
| if (wireFormat == WireFormat.getDefaultWireFormat()) |
| // This is the default wire encoding. |
| this.setDefaultWireEncoding |
| (wireEncoding, WireFormat.getDefaultWireFormat()); |
| return wireEncoding; |
| }; |
| |
| /** |
| * Decode the input using a particular wire format and update this Interest. If |
| * wireFormat is the default wire format, also set the defaultWireEncoding to |
| * another pointer to the input. |
| * @param {Blob|Buffer} input The buffer with the bytes to decode. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to decode |
| * this object. If omitted, use WireFormat.getDefaultWireFormat(). |
| */ |
| Interest.prototype.wireDecode = function(input, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| |
| var result; |
| if (typeof input === 'object' && input instanceof Blob) |
| // Input is a blob, so get its buf() and set copy false. |
| result = wireFormat.decodeInterest(this, input.buf(), false); |
| else |
| result = wireFormat.decodeInterest(this, input, true); |
| |
| if (wireFormat == WireFormat.getDefaultWireFormat()) |
| // This is the default wire encoding. In the Blob constructor, set copy |
| // true, but if input is already a Blob, it won't copy. |
| this.setDefaultWireEncoding(new SignedBlob |
| (new Blob(input, true), result.signedPortionBeginOffset, |
| result.signedPortionEndOffset), |
| WireFormat.getDefaultWireFormat()); |
| else |
| this.setDefaultWireEncoding(new SignedBlob(), null); |
| }; |
| |
| /** |
| * Update the bytes of the nonce with new random values. This ensures that the |
| * new nonce value is different than the current one. If the current nonce is |
| * not specified, this does nothing. |
| */ |
| Interest.prototype.refreshNonce = function() |
| { |
| var currentNonce = this.getNonce(); |
| if (currentNonce.size() === 0) |
| return; |
| |
| var newNonce; |
| while (true) { |
| newNonce = new Blob(Crypto.randomBytes(currentNonce.size()), false); |
| if (!newNonce.equals(currentNonce)) |
| break; |
| } |
| |
| this.nonce_ = newNonce; |
| // Set getNonceChangeCount_ so that the next call to getNonce() won't clear |
| // this.nonce_. |
| ++this.changeCount_; |
| this.getNonceChangeCount_ = this.getChangeCount(); |
| }; |
| |
| /** |
| * An internal library method to set the LpPacket for an incoming packet. The |
| * application should not call this. |
| * @param {LpPacket} lpPacket The LpPacket. This does not make a copy. |
| * @return {Interest} This Interest so that you can chain calls to update values. |
| * @note This is an experimental feature. This API may change in the future. |
| */ |
| Interest.prototype.setLpPacket = function(lpPacket) |
| { |
| this.lpPacket_ = lpPacket; |
| // Don't update changeCount_ since this doesn't affect the wire encoding. |
| return this; |
| } |
| |
| /** |
| * Get the change count, which is incremented each time this object (or a child |
| * object) is changed. |
| * @return {number} The change count. |
| */ |
| Interest.prototype.getChangeCount = function() |
| { |
| // Make sure each of the checkChanged is called. |
| var changed = this.name_.checkChanged(); |
| changed = this.keyLocator_.checkChanged() || changed; |
| changed = this.exclude_.checkChanged() || changed; |
| if (changed) |
| // A child object has changed, so update the change count. |
| ++this.changeCount_; |
| |
| return this.changeCount_; |
| }; |
| |
| Interest.prototype.setDefaultWireEncoding = function |
| (defaultWireEncoding, defaultWireEncodingFormat) |
| { |
| this.defaultWireEncoding_ = defaultWireEncoding; |
| this.defaultWireEncodingFormat_ = defaultWireEncodingFormat; |
| // Set getDefaultWireEncodingChangeCount_ so that the next call to |
| // getDefaultWireEncoding() won't clear _defaultWireEncoding. |
| this.getDefaultWireEncodingChangeCount_ = this.getChangeCount(); |
| }; |
| |
| // Define properties so we can change member variable types and implement changeCount_. |
| Object.defineProperty(Interest.prototype, "name", |
| { get: function() { return this.getName(); }, |
| set: function(val) { this.setName(val); } }); |
| Object.defineProperty(Interest.prototype, "minSuffixComponents", |
| { get: function() { return this.getMinSuffixComponents(); }, |
| set: function(val) { this.setMinSuffixComponents(val); } }); |
| Object.defineProperty(Interest.prototype, "maxSuffixComponents", |
| { get: function() { return this.getMaxSuffixComponents(); }, |
| set: function(val) { this.setMaxSuffixComponents(val); } }); |
| Object.defineProperty(Interest.prototype, "keyLocator", |
| { get: function() { return this.getKeyLocator(); }, |
| set: function(val) { this.setKeyLocator(val); } }); |
| Object.defineProperty(Interest.prototype, "exclude", |
| { get: function() { return this.getExclude(); }, |
| set: function(val) { this.setExclude(val); } }); |
| Object.defineProperty(Interest.prototype, "childSelector", |
| { get: function() { return this.getChildSelector(); }, |
| set: function(val) { this.setChildSelector(val); } }); |
| /** |
| * @deprecated Use getInterestLifetimeMilliseconds and setInterestLifetimeMilliseconds. |
| */ |
| Object.defineProperty(Interest.prototype, "interestLifetime", |
| { get: function() { return this.getInterestLifetimeMilliseconds(); }, |
| set: function(val) { this.setInterestLifetimeMilliseconds(val); } }); |
| /** |
| * @deprecated Use getNonce and setNonce. |
| */ |
| Object.defineProperty(Interest.prototype, "nonce", |
| { get: function() { return this.getNonceAsBuffer(); }, |
| set: function(val) { this.setNonce(val); } }); |
| /** |
| * Copyright (C) 2013-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * A ForwardingFlags object holds the flags which specify how the forwarding daemon should forward an interest for |
| * a registered prefix. We use a separate ForwardingFlags object to retain future compatibility if the daemon forwarding |
| * bits are changed, amended or deprecated. |
| * Create a new ForwardingFlags with "childInherit" set and all other flags cleared. |
| * @constructor |
| */ |
| var ForwardingFlags = function ForwardingFlags(value) |
| { |
| if (typeof value === 'object' && value instanceof ForwardingFlags) { |
| // Make a copy. |
| this.childInherit = value.childInherit; |
| this.capture = value.capture; |
| } |
| else { |
| this.childInherit = true; |
| this.capture = false; |
| } |
| }; |
| |
| exports.ForwardingFlags = ForwardingFlags; |
| |
| ForwardingFlags.NfdForwardingFlags_CHILD_INHERIT = 1; |
| ForwardingFlags.NfdForwardingFlags_CAPTURE = 2; |
| |
| /** |
| * Get an integer with the bits set according to the NFD forwarding flags as |
| * used in the ControlParameters of the command interest. |
| * @return {number} An integer with the bits set. |
| */ |
| ForwardingFlags.prototype.getNfdForwardingFlags = function() |
| { |
| var result = 0; |
| |
| if (this.childInherit) |
| result |= ForwardingFlags.NfdForwardingFlags_CHILD_INHERIT; |
| if (this.capture) |
| result |= ForwardingFlags.NfdForwardingFlags_CAPTURE; |
| |
| return result; |
| }; |
| |
| /** |
| * Set the flags according to the NFD forwarding flags as used in the |
| * ControlParameters of the command interest. |
| * @param {number} nfdForwardingFlags An integer with the bits set. |
| */ |
| ForwardingFlags.prototype.setNfdForwardingFlags = function(nfdForwardingFlags) |
| { |
| this.childInherit = |
| ((nfdForwardingFlags & ForwardingFlags.NfdForwardingFlags_CHILD_INHERIT) != 0); |
| this.capture = |
| ((nfdForwardingFlags & ForwardingFlags.NfdForwardingFlags_CAPTURE) != 0); |
| }; |
| |
| /** |
| * Get the value of the "childInherit" flag. |
| * @return {Boolean} true if the flag is set, false if it is cleared. |
| */ |
| ForwardingFlags.prototype.getChildInherit = function() { return this.childInherit; }; |
| |
| /** |
| * Get the value of the "capture" flag. |
| * @return {Boolean} true if the flag is set, false if it is cleared. |
| */ |
| ForwardingFlags.prototype.getCapture = function() { return this.capture; }; |
| |
| /** |
| * Set the value of the "childInherit" flag |
| * @param {number} value true to set the flag, false to clear it. |
| */ |
| ForwardingFlags.prototype.setChildInherit = function(value) { this.childInherit = value; }; |
| |
| /** |
| * Set the value of the "capture" flag |
| * @param {number} value true to set the flag, false to clear it. |
| */ |
| ForwardingFlags.prototype.setCapture = function(value) { this.capture = value; }; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var ForwardingFlags = require('./forwarding-flags.js').ForwardingFlags; /** @ignore */ |
| var Name = require('./name.js').Name; /** @ignore */ |
| var WireFormat = require('./encoding/wire-format.js').WireFormat; /** @ignore */ |
| var Blob = require('./util/blob.js').Blob; |
| |
| /** |
| * A ControlParameters which holds a Name and other fields for a |
| * ControlParameters which is used, for example, in the command interest to |
| * register a prefix with a forwarder. See |
| * http://redmine.named-data.net/projects/nfd/wiki/ControlCommand#ControlParameters |
| * @constructor |
| */ |
| var ControlParameters = function ControlParameters(value) |
| { |
| if (typeof value === 'object' && value instanceof ControlParameters) { |
| // Make a deep copy. |
| this.name = value.name == null ? null : new Name(value.name); |
| this.faceId = value.faceId; |
| this.uri = value.uri; |
| this.localControlFeature = value.localControlFeature; |
| this.origin = value.origin; |
| this.cost = value.cost; |
| this.forwardingFlags = new ForwardingFlags(value.forwardingFlags); |
| this.strategy = new Name(value.strategy); |
| this.expirationPeriod = value.expirationPeriod; |
| } |
| else { |
| this.name = null; |
| this.faceId = null; |
| this.uri = ''; |
| this.localControlFeature = null; |
| this.origin = null; |
| this.cost = null; |
| this.forwardingFlags = new ForwardingFlags(); |
| this.strategy = new Name(); |
| this.expirationPeriod = null; |
| } |
| }; |
| |
| exports.ControlParameters = ControlParameters; |
| |
| ControlParameters.prototype.clear = function() |
| { |
| this.name = null; |
| this.faceId = null; |
| this.uri = ''; |
| this.localControlFeature = null; |
| this.origin = null; |
| this.cost = null; |
| this.forwardingFlags = new ForwardingFlags(); |
| this.strategy = new Name(); |
| this.expirationPeriod = null; |
| }; |
| |
| /** |
| * Encode this ControlParameters for a particular wire format. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * this object. If omitted, use WireFormat.getDefaultWireFormat(). |
| * @return {Blob} The encoded buffer in a Blob object. |
| */ |
| ControlParameters.prototype.wireEncode = function(wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| return wireFormat.encodeControlParameters(this); |
| }; |
| |
| /** |
| * Decode the input using a particular wire format and update this |
| * ControlParameters. |
| * @param {Blob|Buffer} input The buffer with the bytes to decode. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to decode |
| * this object. If omitted, use WireFormat.getDefaultWireFormat(). |
| */ |
| ControlParameters.prototype.wireDecode = function(input, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| if (typeof input === 'object' && input instanceof Blob) |
| // Input is a blob, so get its buf() and set copy false. |
| wireFormat.decodeControlParameters(this, input.buf(), false); |
| else |
| wireFormat.decodeControlParameters(this, input, true); |
| }; |
| |
| /** |
| * Get the name. |
| * @return {Name} The name. If not specified, return null. |
| */ |
| ControlParameters.prototype.getName = function() |
| { |
| return this.name; |
| }; |
| |
| /** |
| * Get the face ID. |
| * @return {number} The face ID, or null if not specified. |
| */ |
| ControlParameters.prototype.getFaceId = function() |
| { |
| return this.faceId; |
| }; |
| |
| /** |
| * Get the URI. |
| * @return {string} The face URI, or an empty string if not specified. |
| */ |
| ControlParameters.prototype.getUri = function() |
| { |
| return this.uri; |
| }; |
| |
| /** |
| * Get the local control feature value. |
| * @return {number} The local control feature value, or null if not specified. |
| */ |
| ControlParameters.prototype.getLocalControlFeature = function() |
| { |
| return this.localControlFeature; |
| }; |
| |
| /** |
| * Get the origin value. |
| * @return {number} The origin value, or null if not specified. |
| */ |
| ControlParameters.prototype.getOrigin = function() |
| { |
| return this.origin; |
| }; |
| |
| /** |
| * Get the cost value. |
| * @return {number} The cost value, or null if not specified. |
| */ |
| ControlParameters.prototype.getCost = function() |
| { |
| return this.cost; |
| }; |
| |
| /** |
| * Get the ForwardingFlags object. |
| * @return {ForwardingFlags} The ForwardingFlags object. |
| */ |
| ControlParameters.prototype.getForwardingFlags = function() |
| { |
| return this.forwardingFlags; |
| }; |
| |
| /** |
| * Get the strategy. |
| * @return {Name} The strategy or an empty Name |
| */ |
| ControlParameters.prototype.getStrategy = function() |
| { |
| return this.strategy; |
| }; |
| |
| /** |
| * Get the expiration period. |
| * @return {number} The expiration period in milliseconds, or null if not specified. |
| */ |
| ControlParameters.prototype.getExpirationPeriod = function() |
| { |
| return this.expirationPeriod; |
| }; |
| |
| /** |
| * Set the name. |
| * @param {Name} name The name. If not specified, set to null. If specified, this |
| * makes a copy of the name. |
| */ |
| ControlParameters.prototype.setName = function(name) |
| { |
| this.name = typeof name === 'object' && name instanceof Name ? |
| new Name(name) : null; |
| }; |
| |
| /** |
| * Set the Face ID. |
| * @param {number} faceId The new face ID, or null for not specified. |
| */ |
| ControlParameters.prototype.setFaceId = function(faceId) |
| { |
| this.faceId = faceId; |
| }; |
| |
| /** |
| * Set the URI. |
| * @param {string} uri The new uri, or an empty string for not specified. |
| */ |
| ControlParameters.prototype.setUri = function(uri) |
| { |
| this.uri = uri || ''; |
| }; |
| |
| /** |
| * Set the local control feature value. |
| * @param {number} localControlFeature The new local control feature value, or |
| * null for not specified. |
| */ |
| ControlParameters.prototype.setLocalControlFeature = function(localControlFeature) |
| { |
| this.localControlFeature = localControlFeature; |
| }; |
| |
| /** |
| * Set the origin value. |
| * @param {number} origin The new origin value, or null for not specified. |
| */ |
| ControlParameters.prototype.setOrigin = function(origin) |
| { |
| this.origin = origin; |
| }; |
| |
| /** |
| * Set the cost value. |
| * @param {number} cost The new cost value, or null for not specified. |
| */ |
| ControlParameters.prototype.setCost = function(cost) |
| { |
| this.cost = cost; |
| }; |
| |
| /** |
| * Set the ForwardingFlags object to a copy of forwardingFlags. You can use |
| * getForwardingFlags() and change the existing ForwardingFlags object. |
| * @param {ForwardingFlags} forwardingFlags The new cost value, or null for not specified. |
| */ |
| ControlParameters.prototype.setForwardingFlags = function(forwardingFlags) |
| { |
| this.forwardingFlags = |
| typeof forwardingFlags === 'object' && forwardingFlags instanceof ForwardingFlags ? |
| new ForwardingFlags(forwardingFlags) : new ForwardingFlags(); |
| }; |
| |
| /** |
| * Set the strategy to a copy of the given Name. |
| * @param {Name} strategy The Name to copy, or an empty Name if not specified. |
| */ |
| ControlParameters.prototype.setStrategy = function(strategy) |
| { |
| this.strategy = typeof strategy === 'object' && strategy instanceof Name ? |
| new Name(strategy) : new Name(); |
| }; |
| |
| /** |
| * Set the expiration period. |
| * @param {number} expirationPeriod The expiration period in milliseconds, or |
| * null for not specified. |
| */ |
| ControlParameters.prototype.setExpirationPeriod = function(expirationPeriod) |
| { |
| this.expirationPeriod = expirationPeriod; |
| }; |
| /** |
| * Copyright (C) 2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var ControlParameters = require('./control-parameters.js').ControlParameters; /** @ignore */ |
| var WireFormat = require('./encoding/wire-format.js').WireFormat; /** @ignore */ |
| var Blob = require('./util/blob.js').Blob; |
| |
| /** |
| * A ControlResponse holds a status code, status text and other fields for a |
| * ControlResponse which is used, for example, in the response from sending a |
| * register prefix control command to a forwarder. |
| * @see http://redmine.named-data.net/projects/nfd/wiki/ControlCommand |
| * @constructor |
| */ |
| var ControlResponse = function ControlResponse(value) |
| { |
| if (typeof value === 'object' && value instanceof ControlResponse) { |
| // Make a deep copy. |
| this.statusCode_ = value.statusCode_; |
| this.statusText_ = value.statusText_; |
| this.bodyAsControlParameters_ = value.bodyAsControlParameters_ == null ? null |
| : new ControlParameters(value.bodyAsControlParameters_); |
| } |
| else { |
| this.statusCode_ = null; |
| this.statusText_ = ""; |
| this.bodyAsControlParameters_ = null; |
| } |
| }; |
| |
| exports.ControlResponse = ControlResponse; |
| |
| ControlResponse.prototype.clear = function() |
| { |
| this.statusCode_ = null; |
| this.statusText_ = ""; |
| this.bodyAsControlParameters_ = null; |
| }; |
| |
| /** |
| * Encode this ControlResponse for a particular wire format. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * this object. If omitted, use WireFormat.getDefaultWireFormat(). |
| * @return {Blob} The encoded buffer in a Blob object. |
| */ |
| ControlResponse.prototype.wireEncode = function(wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| return wireFormat.encodeControlResponse(this); |
| }; |
| |
| /** |
| * Decode the input using a particular wire format and update this |
| * ControlResponse. |
| * @param {Blob|Buffer} input The buffer with the bytes to decode. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to decode |
| * this object. If omitted, use WireFormat.getDefaultWireFormat(). |
| */ |
| ControlResponse.prototype.wireDecode = function(input, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| if (typeof input === 'object' && input instanceof Blob) |
| // Input is a blob, so get its buf() and set copy false. |
| wireFormat.decodeControlResponse(this, input.buf(), false); |
| else |
| wireFormat.decodeControlResponse(this, input, true); |
| }; |
| |
| /** |
| * Get the status code. |
| * @return {number} The status code. If not specified, return null. |
| */ |
| ControlResponse.prototype.getStatusCode = function() |
| { |
| return this.statusCode_; |
| }; |
| |
| /** |
| * Get the status text. |
| * @return {string} The status text. If not specified, return "". |
| */ |
| ControlResponse.prototype.getStatusText = function() |
| { |
| return this.statusText_; |
| }; |
| |
| /** |
| * Get the control response body as a ControlParameters. |
| * @return {ControlParameters} The ControlParameters, or null if the body is not |
| * specified or if it is not a ControlParameters. |
| */ |
| ControlResponse.prototype.getBodyAsControlParameters = function() |
| { |
| return this.bodyAsControlParameters_; |
| }; |
| |
| /** |
| * Set the status code. |
| * @param statusCode {number} The status code. If not specified, set to null. |
| * @return {ControlResponse} This ControlResponse so that you can chain calls to |
| * update values. |
| */ |
| ControlResponse.prototype.setStatusCode = function(statusCode) |
| { |
| this.statusCode_ = statusCode; |
| return this; |
| }; |
| |
| /** |
| * Set the status text. |
| * @param statusText {string} The status text. If not specified, set to "". |
| * @return {ControlResponse} This ControlResponse so that you can chain calls to |
| * update values. |
| */ |
| ControlResponse.prototype.setStatusText = function(statusText) |
| { |
| this.statusText_ = statusText || ""; |
| return this; |
| }; |
| |
| /** |
| * Set the control response body as a ControlParameters. |
| * @param {ControlParameters} controlParameters The ControlParameters for the |
| * body. This makes a copy of the ControlParameters. If not specified or if the |
| * body is not a ControlParameters, set to null. |
| * @return {ControlResponse} This ControlResponse so that you can chain calls to |
| * update values. |
| */ |
| ControlResponse.prototype.setBodyAsControlParameters = function(controlParameters) |
| { |
| this.bodyAsControlParameters_ = |
| typeof controlParameters === 'object' && controlParameters instanceof ControlParameters ? |
| new ControlParameters(controlParameters) : null; |
| return this; |
| }; |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Name = require('./name.js').Name; /** @ignore */ |
| var NdnRegexMatcher = require('./util/ndn-regex-matcher.js').NdnRegexMatcher; |
| |
| /** |
| * An InterestFilter holds a Name prefix and optional regex match expression for |
| * use in Face.setInterestFilter. |
| * |
| * Create an InterestFilter to match any Interest whose name starts with the |
| * given prefix. If the optional regexFilter is provided then the remaining |
| * components match the regexFilter regular expression as described in doesMatch. |
| * @param {InterestFilter|Name|string} prefix If prefix is another |
| * InterestFilter copy its values. If prefix is a Name then this makes a copy of |
| * the Name. Otherwise this creates a Name from the URI string. |
| * @param {string} regexFilter (optional) The regular expression for matching |
| * the remaining name components. |
| * @constructor |
| */ |
| var InterestFilter = function InterestFilter(prefix, regexFilter) |
| { |
| if (typeof prefix === 'object' && prefix instanceof InterestFilter) { |
| // The copy constructor. |
| var interestFilter = prefix; |
| this.prefix = new Name(interestFilter.prefix); |
| this.regexFilter = interestFilter.regexFilter; |
| this.regexFilterPattern = interestFilter.regexFilterPattern; |
| } |
| else { |
| this.prefix = new Name(prefix); |
| if (regexFilter) { |
| this.regexFilter = regexFilter; |
| this.regexFilterPattern = InterestFilter.makePattern(regexFilter); |
| } |
| else { |
| this.regexFilter = null; |
| this.regexFilterPattern = null; |
| } |
| } |
| }; |
| |
| exports.InterestFilter = InterestFilter; |
| |
| /** |
| * Check if the given name matches this filter. Match if name starts with this |
| * filter's prefix. If this filter has the optional regexFilter then the |
| * remaining components match the regexFilter regular expression. |
| * For example, the following InterestFilter: |
| * |
| * InterestFilter("/hello", "<world><>+") |
| * |
| * will match all Interests, whose name has the prefix `/hello` which is |
| * followed by a component `world` and has at least one more component after it. |
| * Examples: |
| * |
| * /hello/world/! |
| * /hello/world/x/y/z |
| * |
| * Note that the regular expression will need to match all remaining components |
| * (e.g., there are implicit heading `^` and trailing `$` symbols in the |
| * regular expression). |
| * @param {Name} name The name to check against this filter. |
| * @return {boolean} True if name matches this filter, otherwise false. |
| */ |
| InterestFilter.prototype.doesMatch = function(name) |
| { |
| if (name.size() < this.prefix.size()) |
| return false; |
| |
| if (this.hasRegexFilter()) { |
| // Perform a prefix match and regular expression match for the remaining |
| // components. |
| if (!this.prefix.match(name)) |
| return false; |
| |
| return null != NdnRegexMatcher.match |
| (this.regexFilterPattern, name.getSubName(this.prefix.size())); |
| } |
| else |
| // Just perform a prefix match. |
| return this.prefix.match(name); |
| }; |
| |
| /** |
| * Get the prefix given to the constructor. |
| * @return {Name} The prefix Name which you should not modify. |
| */ |
| InterestFilter.prototype.getPrefix = function() { return this.prefix; }; |
| |
| /** |
| * Check if a regexFilter was supplied to the constructor. |
| * @return {boolean} True if a regexFilter was supplied to the constructor. |
| */ |
| InterestFilter.prototype.hasRegexFilter = function() |
| { |
| return this.regexFilter != null; |
| }; |
| |
| /** |
| * Get the regex filter. This is only valid if hasRegexFilter() is true. |
| * @return {string} The regular expression for matching the remaining name |
| * components. |
| */ |
| InterestFilter.prototype.getRegexFilter = function() { return this.regexFilter; }; |
| |
| /** |
| * If regexFilter doesn't already have them, add ^ to the beginning and $ to |
| * the end since these are required by NdnRegexMatcher.match. |
| * @param {string} regexFilter The regex filter. |
| * @return {string} The regex pattern with ^ and $. |
| */ |
| InterestFilter.makePattern = function(regexFilter) |
| { |
| if (regexFilter.length == 0) |
| // We don't expect this. |
| return "^$"; |
| |
| var pattern = regexFilter; |
| if (pattern[0] != '^') |
| pattern = "^" + pattern; |
| if (pattern[pattern.length - 1] != '$') |
| pattern = pattern + "$"; |
| |
| return pattern; |
| }; |
| /** |
| * Copyright (C) 2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Name = require('./name.js').Name; /** @ignore */ |
| var Blob = require('./util/blob.js').Blob; /** @ignore */ |
| var WireFormat = require('./encoding/wire-format.js').WireFormat; |
| |
| /** |
| * A DelegationSet holds a list of DelegationSet.Delegation entries which is |
| * used as the content of a Link instance. If you add elements with add(), then |
| * the list is a set sorted by preference number then by name. But wireDecode |
| * will add the elements from the wire encoding, preserving the given order and |
| * possible duplicates (in which case a DelegationSet really holds a "list" and |
| * not necessarily a "set"). |
| * |
| * Create a new DelegationSet object, possibly copying values from another |
| * object. |
| * |
| * @param {DelegationSet} value (optional) If value is a DelegationSet, copy its |
| * values. |
| * @constructor |
| */ |
| var DelegationSet = function DelegationSet(value) |
| { |
| if (typeof value === 'object' && value instanceof DelegationSet) |
| // Copy the list. |
| this.delegations_ = value.delegations_.slice(0); |
| else |
| this.delegations_ = []; // of DelegationSet.Delegation. |
| }; |
| |
| exports.DelegationSet = DelegationSet; |
| |
| /** |
| * A DelegationSet.Delegation holds a preference number and delegation name. |
| * Create a new DelegationSet.Delegation with the given values. |
| * @param {number} preference The preference number. |
| * @param {Name} name The delegation name. This makes a copy of the name. |
| * @constructor |
| */ |
| DelegationSet.Delegation = function DelegationSetDelegation(preference, name) |
| { |
| this.preference_ = preference; |
| this.name_ = new Name(name); |
| }; |
| |
| /** |
| * Get the preference number. |
| * @return {number} The preference number. |
| */ |
| DelegationSet.Delegation.prototype.getPreference = function() |
| { |
| return this.preference_; |
| }; |
| |
| /** |
| * Get the delegation name. |
| * @return {Name} The delegation name. NOTE: You must not change the name object - |
| * if you need to change it then make a copy. |
| */ |
| DelegationSet.Delegation.prototype.getName = function() |
| { |
| return this.name_; |
| }; |
| |
| /** |
| * Compare this Delegation with other according to the ordering, based first |
| * on the preference number, then on the delegation name. |
| * @param {DelegationSet.Delegation} other The other Delegation to compare with. |
| * @return {number} 0 If they compare equal, -1 if this Delegation comes before |
| * other in the ordering, or 1 if this Delegation comes after. |
| */ |
| DelegationSet.Delegation.prototype.compare = function(other) |
| { |
| if (this.preference_ < other.preference_) |
| return -1; |
| if (this.preference_ > other.preference_) |
| return 1; |
| |
| return this.name_.compare(other.name_); |
| }; |
| |
| /** |
| * Add a new DelegationSet.Delegation to the list of delegations, sorted by |
| * preference number then by name. If there is already a delegation with the |
| * same name, update its preference, and remove any extra delegations with the |
| * same name. |
| * @param {number} preference The preference number. |
| * @param {Name} name The delegation name. This makes a copy of the name. |
| */ |
| DelegationSet.prototype.add = function(preference, name) |
| { |
| this.remove(name); |
| |
| var newDelegation = new DelegationSet.Delegation(preference, name); |
| // Find the index of the first entry where it is not less than newDelegation. |
| var i = 0; |
| while (i < this.delegations_.length) { |
| if (this.delegations_[i].compare(newDelegation) >= 0) |
| break; |
| |
| ++i; |
| } |
| |
| this.delegations_.splice(i, 0, newDelegation); |
| }; |
| |
| /** |
| * Add a new DelegationSet.Delegation to the end of the list of delegations, |
| * without sorting or updating any existing entries. This is useful for adding |
| * preferences from a wire encoding, preserving the supplied ordering and |
| * possible duplicates. |
| * @param {number} preference The preference number. |
| * @param {Name} name The delegation name. This makes a copy of the name. |
| */ |
| DelegationSet.prototype.addUnsorted = function(preference, name) |
| { |
| this.delegations_.push(new DelegationSet.Delegation(preference, name)); |
| }; |
| |
| /** |
| * Remove every DelegationSet.Delegation with the given name. |
| * @param {Name} name The name to match the name of the delegation(s) to be |
| * removed. |
| * @return {boolean} True if a DelegationSet.Delegation was removed, otherwise |
| * false. |
| */ |
| DelegationSet.prototype.remove = function(name) |
| { |
| var wasRemoved = false; |
| // Go backwards through the list so we can remove entries. |
| for (var i = this.delegations_.length - 1; i >= 0; --i) { |
| if (this.delegations_[i].getName().equals(name)) { |
| wasRemoved = true; |
| this.delegations_.splice(i, 1); |
| } |
| } |
| |
| return wasRemoved; |
| }; |
| |
| /** |
| * Clear the list of delegations. |
| */ |
| DelegationSet.prototype.clear = function() { this.delegations_ = []; }; |
| |
| /** |
| * Get the number of delegation entries. |
| * @return {number} The number of delegation entries. |
| */ |
| DelegationSet.prototype.size = function() { return this.delegations_.length; }; |
| |
| /** |
| * Get the delegation at the given index, according to the ordering described |
| * in add(). |
| * @param {number} i The index of the component, starting from 0. |
| * @return {DelegationSet.Delegation} The delegation at the index. |
| */ |
| DelegationSet.prototype.get = function(i) { return this.delegations_[i]; }; |
| |
| /** |
| * Find the first delegation with the given name and return its index. |
| * @param {Name} name Then name of the delegation to find. |
| * @return {number} The index of the delegation, or -1 if not found. |
| */ |
| DelegationSet.prototype.find = function(name) |
| { |
| for (var i = 0; i < this.delegations_.length; ++i) { |
| if (this.delegations_[i].getName().equals(name)) |
| return i; |
| } |
| |
| return -1; |
| }; |
| |
| /** |
| * Encode this DelegationSet for a particular wire format. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * this object. If omitted, use WireFormat.getDefaultWireFormat(). |
| * @return {Blob} The encoded buffer in a Blob object. |
| */ |
| DelegationSet.prototype.wireEncode = function(wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| return wireFormat.encodeDelegationSet(this); |
| }; |
| |
| /** |
| * Decode the input using a particular wire format and update this DelegationSet. |
| * @param {Blob|Buffer} input The buffer with the bytes to decode. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to decode |
| * this object. If omitted, use WireFormat.getDefaultWireFormat(). |
| */ |
| DelegationSet.prototype.wireDecode = function(input, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| if (typeof input === 'object' && input instanceof Blob) |
| // Input is a blob, so get its buf() and set copy false. |
| wireFormat.decodeDelegationSet(this, input.buf(), false); |
| else |
| wireFormat.decodeDelegationSet(this, input, true); |
| }; |
| /** |
| * Copyright (C) 2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var DelegationSet = require('./delegation-set.js').DelegationSet; /** @ignore */ |
| var ContentType = require('./meta-info.js').ContentType; /** @ignore */ |
| var WireFormat = require('./encoding/wire-format.js').WireFormat; /** @ignore */ |
| var Data = require('./data.js').Data; |
| |
| /** |
| * The Link class extends Data and represents a Link instance where the Data |
| * content is an encoded delegation set. The format is defined in "link.pdf" |
| * attached to Redmine issue http://redmine.named-data.net/issues/2587 . |
| * |
| * Create a new Link with the optional values. There are 3 forms of the constructor: |
| * Link(name); |
| * Link(data); |
| * Link(); |
| * @param {Name} name The name for constructing the base Data. |
| * @param {Data} data The Data object to copy values from. If the content can be |
| * decoded using the default wire encoding, then update the list of delegations. |
| * @constructor |
| */ |
| var Link = function Link(value) |
| { |
| this.delegations_ = new DelegationSet(); |
| |
| if (value instanceof Data) { |
| // Call the base constructor. |
| Data.call(this, value); |
| |
| if (!this.getContent().isNull()) { |
| try { |
| this.delegations_.wireDecode(this.getContent()); |
| this.getMetaInfo().setType(ContentType.LINK); |
| } |
| catch (ex) { |
| this.delegations_.clear(); |
| } |
| } |
| } |
| else { |
| if (value != undefined) |
| // value is a Name. |
| Data.call(this, value); |
| else |
| Data.call(this); |
| |
| this.getMetaInfo().setType(ContentType.LINK); |
| } |
| }; |
| |
| Link.prototype = new Data(); |
| Link.prototype.name = "Link"; |
| |
| exports.Link = Link; |
| |
| /** |
| * Override to call the base class wireDecode then populate the list of |
| * delegations from the content. |
| * @param {Blob|Buffer} input The buffer with the bytes to decode. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to decode |
| * this object. If omitted, use WireFormat.getDefaultWireFormat(). |
| */ |
| Link.prototype.wireDecode = function(input, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| |
| Data.prototype.wireDecode.call(this, input, wireFormat); |
| if (this.getMetaInfo().getType() != ContentType.LINK) |
| throw new Error |
| ("Link.wireDecode: MetaInfo ContentType is not LINK."); |
| |
| this.delegations_.wireDecode(this.getContent()); |
| }; |
| |
| /** |
| * Add a new delegation to the list of delegations, sorted by preference number |
| * then by name. Re-encode this object's content using the optional wireFormat. |
| * @param {number} preference The preference number. |
| * @param {Name} name The delegation name. This makes a copy of the name. If |
| * there is already a delegation with the same name, this updates its preference. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * the DelegationSet. If omitted, use WireFormat.getDefaultWireFormat(). |
| * @return {Link} This Link so that you can chain calls to update values. |
| */ |
| Link.prototype.addDelegation = function(preference, name, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| |
| this.delegations_.add(preference, name); |
| this.encodeContent(wireFormat); |
| |
| return this; |
| }; |
| |
| /** |
| * Remove every delegation with the given name. Re-encode this object's content |
| * using the optional wireFormat. |
| * @param {Name} name Then name to match the name of the delegation(s) to be |
| * removed. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * the DelegationSet. If omitted, use WireFormat.getDefaultWireFormat(). |
| * @return {boolean} True if a delegation was removed, otherwise false. |
| */ |
| Link.prototype.removeDelegation = function(name, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| |
| var wasRemoved = this.delegations_.remove(name); |
| if (wasRemoved) |
| this.encodeContent(wireFormat); |
| |
| return wasRemoved; |
| }; |
| |
| /** |
| * Get the list of delegation for read only. |
| * @return {DelegationSet} The list of delegation, which you should treat as |
| * read-only. To modify it, call Link.addDelegation, etc. |
| */ |
| Link.prototype.getDelegations = function() { return this.delegations_; }; |
| |
| /** |
| * A private method to encode the delegations_ and set this object's content. |
| * Also set the meta info content type to LINK. |
| * @param {WireFormat} wireFormat A WireFormat object used to encode the |
| * DelegationSet. |
| */ |
| Link.prototype.encodeContent = function(wireFormat) |
| { |
| this.setContent(this.delegations_.wireEncode(wireFormat)); |
| this.getMetaInfo().setType(ContentType.LINK); |
| }; |
| /** |
| * Copyright (C) 2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From ndn-cxx nack.hpp https://github.com/named-data/ndn-cxx/blob/master/src/lp/nack.hpp |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * NetworkNack represents a network Nack packet and includes a Nack reason. |
| * @constructor |
| */ |
| var NetworkNack = function NetworkNack() |
| { |
| this.reason_ = NetworkNack.Reason.NONE; |
| this.otherReasonCode_ = -1; |
| }; |
| |
| exports.NetworkNack = NetworkNack; |
| |
| /** |
| * A NetworkNack.Reason specifies the reason in a NetworkNack packet. If the |
| * reason code in the packet is not a recognized enum value, then we use |
| * Reason.OTHER_CODE and you can call getOtherReasonCode(). We do this to keep |
| * the recognized reason values independent of packet encoding formats. |
| */ |
| NetworkNack.Reason = { |
| NONE: 0, |
| CONGESTION: 50, |
| DUPLICATE: 100, |
| NO_ROUTE: 150, |
| OTHER_CODE: 0x7fff |
| }; |
| |
| /** |
| * Get the network Nack reason. |
| * @return {number} The reason enum value from NetworkNack.Reason. If this is |
| * Reason.OTHER_CODE, then call getOtherReasonCode() to get the unrecognized |
| * reason code. |
| */ |
| NetworkNack.prototype.getReason = function() { return this.reason_; }; |
| |
| /** |
| * Get the reason code from the packet which is other than a recognized |
| * Reason enum value. This is only meaningful if getReason() is |
| * Reason.OTHER_CODE. |
| * @return {number} The reason code. |
| */ |
| NetworkNack.prototype.getOtherReasonCode = function() |
| { |
| return this.otherReasonCode_; |
| }; |
| |
| /** |
| * Set the network Nack reason. |
| * @param {number} reason The network Nack reason enum value from |
| * NetworkNack.Reason. If the packet's reason code is not a recognized Reason |
| * enum value, use Reason.OTHER_CODE and call setOtherReasonCode(). |
| */ |
| NetworkNack.prototype.setReason = function(reason) { this.reason_ = reason; }; |
| |
| /** |
| * Set the packet's reason code to use when the reason enum is |
| * Reason.OTHER_CODE. If the packet's reason code is a recognized enum value, |
| * just call setReason(). |
| * @param {number} otherReasonCode The packet's unrecognized reason code, which |
| * must be non-negative. |
| */ |
| NetworkNack.prototype.setOtherReasonCode = function(otherReasonCode) |
| { |
| if (otherReasonCode < 0) |
| throw new Error("NetworkNack other reason code must be non-negative"); |
| this.otherReasonCode_ = otherReasonCode; |
| }; |
| |
| /** |
| * Get the first header field in lpPacket which is a NetworkNack. This is |
| * an internal method which the application normally would not use. |
| * @param {LpPacket} lpPacket The LpPacket with the header fields to search. |
| * @return {NetworkNack} The first NetworkNack header field, or null if not |
| * found. |
| */ |
| NetworkNack.getFirstHeader = function(lpPacket) |
| { |
| for (var i = 0; i < lpPacket.countHeaderFields(); ++i) { |
| var field = lpPacket.getHeaderField(i); |
| if (field instanceof NetworkNack) |
| return field; |
| } |
| |
| return null; |
| }; |
| /** |
| * Copyright (C) 2013-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Crypto = require('../crypto.js'); /** @ignore */ |
| var Blob = require('../util/blob.js').Blob; /** @ignore */ |
| var Name = require('../name.js').Name; /** @ignore */ |
| var ForwardingFlags = require('../forwarding-flags').ForwardingFlags; /** @ignore */ |
| var Tlv = require('./tlv/tlv.js').Tlv; /** @ignore */ |
| var TlvEncoder = require('./tlv/tlv-encoder.js').TlvEncoder; /** @ignore */ |
| var TlvDecoder = require('./tlv/tlv-decoder.js').TlvDecoder; /** @ignore */ |
| var WireFormat = require('./wire-format.js').WireFormat; /** @ignore */ |
| var Exclude = require('../exclude.js').Exclude; /** @ignore */ |
| var ContentType = require('../meta-info.js').ContentType; /** @ignore */ |
| var KeyLocatorType = require('../key-locator.js').KeyLocatorType; /** @ignore */ |
| var Sha256WithRsaSignature = require('../sha256-with-rsa-signature.js').Sha256WithRsaSignature; /** @ignore */ |
| var Sha256WithEcdsaSignature = require('../sha256-with-ecdsa-signature.js').Sha256WithEcdsaSignature; /** @ignore */ |
| var GenericSignature = require('../generic-signature.js').GenericSignature; /** @ignore */ |
| var HmacWithSha256Signature = require('../hmac-with-sha256-signature.js').HmacWithSha256Signature; /** @ignore */ |
| var DigestSha256Signature = require('../digest-sha256-signature.js').DigestSha256Signature; /** @ignore */ |
| var ControlParameters = require('../control-parameters.js').ControlParameters; /** @ignore */ |
| var ForwardingFlags = require('../forwarding-flags.js').ForwardingFlags; /** @ignore */ |
| var NetworkNack = require('../network-nack.js').NetworkNack; /** @ignore */ |
| var IncomingFaceId = require('../lp/incoming-face-id.js').IncomingFaceId; /** @ignore */ |
| var DecodingException = require('./decoding-exception.js').DecodingException; |
| |
| /** |
| * A Tlv0_2WireFormat implements the WireFormat interface for encoding and |
| * decoding with the NDN-TLV wire format, version 0.2. |
| * @constructor |
| */ |
| var Tlv0_2WireFormat = function Tlv0_2WireFormat() |
| { |
| // Inherit from WireFormat. |
| WireFormat.call(this); |
| }; |
| |
| Tlv0_2WireFormat.prototype = new WireFormat(); |
| Tlv0_2WireFormat.prototype.name = "Tlv0_2WireFormat"; |
| |
| exports.Tlv0_2WireFormat = Tlv0_2WireFormat; |
| |
| // Default object. |
| Tlv0_2WireFormat.instance = null; |
| |
| /** |
| * Encode name as an NDN-TLV Name and return the encoding. |
| * @param {Name} name The Name to encode. |
| * @return {Blobl} A Blob containing the encoding. |
| */ |
| Tlv0_2WireFormat.prototype.encodeName = function(name) |
| { |
| var encoder = new TlvEncoder(); |
| Tlv0_2WireFormat.encodeName(name, encoder); |
| return new Blob(encoder.getOutput(), false); |
| }; |
| |
| /** |
| * Decode input as a NDN-TLV name and set the fields of the Name object. |
| * @param {Name} name The Name object whose fields are updated. |
| * @param {Buffer} input The buffer with the bytes to decode. |
| * @param {boolean} copy (optional) If true, copy from the input when making new |
| * Blob values. If false, then Blob values share memory with the input, which |
| * must remain unchanged while the Blob values are used. If omitted, use true. |
| */ |
| Tlv0_2WireFormat.prototype.decodeName = function(name, input, copy) |
| { |
| if (copy == null) |
| copy = true; |
| |
| var decoder = new TlvDecoder(input); |
| Tlv0_2WireFormat.decodeName(name, decoder, copy); |
| }; |
| |
| /** |
| * Encode the interest using NDN-TLV and return a Buffer. |
| * @param {Interest} interest The Interest object to encode. |
| * @return {object} An associative array with fields |
| * (encoding, signedPortionBeginOffset, signedPortionEndOffset) where encoding |
| * is a Blob containing the encoding, signedPortionBeginOffset is the offset in |
| * the encoding of the beginning of the signed portion, and |
| * signedPortionEndOffset is the offset in the encoding of the end of the signed |
| * portion. The signed portion starts from the first name component and ends |
| * just before the final name component (which is assumed to be a signature for |
| * a signed interest). |
| */ |
| Tlv0_2WireFormat.prototype.encodeInterest = function(interest) |
| { |
| var encoder = new TlvEncoder(256); |
| var saveLength = encoder.getLength(); |
| |
| // Encode backwards. |
| encoder.writeOptionalNonNegativeIntegerTlv |
| (Tlv.SelectedDelegation, interest.getSelectedDelegationIndex()); |
| var linkWireEncoding = interest.getLinkWireEncoding(this); |
| if (!linkWireEncoding.isNull()) |
| // Encode the entire link as is. |
| encoder.writeBuffer(linkWireEncoding.buf()); |
| |
| encoder.writeOptionalNonNegativeIntegerTlv |
| (Tlv.InterestLifetime, interest.getInterestLifetimeMilliseconds()); |
| |
| // Encode the Nonce as 4 bytes. |
| if (interest.getNonce().isNull() || interest.getNonce().size() == 0) |
| // This is the most common case. Generate a nonce. |
| encoder.writeBlobTlv(Tlv.Nonce, Crypto.randomBytes(4)); |
| else if (interest.getNonce().size() < 4) { |
| var nonce = Buffer(4); |
| // Copy existing nonce bytes. |
| interest.getNonce().buf().copy(nonce); |
| |
| // Generate random bytes for remaining bytes in the nonce. |
| for (var i = interest.getNonce().size(); i < 4; ++i) |
| nonce[i] = Crypto.randomBytes(1)[0]; |
| |
| encoder.writeBlobTlv(Tlv.Nonce, nonce); |
| } |
| else if (interest.getNonce().size() == 4) |
| // Use the nonce as-is. |
| encoder.writeBlobTlv(Tlv.Nonce, interest.getNonce().buf()); |
| else |
| // Truncate. |
| encoder.writeBlobTlv(Tlv.Nonce, interest.getNonce().buf().slice(0, 4)); |
| |
| Tlv0_2WireFormat.encodeSelectors(interest, encoder); |
| var tempOffsets = Tlv0_2WireFormat.encodeName(interest.getName(), encoder); |
| var signedPortionBeginOffsetFromBack = |
| encoder.getLength() - tempOffsets.signedPortionBeginOffset; |
| var signedPortionEndOffsetFromBack = |
| encoder.getLength() - tempOffsets.signedPortionEndOffset; |
| |
| encoder.writeTypeAndLength(Tlv.Interest, encoder.getLength() - saveLength); |
| var signedPortionBeginOffset = |
| encoder.getLength() - signedPortionBeginOffsetFromBack; |
| var signedPortionEndOffset = |
| encoder.getLength() - signedPortionEndOffsetFromBack; |
| |
| return { encoding: new Blob(encoder.getOutput(), false), |
| signedPortionBeginOffset: signedPortionBeginOffset, |
| signedPortionEndOffset: signedPortionEndOffset }; |
| }; |
| |
| /** |
| * Decode input as an NDN-TLV interest and set the fields of the interest |
| * object. |
| * @param {Interest} interest The Interest object whose fields are updated. |
| * @param {Buffer} input The buffer with the bytes to decode. |
| * @param {boolean} copy (optional) If true, copy from the input when making new |
| * Blob values. If false, then Blob values share memory with the input, which |
| * must remain unchanged while the Blob values are used. If omitted, use true. |
| * @return {object} An associative array with fields |
| * (signedPortionBeginOffset, signedPortionEndOffset) where |
| * signedPortionBeginOffset is the offset in the encoding of the beginning of |
| * the signed portion, and signedPortionEndOffset is the offset in the encoding |
| * of the end of the signed portion. The signed portion starts from the first |
| * name component and ends just before the final name component (which is |
| * assumed to be a signature for a signed interest). |
| */ |
| Tlv0_2WireFormat.prototype.decodeInterest = function(interest, input, copy) |
| { |
| if (copy == null) |
| copy = true; |
| |
| var decoder = new TlvDecoder(input); |
| |
| var endOffset = decoder.readNestedTlvsStart(Tlv.Interest); |
| var offsets = Tlv0_2WireFormat.decodeName(interest.getName(), decoder, copy); |
| if (decoder.peekType(Tlv.Selectors, endOffset)) |
| Tlv0_2WireFormat.decodeSelectors(interest, decoder, copy); |
| // Require a Nonce, but don't force it to be 4 bytes. |
| var nonce = decoder.readBlobTlv(Tlv.Nonce); |
| interest.setInterestLifetimeMilliseconds |
| (decoder.readOptionalNonNegativeIntegerTlv(Tlv.InterestLifetime, endOffset)); |
| |
| if (decoder.peekType(Tlv.Data, endOffset)) { |
| // Get the bytes of the Link TLV. |
| var linkBeginOffset = decoder.getOffset(); |
| var linkEndOffset = decoder.readNestedTlvsStart(Tlv.Data); |
| decoder.seek(linkEndOffset); |
| |
| interest.setLinkWireEncoding |
| (new Blob(decoder.getSlice(linkBeginOffset, linkEndOffset), copy), this); |
| } |
| else |
| interest.unsetLink(); |
| interest.setSelectedDelegationIndex |
| (decoder.readOptionalNonNegativeIntegerTlv(Tlv.SelectedDelegation, endOffset)); |
| if (interest.getSelectedDelegationIndex() != null && |
| interest.getSelectedDelegationIndex() >= 0 && !interest.hasLink()) |
| throw new Error("Interest has a selected delegation, but no link object"); |
| |
| // Set the nonce last because setting other interest fields clears it. |
| interest.setNonce(new Blob(nonce, copy)); |
| |
| decoder.finishNestedTlvs(endOffset); |
| return offsets; |
| }; |
| |
| /** |
| * Encode data as NDN-TLV and return the encoding and signed offsets. |
| * @param {Data} data The Data object to encode. |
| * @return {object} An associative array with fields |
| * (encoding, signedPortionBeginOffset, signedPortionEndOffset) where encoding |
| * is a Blob containing the encoding, signedPortionBeginOffset is the offset in |
| * the encoding of the beginning of the signed portion, and |
| * signedPortionEndOffset is the offset in the encoding of the end of the |
| * signed portion. |
| */ |
| Tlv0_2WireFormat.prototype.encodeData = function(data) |
| { |
| var encoder = new TlvEncoder(1500); |
| var saveLength = encoder.getLength(); |
| |
| // Encode backwards. |
| encoder.writeBlobTlv(Tlv.SignatureValue, data.getSignature().getSignature().buf()); |
| var signedPortionEndOffsetFromBack = encoder.getLength(); |
| |
| Tlv0_2WireFormat.encodeSignatureInfo_(data.getSignature(), encoder); |
| encoder.writeBlobTlv(Tlv.Content, data.getContent().buf()); |
| Tlv0_2WireFormat.encodeMetaInfo(data.getMetaInfo(), encoder); |
| Tlv0_2WireFormat.encodeName(data.getName(), encoder); |
| var signedPortionBeginOffsetFromBack = encoder.getLength(); |
| |
| encoder.writeTypeAndLength(Tlv.Data, encoder.getLength() - saveLength); |
| var signedPortionBeginOffset = |
| encoder.getLength() - signedPortionBeginOffsetFromBack; |
| var signedPortionEndOffset = encoder.getLength() - signedPortionEndOffsetFromBack; |
| |
| return { encoding: new Blob(encoder.getOutput(), false), |
| signedPortionBeginOffset: signedPortionBeginOffset, |
| signedPortionEndOffset: signedPortionEndOffset }; |
| }; |
| |
| /** |
| * Decode input as an NDN-TLV data packet, set the fields in the data object, |
| * and return the signed offsets. |
| * @param {Data} data The Data object whose fields are updated. |
| * @param {Buffer} input The buffer with the bytes to decode. |
| * @param {boolean} copy (optional) If true, copy from the input when making new |
| * Blob values. If false, then Blob values share memory with the input, which |
| * must remain unchanged while the Blob values are used. If omitted, use true. |
| * @return {object} An associative array with fields |
| * (signedPortionBeginOffset, signedPortionEndOffset) where |
| * signedPortionBeginOffset is the offset in the encoding of the beginning of |
| * the signed portion, and signedPortionEndOffset is the offset in the encoding |
| * of the end of the signed portion. |
| */ |
| Tlv0_2WireFormat.prototype.decodeData = function(data, input, copy) |
| { |
| if (copy == null) |
| copy = true; |
| |
| var decoder = new TlvDecoder(input); |
| |
| var endOffset = decoder.readNestedTlvsStart(Tlv.Data); |
| var signedPortionBeginOffset = decoder.getOffset(); |
| |
| Tlv0_2WireFormat.decodeName(data.getName(), decoder, copy); |
| Tlv0_2WireFormat.decodeMetaInfo(data.getMetaInfo(), decoder, copy); |
| data.setContent(new Blob(decoder.readBlobTlv(Tlv.Content), copy)); |
| Tlv0_2WireFormat.decodeSignatureInfo(data, decoder, copy); |
| |
| var signedPortionEndOffset = decoder.getOffset(); |
| data.getSignature().setSignature |
| (new Blob(decoder.readBlobTlv(Tlv.SignatureValue), copy)); |
| |
| decoder.finishNestedTlvs(endOffset); |
| return { signedPortionBeginOffset: signedPortionBeginOffset, |
| signedPortionEndOffset: signedPortionEndOffset }; |
| }; |
| |
| /** |
| * Encode controlParameters as NDN-TLV and return the encoding. |
| * @param {ControlParameters} controlParameters The ControlParameters object to |
| * encode. |
| * @return {Blob} A Blob containing the encoding. |
| */ |
| Tlv0_2WireFormat.prototype.encodeControlParameters = function(controlParameters) |
| { |
| var encoder = new TlvEncoder(256); |
| Tlv0_2WireFormat.encodeControlParameters(controlParameters, encoder); |
| return new Blob(encoder.getOutput(), false); |
| }; |
| |
| /** |
| * Decode controlParameters in NDN-TLV and return the encoding. |
| * @param {ControlParameters} controlParameters The ControlParameters object to |
| * encode. |
| * @param {Buffer} input The buffer with the bytes to decode. |
| * @param {boolean} copy (optional) If true, copy from the input when making new |
| * Blob values. If false, then Blob values share memory with the input, which |
| * must remain unchanged while the Blob values are used. If omitted, use true. |
| * @throws DecodingException For invalid encoding |
| */ |
| Tlv0_2WireFormat.prototype.decodeControlParameters = function |
| (controlParameters, input, copy) |
| { |
| if (copy == null) |
| copy = true; |
| |
| var decoder = new TlvDecoder(input); |
| Tlv0_2WireFormat.decodeControlParameters(controlParameters, decoder, copy); |
| }; |
| |
| /** |
| * Encode controlResponse as NDN-TLV and return the encoding. |
| * @param {ControlResponse} controlResponse The ControlResponse object to |
| * encode. |
| * @return {Blob} A Blob containing the encoding. |
| */ |
| Tlv0_2WireFormat.prototype.encodeControlResponse = function(controlResponse) |
| { |
| var encoder = new TlvEncoder(256); |
| var saveLength = encoder.getLength(); |
| |
| // Encode backwards. |
| |
| // Encode the body. |
| if (controlResponse.getBodyAsControlParameters() != null) |
| Tlv0_2WireFormat.encodeControlParameters |
| (controlResponse.getBodyAsControlParameters(), encoder); |
| |
| encoder.writeBlobTlv |
| (Tlv.NfdCommand_StatusText, new Blob(controlResponse.getStatusText()).buf()); |
| encoder.writeNonNegativeIntegerTlv |
| (Tlv.NfdCommand_StatusCode, controlResponse.getStatusCode()); |
| |
| encoder.writeTypeAndLength |
| (Tlv.NfdCommand_ControlResponse, encoder.getLength() - saveLength); |
| |
| return new Blob(encoder.getOutput(), false); |
| }; |
| |
| /** |
| * Decode controlResponse in NDN-TLV and return the encoding. |
| * @param {ControlResponse} controlResponse The ControlResponse object to |
| * encode. |
| * @param {Buffer} input The buffer with the bytes to decode. |
| * @param {boolean} copy (optional) If true, copy from the input when making new |
| * Blob values. If false, then Blob values share memory with the input, which |
| * must remain unchanged while the Blob values are used. If omitted, use true. |
| * @throws DecodingException For invalid encoding |
| */ |
| Tlv0_2WireFormat.prototype.decodeControlResponse = function |
| (controlResponse, input, copy) |
| { |
| if (copy == null) |
| copy = true; |
| |
| var decoder = new TlvDecoder(input); |
| var endOffset = decoder.readNestedTlvsStart(Tlv.NfdCommand_ControlResponse); |
| |
| controlResponse.setStatusCode(decoder.readNonNegativeIntegerTlv |
| (Tlv.NfdCommand_StatusCode)); |
| // Set copy false since we just immediately get a string. |
| var statusText = new Blob |
| (decoder.readBlobTlv(Tlv.NfdCommand_StatusText), false); |
| controlResponse.setStatusText(statusText.toString()); |
| |
| // Decode the body. |
| if (decoder.peekType(Tlv.ControlParameters_ControlParameters, endOffset)) { |
| controlResponse.setBodyAsControlParameters(new ControlParameters()); |
| // Decode into the existing ControlParameters to avoid copying. |
| Tlv0_2WireFormat.decodeControlParameters |
| (controlResponse.getBodyAsControlParameters(), decoder, copy); |
| } |
| else |
| controlResponse.setBodyAsControlParameters(null); |
| |
| decoder.finishNestedTlvs(endOffset); |
| }; |
| |
| /** |
| * Encode signature as an NDN-TLV SignatureInfo and return the encoding. |
| * @param {Signature} signature An object of a subclass of Signature to encode. |
| * @return {Blob} A Blob containing the encoding. |
| */ |
| Tlv0_2WireFormat.prototype.encodeSignatureInfo = function(signature) |
| { |
| var encoder = new TlvEncoder(256); |
| Tlv0_2WireFormat.encodeSignatureInfo_(signature, encoder); |
| |
| return new Blob(encoder.getOutput(), false); |
| }; |
| |
| // SignatureHolder is used by decodeSignatureInfoAndValue. |
| Tlv0_2WireFormat.SignatureHolder = function Tlv0_2WireFormatSignatureHolder() |
| { |
| }; |
| |
| Tlv0_2WireFormat.SignatureHolder.prototype.setSignature = function(signature) |
| { |
| this.signature = signature; |
| }; |
| |
| Tlv0_2WireFormat.SignatureHolder.prototype.getSignature = function() |
| { |
| return this.signature; |
| }; |
| |
| /** |
| * Decode signatureInfo as an NDN-TLV SignatureInfo and signatureValue as the |
| * related SignatureValue, and return a new object which is a subclass of Signature. |
| * @param {Buffer} signatureInfo The buffer with the signature info bytes to |
| * decode. |
| * @param {Buffer} signatureValue The buffer with the signature value to decode. |
| * @param {boolean} copy (optional) If true, copy from the input when making new |
| * Blob values. If false, then Blob values share memory with the input, which |
| * must remain unchanged while the Blob values are used. If omitted, use true. |
| * @return {Signature} A new object which is a subclass of Signature. |
| */ |
| Tlv0_2WireFormat.prototype.decodeSignatureInfoAndValue = function |
| (signatureInfo, signatureValue, copy) |
| { |
| if (copy == null) |
| copy = true; |
| |
| // Use a SignatureHolder to imitate a Data object for decodeSignatureInfo. |
| var signatureHolder = new Tlv0_2WireFormat.SignatureHolder(); |
| var decoder = new TlvDecoder(signatureInfo); |
| Tlv0_2WireFormat.decodeSignatureInfo(signatureHolder, decoder, copy); |
| |
| decoder = new TlvDecoder(signatureValue); |
| signatureHolder.getSignature().setSignature |
| (new Blob(decoder.readBlobTlv(Tlv.SignatureValue), copy)); |
| |
| return signatureHolder.getSignature(); |
| }; |
| |
| /** |
| * Encode the signatureValue in the Signature object as an NDN-TLV |
| * SignatureValue (the signature bits) and return the encoding. |
| * @param {Signature} signature An object of a subclass of Signature with the |
| * signature value to encode. |
| * @return {Blob} A Blob containing the encoding. |
| */ |
| Tlv0_2WireFormat.prototype.encodeSignatureValue = function(signature) |
| { |
| var encoder = new TlvEncoder(256); |
| encoder.writeBlobTlv(Tlv.SignatureValue, signature.getSignature().buf()); |
| |
| return new Blob(encoder.getOutput(), false); |
| }; |
| |
| /** |
| * Decode input as an NDN-TLV LpPacket and set the fields of the lpPacket object. |
| * @param {LpPacket} lpPacket The LpPacket object whose fields are updated. |
| * @param {Buffer} input The buffer with the bytes to decode. |
| * @param {boolean} copy (optional) If true, copy from the input when making new |
| * Blob values. If false, then Blob values share memory with the input, which |
| * must remain unchanged while the Blob values are used. If omitted, use true. |
| */ |
| Tlv0_2WireFormat.prototype.decodeLpPacket = function(lpPacket, input, copy) |
| { |
| if (copy == null) |
| copy = true; |
| |
| lpPacket.clear(); |
| |
| var decoder = new TlvDecoder(input); |
| var endOffset = decoder.readNestedTlvsStart(Tlv.LpPacket_LpPacket); |
| |
| while (decoder.getOffset() < endOffset) { |
| // Imitate TlvDecoder.readTypeAndLength. |
| var fieldType = decoder.readVarNumber(); |
| var fieldLength = decoder.readVarNumber(); |
| var fieldEndOffset = decoder.getOffset() + fieldLength; |
| if (fieldEndOffset > input.length) |
| throw new DecodingException(new Error("TLV length exceeds the buffer length")); |
| |
| if (fieldType == Tlv.LpPacket_Fragment) { |
| // Set the fragment to the bytes of the TLV value. |
| lpPacket.setFragmentWireEncoding |
| (new Blob(decoder.getSlice(decoder.getOffset(), fieldEndOffset), copy)); |
| decoder.seek(fieldEndOffset); |
| |
| // The fragment is supposed to be the last field. |
| break; |
| } |
| else if (fieldType == Tlv.LpPacket_Nack) { |
| var networkNack = new NetworkNack(); |
| var code = decoder.readOptionalNonNegativeIntegerTlv |
| (Tlv.LpPacket_NackReason, fieldEndOffset); |
| var reason; |
| // The enum numeric values are the same as this wire format, so use as is. |
| if (code < 0 || code == NetworkNack.Reason.NONE) |
| // This includes an omitted NackReason. |
| networkNack.setReason(NetworkNack.Reason.NONE); |
| else if (code == NetworkNack.Reason.CONGESTION || |
| code == NetworkNack.Reason.DUPLICATE || |
| code == NetworkNack.Reason.NO_ROUTE) |
| networkNack.setReason(code); |
| else { |
| // Unrecognized reason. |
| networkNack.setReason(NetworkNack.Reason.OTHER_CODE); |
| networkNack.setOtherReasonCode(code); |
| } |
| |
| lpPacket.addHeaderField(networkNack); |
| } |
| else if (fieldType == Tlv.LpPacket_IncomingFaceId) { |
| var incomingFaceId = new IncomingFaceId(); |
| incomingFaceId.setFaceId(decoder.readNonNegativeInteger(fieldLength)); |
| lpPacket.addHeaderField(incomingFaceId); |
| } |
| else { |
| // Unrecognized field type. The conditions for ignoring are here: |
| // http://redmine.named-data.net/projects/nfd/wiki/NDNLPv2 |
| var canIgnore = |
| (fieldType >= Tlv.LpPacket_IGNORE_MIN && |
| fieldType <= Tlv.LpPacket_IGNORE_MAX && |
| (fieldType & 0x01) === 1); |
| if (!canIgnore) |
| throw new DecodingException(new Error("Did not get the expected TLV type")); |
| |
| // Ignore. |
| decoder.seek(fieldEndOffset); |
| } |
| |
| decoder.finishNestedTlvs(fieldEndOffset); |
| } |
| |
| decoder.finishNestedTlvs(endOffset); |
| }; |
| |
| /** |
| * Encode delegationSet as a sequence of NDN-TLV Delegation, and return the |
| * encoding. Note that the sequence of Delegation does not have an outer TLV |
| * type and length because it is intended to use the type and length of a Data |
| * packet's Content. |
| * @param {DelegationSet} delegationSet The DelegationSet object to encode. |
| * @return {Blob} A Blob containing the encoding. |
| */ |
| Tlv0_2WireFormat.prototype.encodeDelegationSet = function(delegationSet) |
| { |
| var encoder = new TlvEncoder(256); |
| |
| // Encode backwards. |
| for (var i = delegationSet.size() - 1; i >= 0; --i) { |
| var saveLength = encoder.getLength(); |
| |
| Tlv0_2WireFormat.encodeName(delegationSet.get(i).getName(), encoder); |
| encoder.writeNonNegativeIntegerTlv |
| (Tlv.Link_Preference, delegationSet.get(i).getPreference()); |
| |
| encoder.writeTypeAndLength |
| (Tlv.Link_Delegation, encoder.getLength() - saveLength); |
| } |
| |
| return new Blob(encoder.getOutput(), false); |
| }; |
| |
| /** |
| * Decode input as a sequence of NDN-TLV Delegation and set the fields of the |
| * delegationSet object. Note that the sequence of Delegation does not have an |
| * outer TLV type and length because it is intended to use the type and length |
| * of a Data packet's Content. This ignores any elements after the sequence |
| * of Delegation. |
| * @param {DelegationSet} delegationSet The DelegationSet object |
| * whose fields are updated. |
| * @param {Buffer} input The buffer with the bytes to decode. |
| * @param {boolean} copy (optional) If true, copy from the input when making new |
| * Blob values. If false, then Blob values share memory with the input, which |
| * must remain unchanged while the Blob values are used. If omitted, use true. |
| */ |
| Tlv0_2WireFormat.prototype.decodeDelegationSet = function |
| (delegationSet, input, copy) |
| { |
| if (copy == null) |
| copy = true; |
| |
| var decoder = new TlvDecoder(input); |
| var endOffset = input.length; |
| |
| delegationSet.clear(); |
| while (decoder.getOffset() < endOffset) { |
| decoder.readTypeAndLength(Tlv.Link_Delegation); |
| var preference = decoder.readNonNegativeIntegerTlv(Tlv.Link_Preference); |
| var name = new Name(); |
| Tlv0_2WireFormat.decodeName(name, decoder, copy); |
| |
| // Add unsorted to preserve the order so that Interest selected delegation |
| // index will work. |
| delegationSet.addUnsorted(preference, name); |
| } |
| }; |
| |
| /** |
| * Encode the EncryptedContent in NDN-TLV and return the encoding. |
| * @param {EncryptedContent} encryptedContent The EncryptedContent object to |
| * encode. |
| * @return {Blob} A Blob containing the encoding. |
| */ |
| Tlv0_2WireFormat.prototype.encodeEncryptedContent = function(encryptedContent) |
| { |
| var encoder = new TlvEncoder(256); |
| var saveLength = encoder.getLength(); |
| |
| // Encode backwards. |
| encoder.writeBlobTlv |
| (Tlv.Encrypt_EncryptedPayload, encryptedContent.getPayload().buf()); |
| encoder.writeOptionalBlobTlv |
| (Tlv.Encrypt_InitialVector, encryptedContent.getInitialVector().buf()); |
| // Assume the algorithmType value is the same as the TLV type. |
| encoder.writeNonNegativeIntegerTlv |
| (Tlv.Encrypt_EncryptionAlgorithm, encryptedContent.getAlgorithmType()); |
| Tlv0_2WireFormat.encodeKeyLocator |
| (Tlv.KeyLocator, encryptedContent.getKeyLocator(), encoder); |
| |
| encoder.writeTypeAndLength |
| (Tlv.Encrypt_EncryptedContent, encoder.getLength() - saveLength); |
| |
| return new Blob(encoder.getOutput(), false); |
| }; |
| |
| /** |
| * Decode input as an EncryptedContent in NDN-TLV and set the fields of the |
| * encryptedContent object. |
| * @param {EncryptedContent} encryptedContent The EncryptedContent object |
| * whose fields are updated. |
| * @param {Buffer} input The buffer with the bytes to decode. |
| * @param {boolean} copy (optional) If true, copy from the input when making new |
| * Blob values. If false, then Blob values share memory with the input, which |
| * must remain unchanged while the Blob values are used. If omitted, use true. |
| */ |
| Tlv0_2WireFormat.prototype.decodeEncryptedContent = function |
| (encryptedContent, input, copy) |
| { |
| if (copy == null) |
| copy = true; |
| |
| var decoder = new TlvDecoder(input); |
| var endOffset = decoder. |
| readNestedTlvsStart(Tlv.Encrypt_EncryptedContent); |
| |
| Tlv0_2WireFormat.decodeKeyLocator |
| (Tlv.KeyLocator, encryptedContent.getKeyLocator(), decoder, copy); |
| encryptedContent.setAlgorithmType |
| (decoder.readNonNegativeIntegerTlv(Tlv.Encrypt_EncryptionAlgorithm)); |
| encryptedContent.setInitialVector |
| (new Blob(decoder.readOptionalBlobTlv |
| (Tlv.Encrypt_InitialVector, endOffset), copy)); |
| encryptedContent.setPayload |
| (new Blob(decoder.readBlobTlv(Tlv.Encrypt_EncryptedPayload), copy)); |
| |
| decoder.finishNestedTlvs(endOffset); |
| }; |
| |
| /** |
| * Get a singleton instance of a Tlv0_2WireFormat. To always use the |
| * preferred version NDN-TLV, you should use TlvWireFormat.get(). |
| * @return {Tlv0_2WireFormat} The singleton instance. |
| */ |
| Tlv0_2WireFormat.get = function() |
| { |
| if (Tlv0_2WireFormat.instance === null) |
| Tlv0_2WireFormat.instance = new Tlv0_2WireFormat(); |
| return Tlv0_2WireFormat.instance; |
| }; |
| |
| /** |
| * Encode the name component to the encoder as NDN-TLV. This handles different |
| * component types such as ImplicitSha256DigestComponent. |
| * @param {Name.Component} component The name component to encode. |
| * @param {TlvEncoder} encoder The encoder to receive the encoding. |
| */ |
| Tlv0_2WireFormat.encodeNameComponent = function(component, encoder) |
| { |
| var type = component.isImplicitSha256Digest() ? |
| Tlv.ImplicitSha256DigestComponent : Tlv.NameComponent; |
| encoder.writeBlobTlv(type, component.getValue().buf()); |
| }; |
| |
| /** |
| * Decode the name component as NDN-TLV and return the component. This handles |
| * different component types such as ImplicitSha256DigestComponent. |
| * @param {TlvDecoder} decoder The decoder with the input. |
| * @param {boolean} copy (optional) If true, copy from the input when making new |
| * Blob values. If false, then Blob values share memory with the input, which |
| * must remain unchanged while the Blob values are used. If omitted, use true. |
| * @return {Name.Component} A new Name.Component. |
| */ |
| Tlv0_2WireFormat.decodeNameComponent = function(decoder, copy) |
| { |
| if (copy == null) |
| copy = true; |
| |
| var savePosition = decoder.getOffset(); |
| var type = decoder.readVarNumber(); |
| // Restore the position. |
| decoder.seek(savePosition); |
| |
| var value = new Blob(decoder.readBlobTlv(type), copy); |
| if (type === Tlv.ImplicitSha256DigestComponent) |
| return Name.Component.fromImplicitSha256Digest(value); |
| else |
| return new Name.Component(value); |
| }; |
| |
| /** |
| * Encode the name to the encoder. |
| * @param {Name} name The name to encode. |
| * @param {TlvEncoder} encoder The encoder to receive the encoding. |
| * @return {object} An associative array with fields |
| * (signedPortionBeginOffset, signedPortionEndOffset) where |
| * signedPortionBeginOffset is the offset in the encoding of the beginning of |
| * the signed portion, and signedPortionEndOffset is the offset in the encoding |
| * of the end of the signed portion. The signed portion starts from the first |
| * name component and ends just before the final name component (which is |
| * assumed to be a signature for a signed interest). |
| */ |
| Tlv0_2WireFormat.encodeName = function(name, encoder) |
| { |
| var saveLength = encoder.getLength(); |
| |
| // Encode the components backwards. |
| var signedPortionEndOffsetFromBack; |
| for (var i = name.size() - 1; i >= 0; --i) { |
| Tlv0_2WireFormat.encodeNameComponent(name.get(i), encoder); |
| if (i == name.size() - 1) |
| signedPortionEndOffsetFromBack = encoder.getLength(); |
| } |
| |
| var signedPortionBeginOffsetFromBack = encoder.getLength(); |
| encoder.writeTypeAndLength(Tlv.Name, encoder.getLength() - saveLength); |
| |
| var signedPortionBeginOffset = |
| encoder.getLength() - signedPortionBeginOffsetFromBack; |
| var signedPortionEndOffset; |
| if (name.size() == 0) |
| // There is no "final component", so set signedPortionEndOffset arbitrarily. |
| signedPortionEndOffset = signedPortionBeginOffset; |
| else |
| signedPortionEndOffset = encoder.getLength() - signedPortionEndOffsetFromBack; |
| |
| return { signedPortionBeginOffset: signedPortionBeginOffset, |
| signedPortionEndOffset: signedPortionEndOffset }; |
| }; |
| |
| /** |
| * Clear the name, decode a Name from the decoder and set the fields of the name |
| * object. |
| * @param {Name} name The name object whose fields are updated. |
| * @param {TlvDecoder} decoder The decoder with the input. |
| * @return {object} An associative array with fields |
| * (signedPortionBeginOffset, signedPortionEndOffset) where |
| * signedPortionBeginOffset is the offset in the encoding of the beginning of |
| * the signed portion, and signedPortionEndOffset is the offset in the encoding |
| * of the end of the signed portion. The signed portion starts from the first |
| * name component and ends just before the final name component (which is |
| * assumed to be a signature for a signed interest). |
| */ |
| Tlv0_2WireFormat.decodeName = function(name, decoder, copy) |
| { |
| name.clear(); |
| |
| var endOffset = decoder.readNestedTlvsStart(Tlv.Name); |
| var signedPortionBeginOffset = decoder.getOffset(); |
| // In case there are no components, set signedPortionEndOffset arbitrarily. |
| var signedPortionEndOffset = signedPortionBeginOffset; |
| |
| while (decoder.getOffset() < endOffset) { |
| signedPortionEndOffset = decoder.getOffset(); |
| name.append(Tlv0_2WireFormat.decodeNameComponent(decoder, copy)); |
| } |
| |
| decoder.finishNestedTlvs(endOffset); |
| |
| return { signedPortionBeginOffset: signedPortionBeginOffset, |
| signedPortionEndOffset: signedPortionEndOffset }; |
| }; |
| |
| /** |
| * Encode the interest selectors. If no selectors are written, do not output a |
| * Selectors TLV. |
| */ |
| Tlv0_2WireFormat.encodeSelectors = function(interest, encoder) |
| { |
| var saveLength = encoder.getLength(); |
| |
| // Encode backwards. |
| if (interest.getMustBeFresh()) |
| encoder.writeTypeAndLength(Tlv.MustBeFresh, 0); |
| encoder.writeOptionalNonNegativeIntegerTlv( |
| Tlv.ChildSelector, interest.getChildSelector()); |
| if (interest.getExclude().size() > 0) |
| Tlv0_2WireFormat.encodeExclude(interest.getExclude(), encoder); |
| |
| if (interest.getKeyLocator().getType() != null) |
| Tlv0_2WireFormat.encodeKeyLocator |
| (Tlv.PublisherPublicKeyLocator, interest.getKeyLocator(), encoder); |
| |
| encoder.writeOptionalNonNegativeIntegerTlv( |
| Tlv.MaxSuffixComponents, interest.getMaxSuffixComponents()); |
| encoder.writeOptionalNonNegativeIntegerTlv( |
| Tlv.MinSuffixComponents, interest.getMinSuffixComponents()); |
| |
| // Only output the type and length if values were written. |
| if (encoder.getLength() != saveLength) |
| encoder.writeTypeAndLength(Tlv.Selectors, encoder.getLength() - saveLength); |
| }; |
| |
| Tlv0_2WireFormat.decodeSelectors = function(interest, decoder, copy) |
| { |
| if (copy == null) |
| copy = true; |
| |
| var endOffset = decoder.readNestedTlvsStart(Tlv.Selectors); |
| |
| interest.setMinSuffixComponents(decoder.readOptionalNonNegativeIntegerTlv |
| (Tlv.MinSuffixComponents, endOffset)); |
| interest.setMaxSuffixComponents(decoder.readOptionalNonNegativeIntegerTlv |
| (Tlv.MaxSuffixComponents, endOffset)); |
| |
| if (decoder.peekType(Tlv.PublisherPublicKeyLocator, endOffset)) |
| Tlv0_2WireFormat.decodeKeyLocator |
| (Tlv.PublisherPublicKeyLocator, interest.getKeyLocator(), decoder, copy); |
| else |
| interest.getKeyLocator().clear(); |
| |
| if (decoder.peekType(Tlv.Exclude, endOffset)) |
| Tlv0_2WireFormat.decodeExclude(interest.getExclude(), decoder, copy); |
| else |
| interest.getExclude().clear(); |
| |
| interest.setChildSelector(decoder.readOptionalNonNegativeIntegerTlv |
| (Tlv.ChildSelector, endOffset)); |
| interest.setMustBeFresh(decoder.readBooleanTlv(Tlv.MustBeFresh, endOffset)); |
| |
| decoder.finishNestedTlvs(endOffset); |
| }; |
| |
| Tlv0_2WireFormat.encodeExclude = function(exclude, encoder) |
| { |
| var saveLength = encoder.getLength(); |
| |
| // TODO: Do we want to order the components (except for ANY)? |
| // Encode the entries backwards. |
| for (var i = exclude.size() - 1; i >= 0; --i) { |
| var entry = exclude.get(i); |
| |
| if (entry == Exclude.ANY) |
| encoder.writeTypeAndLength(Tlv.Any, 0); |
| else |
| Tlv0_2WireFormat.encodeNameComponent(entry, encoder); |
| } |
| |
| encoder.writeTypeAndLength(Tlv.Exclude, encoder.getLength() - saveLength); |
| }; |
| |
| Tlv0_2WireFormat.decodeExclude = function(exclude, decoder, copy) |
| { |
| if (copy == null) |
| copy = true; |
| |
| var endOffset = decoder.readNestedTlvsStart(Tlv.Exclude); |
| |
| exclude.clear(); |
| while (decoder.getOffset() < endOffset) { |
| if (decoder.peekType(Tlv.Any, endOffset)) { |
| // Read past the Any TLV. |
| decoder.readBooleanTlv(Tlv.Any, endOffset); |
| exclude.appendAny(); |
| } |
| else |
| exclude.appendComponent(Tlv0_2WireFormat.decodeNameComponent(decoder, copy)); |
| } |
| |
| decoder.finishNestedTlvs(endOffset); |
| }; |
| |
| Tlv0_2WireFormat.encodeKeyLocator = function(type, keyLocator, encoder) |
| { |
| var saveLength = encoder.getLength(); |
| |
| // Encode backwards. |
| if (keyLocator.getType() != null) { |
| if (keyLocator.getType() == KeyLocatorType.KEYNAME) |
| Tlv0_2WireFormat.encodeName(keyLocator.getKeyName(), encoder); |
| else if (keyLocator.getType() == KeyLocatorType.KEY_LOCATOR_DIGEST && |
| keyLocator.getKeyData().size() > 0) |
| encoder.writeBlobTlv(Tlv.KeyLocatorDigest, keyLocator.getKeyData().buf()); |
| else |
| throw new Error("Unrecognized KeyLocatorType " + keyLocator.getType()); |
| } |
| |
| encoder.writeTypeAndLength(type, encoder.getLength() - saveLength); |
| }; |
| |
| Tlv0_2WireFormat.decodeKeyLocator = function |
| (expectedType, keyLocator, decoder, copy) |
| { |
| if (copy == null) |
| copy = true; |
| |
| var endOffset = decoder.readNestedTlvsStart(expectedType); |
| |
| keyLocator.clear(); |
| |
| if (decoder.getOffset() == endOffset) |
| // The KeyLocator is omitted, so leave the fields as none. |
| return; |
| |
| if (decoder.peekType(Tlv.Name, endOffset)) { |
| // KeyLocator is a Name. |
| keyLocator.setType(KeyLocatorType.KEYNAME); |
| Tlv0_2WireFormat.decodeName(keyLocator.getKeyName(), decoder, copy); |
| } |
| else if (decoder.peekType(Tlv.KeyLocatorDigest, endOffset)) { |
| // KeyLocator is a KeyLocatorDigest. |
| keyLocator.setType(KeyLocatorType.KEY_LOCATOR_DIGEST); |
| keyLocator.setKeyData |
| (new Blob(decoder.readBlobTlv(Tlv.KeyLocatorDigest), copy)); |
| } |
| else |
| throw new DecodingException(new Error |
| ("decodeKeyLocator: Unrecognized key locator type")); |
| |
| decoder.finishNestedTlvs(endOffset); |
| }; |
| |
| /** |
| * An internal method to encode signature as the appropriate form of |
| * SignatureInfo in NDN-TLV. |
| * @param {Signature} signature An object of a subclass of Signature to encode. |
| * @param {TlvEncoder} encoder The encoder. |
| */ |
| Tlv0_2WireFormat.encodeSignatureInfo_ = function(signature, encoder) |
| { |
| if (signature instanceof GenericSignature) { |
| // Handle GenericSignature separately since it has the entire encoding. |
| var encoding = signature.getSignatureInfoEncoding(); |
| |
| // Do a test decoding to sanity check that it is valid TLV. |
| try { |
| var decoder = new TlvDecoder(encoding.buf()); |
| var endOffset = decoder.readNestedTlvsStart(Tlv.SignatureInfo); |
| decoder.readNonNegativeIntegerTlv(Tlv.SignatureType); |
| decoder.finishNestedTlvs(endOffset); |
| } catch (ex) { |
| throw new Error |
| ("The GenericSignature encoding is not a valid NDN-TLV SignatureInfo: " + |
| ex.message); |
| } |
| |
| encoder.writeBuffer(encoding.buf()); |
| return; |
| } |
| |
| var saveLength = encoder.getLength(); |
| |
| // Encode backwards. |
| if (signature instanceof Sha256WithRsaSignature) { |
| Tlv0_2WireFormat.encodeKeyLocator |
| (Tlv.KeyLocator, signature.getKeyLocator(), encoder); |
| encoder.writeNonNegativeIntegerTlv |
| (Tlv.SignatureType, Tlv.SignatureType_SignatureSha256WithRsa); |
| } |
| else if (signature instanceof Sha256WithEcdsaSignature) { |
| Tlv0_2WireFormat.encodeKeyLocator |
| (Tlv.KeyLocator, signature.getKeyLocator(), encoder); |
| encoder.writeNonNegativeIntegerTlv |
| (Tlv.SignatureType, Tlv.SignatureType_SignatureSha256WithEcdsa); |
| } |
| else if (signature instanceof HmacWithSha256Signature) { |
| Tlv0_2WireFormat.encodeKeyLocator |
| (Tlv.KeyLocator, signature.getKeyLocator(), encoder); |
| encoder.writeNonNegativeIntegerTlv |
| (Tlv.SignatureType, Tlv.SignatureType_SignatureHmacWithSha256); |
| } |
| else if (signature instanceof DigestSha256Signature) |
| encoder.writeNonNegativeIntegerTlv |
| (Tlv.SignatureType, Tlv.SignatureType_DigestSha256); |
| else |
| throw new Error("encodeSignatureInfo: Unrecognized Signature object type"); |
| |
| encoder.writeTypeAndLength(Tlv.SignatureInfo, encoder.getLength() - saveLength); |
| }; |
| |
| Tlv0_2WireFormat.decodeSignatureInfo = function(data, decoder, copy) |
| { |
| if (copy == null) |
| copy = true; |
| |
| var beginOffset = decoder.getOffset(); |
| var endOffset = decoder.readNestedTlvsStart(Tlv.SignatureInfo); |
| |
| var signatureType = decoder.readNonNegativeIntegerTlv(Tlv.SignatureType); |
| if (signatureType == Tlv.SignatureType_SignatureSha256WithRsa) { |
| data.setSignature(new Sha256WithRsaSignature()); |
| // Modify data's signature object because if we create an object |
| // and set it, then data will have to copy all the fields. |
| var signatureInfo = data.getSignature(); |
| Tlv0_2WireFormat.decodeKeyLocator |
| (Tlv.KeyLocator, signatureInfo.getKeyLocator(), decoder, copy); |
| } |
| else if (signatureType == Tlv.SignatureType_SignatureSha256WithEcdsa) { |
| data.setSignature(new Sha256WithEcdsaSignature()); |
| var signatureInfo = data.getSignature(); |
| Tlv0_2WireFormat.decodeKeyLocator |
| (Tlv.KeyLocator, signatureInfo.getKeyLocator(), decoder, copy); |
| } |
| else if (signatureType == Tlv.SignatureType_SignatureHmacWithSha256) { |
| data.setSignature(new HmacWithSha256Signature()); |
| var signatureInfo = data.getSignature(); |
| Tlv0_2WireFormat.decodeKeyLocator |
| (Tlv.KeyLocator, signatureInfo.getKeyLocator(), decoder, copy); |
| } |
| else if (signatureType == Tlv.SignatureType_DigestSha256) |
| data.setSignature(new DigestSha256Signature()); |
| else { |
| data.setSignature(new GenericSignature()); |
| var signatureInfo = data.getSignature(); |
| |
| // Get the bytes of the SignatureInfo TLV. |
| signatureInfo.setSignatureInfoEncoding |
| (new Blob(decoder.getSlice(beginOffset, endOffset), copy), signatureType); |
| } |
| |
| decoder.finishNestedTlvs(endOffset); |
| }; |
| |
| Tlv0_2WireFormat.encodeMetaInfo = function(metaInfo, encoder) |
| { |
| var saveLength = encoder.getLength(); |
| |
| // Encode backwards. |
| var finalBlockIdBuf = metaInfo.getFinalBlockId().getValue().buf(); |
| if (finalBlockIdBuf != null && finalBlockIdBuf.length > 0) { |
| // FinalBlockId has an inner NameComponent. |
| var finalBlockIdSaveLength = encoder.getLength(); |
| Tlv0_2WireFormat.encodeNameComponent(metaInfo.getFinalBlockId(), encoder); |
| encoder.writeTypeAndLength |
| (Tlv.FinalBlockId, encoder.getLength() - finalBlockIdSaveLength); |
| } |
| |
| encoder.writeOptionalNonNegativeIntegerTlv |
| (Tlv.FreshnessPeriod, metaInfo.getFreshnessPeriod()); |
| if (metaInfo.getType() != ContentType.BLOB) { |
| // Not the default, so we need to encode the type. |
| if (metaInfo.getType() == ContentType.LINK || |
| metaInfo.getType() == ContentType.KEY || |
| metaInfo.getType() == ContentType.NACK) |
| // The ContentType enum is set up with the correct integer for |
| // each NDN-TLV ContentType. |
| encoder.writeNonNegativeIntegerTlv(Tlv.ContentType, metaInfo.getType()); |
| else if (metaInfo.getType() == ContentType.OTHER_CODE) |
| encoder.writeNonNegativeIntegerTlv |
| (Tlv.ContentType, metaInfo.getOtherTypeCode()); |
| else |
| // We don't expect this to happen. |
| throw new Error("unrecognized TLV ContentType"); |
| } |
| |
| encoder.writeTypeAndLength(Tlv.MetaInfo, encoder.getLength() - saveLength); |
| }; |
| |
| Tlv0_2WireFormat.decodeMetaInfo = function(metaInfo, decoder, copy) |
| { |
| if (copy == null) |
| copy = true; |
| |
| var endOffset = decoder.readNestedTlvsStart(Tlv.MetaInfo); |
| |
| var type = decoder.readOptionalNonNegativeIntegerTlv |
| (Tlv.ContentType, endOffset); |
| if (type == null || type < 0 || type === ContentType.BLOB) |
| metaInfo.setType(ContentType.BLOB); |
| else if (type === ContentType.LINK || |
| type === ContentType.KEY || |
| type === ContentType.NACK) |
| // The ContentType enum is set up with the correct integer for each NDN-TLV |
| // ContentType. |
| metaInfo.setType(type); |
| else { |
| // Unrecognized content type. |
| metaInfo.setType(ContentType.OTHER_CODE); |
| metaInfo.setOtherTypeCode(type); |
| } |
| |
| metaInfo.setFreshnessPeriod |
| (decoder.readOptionalNonNegativeIntegerTlv(Tlv.FreshnessPeriod, endOffset)); |
| if (decoder.peekType(Tlv.FinalBlockId, endOffset)) { |
| var finalBlockIdEndOffset = decoder.readNestedTlvsStart(Tlv.FinalBlockId); |
| metaInfo.setFinalBlockId(Tlv0_2WireFormat.decodeNameComponent(decoder, copy)); |
| decoder.finishNestedTlvs(finalBlockIdEndOffset); |
| } |
| else |
| metaInfo.setFinalBlockId(null); |
| |
| decoder.finishNestedTlvs(endOffset); |
| }; |
| |
| Tlv0_2WireFormat.encodeControlParameters = function(controlParameters, encoder) |
| { |
| var saveLength = encoder.getLength(); |
| |
| // Encode backwards. |
| encoder.writeOptionalNonNegativeIntegerTlv |
| (Tlv.ControlParameters_ExpirationPeriod, |
| controlParameters.getExpirationPeriod()); |
| |
| if (controlParameters.getStrategy().size() > 0){ |
| var strategySaveLength = encoder.getLength(); |
| Tlv0_2WireFormat.encodeName(controlParameters.getStrategy(), encoder); |
| encoder.writeTypeAndLength(Tlv.ControlParameters_Strategy, |
| encoder.getLength() - strategySaveLength); |
| } |
| |
| var flags = controlParameters.getForwardingFlags().getNfdForwardingFlags(); |
| if (flags != new ForwardingFlags().getNfdForwardingFlags()) |
| // The flags are not the default value. |
| encoder.writeNonNegativeIntegerTlv |
| (Tlv.ControlParameters_Flags, flags); |
| |
| encoder.writeOptionalNonNegativeIntegerTlv |
| (Tlv.ControlParameters_Cost, controlParameters.getCost()); |
| encoder.writeOptionalNonNegativeIntegerTlv |
| (Tlv.ControlParameters_Origin, controlParameters.getOrigin()); |
| encoder.writeOptionalNonNegativeIntegerTlv |
| (Tlv.ControlParameters_LocalControlFeature, |
| controlParameters.getLocalControlFeature()); |
| |
| if (controlParameters.getUri().length != 0) |
| encoder.writeBlobTlv |
| (Tlv.ControlParameters_Uri, new Blob(controlParameters.getUri()).buf()); |
| |
| encoder.writeOptionalNonNegativeIntegerTlv |
| (Tlv.ControlParameters_FaceId, controlParameters.getFaceId()); |
| if (controlParameters.getName() != null) |
| Tlv0_2WireFormat.encodeName(controlParameters.getName(), encoder); |
| |
| encoder.writeTypeAndLength |
| (Tlv.ControlParameters_ControlParameters, encoder.getLength() - saveLength); |
| }; |
| |
| Tlv0_2WireFormat.decodeControlParameters = function |
| (controlParameters, decoder, copy) |
| { |
| if (copy == null) |
| copy = true; |
| |
| controlParameters.clear(); |
| var endOffset = decoder. |
| readNestedTlvsStart(Tlv.ControlParameters_ControlParameters); |
| |
| // decode name |
| if (decoder.peekType(Tlv.Name, endOffset)) { |
| var name = new Name(); |
| Tlv0_2WireFormat.decodeName(name, decoder, copy); |
| controlParameters.setName(name); |
| } |
| |
| // decode face ID |
| controlParameters.setFaceId(decoder.readOptionalNonNegativeIntegerTlv |
| (Tlv.ControlParameters_FaceId, endOffset)); |
| |
| // decode URI |
| if (decoder.peekType(Tlv.ControlParameters_Uri, endOffset)) { |
| // Set copy false since we just immediately get the string. |
| var uri = new Blob |
| (decoder.readOptionalBlobTlv(Tlv.ControlParameters_Uri, endOffset), false); |
| controlParameters.setUri(uri.toString()); |
| } |
| |
| // decode integers |
| controlParameters.setLocalControlFeature(decoder. |
| readOptionalNonNegativeIntegerTlv( |
| Tlv.ControlParameters_LocalControlFeature, endOffset)); |
| controlParameters.setOrigin(decoder. |
| readOptionalNonNegativeIntegerTlv(Tlv.ControlParameters_Origin, |
| endOffset)); |
| controlParameters.setCost(decoder.readOptionalNonNegativeIntegerTlv( |
| Tlv.ControlParameters_Cost, endOffset)); |
| |
| // set forwarding flags |
| if (decoder.peekType(Tlv.ControlParameters_Flags, endOffset)) { |
| var flags = new ForwardingFlags(); |
| flags.setNfdForwardingFlags(decoder. |
| readNonNegativeIntegerTlv(Tlv.ControlParameters_Flags, endOffset)); |
| controlParameters.setForwardingFlags(flags); |
| } |
| |
| // decode strategy |
| if (decoder.peekType(Tlv.ControlParameters_Strategy, endOffset)) { |
| var strategyEndOffset = decoder.readNestedTlvsStart(Tlv.ControlParameters_Strategy); |
| Tlv0_2WireFormat.decodeName(controlParameters.getStrategy(), decoder, copy); |
| decoder.finishNestedTlvs(strategyEndOffset); |
| } |
| |
| // decode expiration period |
| controlParameters.setExpirationPeriod( |
| decoder.readOptionalNonNegativeIntegerTlv( |
| Tlv.ControlParameters_ExpirationPeriod, endOffset)); |
| |
| decoder.finishNestedTlvs(endOffset); |
| }; |
| /** |
| * Copyright (C) 2013-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Tlv0_2WireFormat = require('./tlv-0_2-wire-format.js').Tlv0_2WireFormat; |
| |
| /** |
| * A Tlv0_1_1WireFormat extends Tlv0_2WireFormat so that it is an alias in case |
| * any applications use Tlv0_1_1WireFormat directly. These two wire formats are |
| * the same except that Tlv0_2WireFormat adds support for the name component |
| * type ImplicitSha256Digest. |
| * @constructor |
| */ |
| var Tlv0_1_1WireFormat = function Tlv0_1_1WireFormat() |
| { |
| // Inherit from Tlv0_2WireFormat. |
| Tlv0_2WireFormat.call(this); |
| }; |
| |
| Tlv0_1_1WireFormat.prototype = new Tlv0_2WireFormat(); |
| Tlv0_1_1WireFormat.prototype.name = "Tlv0_1_1WireFormat"; |
| |
| exports.Tlv0_1_1WireFormat = Tlv0_1_1WireFormat; |
| |
| // Default object. |
| Tlv0_1_1WireFormat.instance = null; |
| |
| /** |
| * Get a singleton instance of a Tlv0_1_1WireFormat. |
| * @return {Tlv0_1_1WireFormat} The singleton instance. |
| */ |
| Tlv0_1_1WireFormat.get = function() |
| { |
| if (Tlv0_1_1WireFormat.instance === null) |
| Tlv0_1_1WireFormat.instance = new Tlv0_1_1WireFormat(); |
| return Tlv0_1_1WireFormat.instance; |
| }; |
| /** |
| * Copyright (C) 2013-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var WireFormat = require('./wire-format.js').WireFormat; /** @ignore */ |
| var Tlv0_1_1WireFormat = require('./tlv-0_1_1-wire-format.js').Tlv0_1_1WireFormat; |
| |
| /** |
| * A Tlv0_1WireFormat extends Tlv0_1_1WireFormat so that it is an alias in case |
| * any applications use Tlv0_1WireFormat directly. These two wire formats are |
| * the same except that Tlv0_1_1WireFormat adds support for |
| * Sha256WithEcdsaSignature. |
| * @constructor |
| */ |
| var Tlv0_1WireFormat = function Tlv0_1WireFormat() |
| { |
| // Inherit from Tlv0_1_1WireFormat. |
| Tlv0_1_1WireFormat.call(this); |
| }; |
| |
| Tlv0_1WireFormat.prototype = new Tlv0_1_1WireFormat(); |
| Tlv0_1WireFormat.prototype.name = "Tlv0_1WireFormat"; |
| |
| exports.Tlv0_1WireFormat = Tlv0_1WireFormat; |
| |
| // Default object. |
| Tlv0_1WireFormat.instance = null; |
| |
| /** |
| * Get a singleton instance of a Tlv0_1WireFormat. |
| * @return {Tlv0_1WireFormat} The singleton instance. |
| */ |
| Tlv0_1WireFormat.get = function() |
| { |
| if (Tlv0_1WireFormat.instance === null) |
| Tlv0_1WireFormat.instance = new Tlv0_1WireFormat(); |
| return Tlv0_1WireFormat.instance; |
| }; |
| /** |
| * Copyright (C) 2013-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var WireFormat = require('./wire-format.js').WireFormat; /** @ignore */ |
| var Tlv0_2WireFormat = require('./tlv-0_2-wire-format.js').Tlv0_2WireFormat; |
| |
| /** |
| * A TlvWireFormat extends WireFormat to override its methods to |
| * implement encoding and decoding using the preferred implementation of NDN-TLV. |
| * @constructor |
| */ |
| var TlvWireFormat = function TlvWireFormat() |
| { |
| // Inherit from Tlv0_2WireFormat. |
| Tlv0_2WireFormat.call(this); |
| }; |
| |
| TlvWireFormat.prototype = new Tlv0_2WireFormat(); |
| TlvWireFormat.prototype.name = "TlvWireFormat"; |
| |
| exports.TlvWireFormat = TlvWireFormat; |
| |
| // Default object. |
| TlvWireFormat.instance = null; |
| |
| /** |
| * Get a singleton instance of a TlvWireFormat. Assuming that the default |
| * wire format was set with WireFormat.setDefaultWireFormat(TlvWireFormat.get()), |
| * you can check if this is the default wire encoding with |
| * if WireFormat.getDefaultWireFormat() == TlvWireFormat.get(). |
| * @return {TlvWireFormat} The singleton instance. |
| */ |
| TlvWireFormat.get = function() |
| { |
| if (TlvWireFormat.instance === null) |
| TlvWireFormat.instance = new TlvWireFormat(); |
| return TlvWireFormat.instance; |
| }; |
| |
| // On loading this module, make this the default wire format. |
| // This module will be loaded because WireFormat loads it. |
| WireFormat.setDefaultWireFormat(TlvWireFormat.get()); |
| /** |
| * This file contains utilities to help encode and decode NDN objects. |
| * Copyright (C) 2013-2016 Regents of the University of California. |
| * author: Meki Cheraoui |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var DataUtils = require('./data-utils.js').DataUtils; /** @ignore */ |
| var KeyLocatorType = require('../key-locator.js').KeyLocatorType; /** @ignore */ |
| var Interest = require('../interest.js').Interest; /** @ignore */ |
| var Data = require('../data.js').Data; /** @ignore */ |
| var Sha256WithRsaSignature = require('../sha256-with-rsa-signature.js').Sha256WithRsaSignature; /** @ignore */ |
| var Sha256WithEcdsaSignature = require('../sha256-with-ecdsa-signature.js').Sha256WithEcdsaSignature; /** @ignore */ |
| var HmacWithSha256Signature = require('../hmac-with-sha256-signature.js').HmacWithSha256Signature; /** @ignore */ |
| var DigestSha256Signature = require('../digest-sha256-signature.js').DigestSha256Signature; /** @ignore */ |
| var ContentType = require('../meta-info.js').ContentType; /** @ignore */ |
| var WireFormat = require('./wire-format.js').WireFormat; |
| |
| /** |
| * An EncodingUtils has static methods for encoding data. |
| * @constructor |
| */ |
| var EncodingUtils = function EncodingUtils() |
| { |
| }; |
| |
| exports.EncodingUtils = EncodingUtils; |
| |
| EncodingUtils.encodeToHexInterest = function(interest, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| return DataUtils.toHex(interest.wireEncode(wireFormat).buf()); |
| }; |
| |
| EncodingUtils.encodeToHexData = function(data, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| return DataUtils.toHex(data.wireEncode(wireFormat).buf()); |
| }; |
| |
| EncodingUtils.decodeHexInterest = function(input, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| var interest = new Interest(); |
| interest.wireDecode(DataUtils.toNumbers(input), wireFormat); |
| return interest; |
| }; |
| |
| EncodingUtils.decodeHexData = function(input, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| var data = new Data(); |
| data.wireDecode(DataUtils.toNumbers(input), wireFormat); |
| return data; |
| }; |
| |
| /** |
| * Decode the Buffer array which holds SubjectPublicKeyInfo and return an RSAKey. |
| */ |
| EncodingUtils.decodeSubjectPublicKeyInfo = function(array) |
| { |
| var hex = DataUtils.toHex(array).toLowerCase(); |
| var a = _x509_getPublicKeyHexArrayFromCertHex(hex, _x509_getSubjectPublicKeyPosFromCertHex(hex, 0)); |
| var rsaKey = new RSAKey(); |
| rsaKey.setPublic(a[0], a[1]); |
| return rsaKey; |
| } |
| |
| /** |
| * Return a user friendly HTML string with the contents of data. |
| */ |
| EncodingUtils.dataToHtml = function(/* Data */ data) |
| { |
| if (data == -1) |
| return "NO CONTENT FOUND"; |
| if (data == -2) |
| return "CONTENT NAME IS EMPTY"; |
| |
| var output = ""; |
| function append(message) { |
| message = message.replace(/&/g, "&"); |
| message = message.replace(/</g, "<"); |
| |
| output += message; |
| output += "<br/>"; |
| } |
| |
| // Imitate dumpData in examples/node/test-encode-decode-data.js |
| |
| append("name: " + data.getName().toUri()); |
| if (data.getContent().size() > 0) { |
| append("content (raw): " + data.getContent().buf().toString('binary')); |
| append("content (hex): " + data.getContent().toHex()); |
| } |
| else |
| append("content: <empty>"); |
| |
| if (!(data.getMetaInfo().getType() == ContentType.BLOB)) { |
| if (data.getMetaInfo().getType() == ContentType.KEY) |
| append("metaInfo.type: KEY"); |
| else if (data.getMetaInfo().getType() == ContentType.LINK) |
| append("metaInfo.type: LINK"); |
| else if (data.getMetaInfo().getType() == ContentType.NACK) |
| append("metaInfo.type: NACK"); |
| else if (data.getMetaInfo().getType() == ContentType.OTHER_CODE) |
| append("metaInfo.type: other code " + data.getMetaInfo().getOtherTypeCode()); |
| } |
| append("metaInfo.freshnessPeriod (milliseconds): " + |
| (data.getMetaInfo().getFreshnessPeriod() >= 0 ? |
| "" + data.getMetaInfo().getFreshnessPeriod() : "<none>")); |
| append("metaInfo.finalBlockId: " + |
| (data.getMetaInfo().getFinalBlockId().getValue().size() > 0 ? |
| data.getMetaInfo().getFinalBlockId().getValue().toHex() : "<none>")); |
| |
| var keyLocator = null; |
| var signature = data.getSignature(); |
| if (signature instanceof Sha256WithRsaSignature) { |
| var signature = data.getSignature(); |
| append("Sha256WithRsa signature.signature: " + |
| (signature.getSignature().size() > 0 ? |
| signature.getSignature().toHex() : "<none>")); |
| keyLocator = signature.getKeyLocator(); |
| } |
| else if (signature instanceof Sha256WithEcdsaSignature) { |
| var signature = data.getSignature(); |
| append("Sha256WithEcdsa signature.signature: " + |
| (signature.getSignature().size() > 0 ? |
| signature.getSignature().toHex() : "<none>")); |
| keyLocator = signature.getKeyLocator(); |
| } |
| else if (signature instanceof HmacWithSha256Signature) { |
| var signature = data.getSignature(); |
| append("HmacWithSha256 signature.signature: " + |
| (signature.getSignature().size() > 0 ? |
| signature.getSignature().toHex() : "<none>")); |
| keyLocator = signature.getKeyLocator(); |
| } |
| else if (signature instanceof DigestSha256Signature) { |
| var signature = data.getSignature(); |
| append("DigestSha256 signature.signature: " + |
| (signature.getSignature().size() > 0 ? |
| signature.getSignature().toHex() : "<none>")); |
| } |
| if (keyLocator !== null) { |
| if (keyLocator.getType() == null) |
| append("signature.keyLocator: <none>"); |
| else if (keyLocator.getType() == KeyLocatorType.KEY_LOCATOR_DIGEST) |
| append("signature.keyLocator: KeyLocatorDigest: " + keyLocator.getKeyData().toHex()); |
| else if (keyLocator.getType() == KeyLocatorType.KEYNAME) |
| append("signature.keyLocator: KeyName: " + keyLocator.getKeyName().toUri()); |
| else |
| append("signature.keyLocator: <unrecognized ndn_KeyLocatorType>"); |
| } |
| |
| return output; |
| }; |
| |
| // |
| // Deprecated: For the browser, define these in the global scope. Applications should access as member of EncodingUtils. |
| // |
| |
| var encodeToHexInterest = function(interest) { return EncodingUtils.encodeToHexInterest(interest); } |
| var decodeHexInterest = function(input) { return EncodingUtils.decodeHexInterest(input); } |
| var decodeSubjectPublicKeyInfo = function(input) { return EncodingUtils.decodeSubjectPublicKeyInfo(input); } |
| |
| /** |
| * @deprecated Use interest.wireEncode(). |
| */ |
| function encodeToBinaryInterest(interest) { return interest.wireEncode().buf(); } |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From ndn-group-encrypt src/algo/aes https://github.com/named-data/ndn-group-encrypt |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| // (This is ported from ndn::gep::algo::Aes, and named AesAlgorithm because |
| // "Aes" is very short and not all the Common Client Libraries have namespaces.) |
| |
| /** @ignore */ |
| var Crypto = require('../../crypto.js'); /** @ignore */ |
| var Blob = require('../../util/blob.js').Blob; /** @ignore */ |
| var DecryptKey = require('../decrypt-key.js').DecryptKey; /** @ignore */ |
| var EncryptKey = require('../encrypt-key.js').EncryptKey; /** @ignore */ |
| var EncryptAlgorithmType = require('./encrypt-params.js').EncryptAlgorithmType; /** @ignore */ |
| var UseSubtleCrypto = require('../../use-subtle-crypto-node.js').UseSubtleCrypto; /** @ignore */ |
| var SyncPromise = require('../../util/sync-promise.js').SyncPromise; |
| |
| /** |
| * The AesAlgorithm class provides static methods to manipulate keys, encrypt |
| * and decrypt using the AES symmetric key cipher. |
| * @note This class is an experimental feature. The API may change. |
| * @constructor |
| */ |
| var AesAlgorithm = function AesAlgorithm() |
| { |
| }; |
| |
| exports.AesAlgorithm = AesAlgorithm; |
| |
| /** |
| * Generate a new random decrypt key for AES based on the given params. |
| * @param {AesKeyParams} params The key params with the key size (in bits). |
| * @return {DecryptKey} The new decrypt key. |
| */ |
| AesAlgorithm.generateKey = function(params) |
| { |
| // Convert the key bit size to bytes. |
| var key = Crypto.randomBytes(params.getKeySize() / 8); |
| |
| var decryptKey = new DecryptKey(new Blob(key, false)); |
| return decryptKey; |
| }; |
| |
| /** |
| * Derive a new encrypt key from the given decrypt key value. |
| * @param {Blob} keyBits The key value of the decrypt key. |
| * @return {EncryptKey} The new encrypt key. |
| */ |
| AesAlgorithm.deriveEncryptKey = function(keyBits) |
| { |
| return new EncryptKey(keyBits); |
| }; |
| |
| /** |
| * Decrypt the encryptedData using the keyBits according the encrypt params. |
| * @param {Blob} keyBits The key value. |
| * @param {Blob} encryptedData The data to decrypt. |
| * @param {EncryptParams} params This decrypts according to |
| * params.getAlgorithmType() and other params as needed such as |
| * params.getInitialVector(). |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which returns the decrypted Blob. |
| */ |
| AesAlgorithm.decryptPromise = function(keyBits, encryptedData, params, useSync) |
| { |
| if (UseSubtleCrypto() && !useSync && |
| // Crypto.subtle doesn't implement ECB. |
| params.getAlgorithmType() != EncryptAlgorithmType.AesEcb) { |
| if (params.getAlgorithmType() == EncryptAlgorithmType.AesCbc) { |
| return crypto.subtle.importKey |
| ("raw", keyBits.buf(), { name: "AES-CBC" }, false, |
| ["encrypt", "decrypt"]) |
| .then(function(key) { |
| return crypto.subtle.decrypt |
| ({ name: "AES-CBC", iv: params.getInitialVector().buf() }, |
| key, encryptedData.buf()); |
| }) |
| .then(function(result) { |
| return Promise.resolve(new Blob(new Uint8Array(result), false)); |
| }); |
| } |
| else |
| return Promise.reject(new Error("unsupported encryption mode")); |
| } |
| else { |
| if (params.getAlgorithmType() == EncryptAlgorithmType.AesEcb) { |
| try { |
| // ECB ignores the initial vector. |
| var cipher = Crypto.createDecipheriv("aes-128-ecb", keyBits.buf(), ""); |
| return SyncPromise.resolve(new Blob |
| (Buffer.concat([cipher.update(encryptedData.buf()), cipher.final()]), |
| false)); |
| } catch (err) { |
| return SyncPromise.reject(err); |
| } |
| } |
| else if (params.getAlgorithmType() == EncryptAlgorithmType.AesCbc) { |
| try { |
| var cipher = Crypto.createDecipheriv |
| ("aes-128-cbc", keyBits.buf(), params.getInitialVector().buf()); |
| return SyncPromise.resolve(new Blob |
| (Buffer.concat([cipher.update(encryptedData.buf()), cipher.final()]), |
| false)); |
| } catch (err) { |
| return SyncPromise.reject(err); |
| } |
| } |
| else |
| return SyncPromise.reject(new Error("unsupported encryption mode")); |
| } |
| }; |
| |
| /** |
| * Decrypt the encryptedData using the keyBits according the encrypt params. |
| * @param {Blob} keyBits The key value. |
| * @param {Blob} encryptedData The data to decrypt. |
| * @param {EncryptParams} params This decrypts according to |
| * params.getAlgorithmType() and other params as needed such as |
| * params.getInitialVector(). |
| * @return {Blob} The decrypted data. |
| * @throws Error If decryptPromise doesn't return a SyncPromise which is |
| * already fulfilled. |
| */ |
| AesAlgorithm.decrypt = function(keyBits, encryptedData, params) |
| { |
| return SyncPromise.getValue(this.decryptPromise |
| (keyBits, encryptedData, params, true)); |
| }; |
| |
| /** |
| * Encrypt the plainData using the keyBits according the encrypt params. |
| * @param {Blob} keyBits The key value. |
| * @param {Blob} plainData The data to encrypt. |
| * @param {EncryptParams} params This encrypts according to |
| * params.getAlgorithmType() and other params as needed such as |
| * params.getInitialVector(). |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which returns the encrypted Blob. |
| */ |
| AesAlgorithm.encryptPromise = function(keyBits, plainData, params, useSync) |
| { |
| if (params.getAlgorithmType() == EncryptAlgorithmType.AesCbc) { |
| if (params.getInitialVector().size() != AesAlgorithm.BLOCK_SIZE) |
| return SyncPromise.reject(new Error("incorrect initial vector size")); |
| } |
| |
| if (UseSubtleCrypto() && !useSync && |
| // Crypto.subtle doesn't implement ECB. |
| params.getAlgorithmType() != EncryptAlgorithmType.AesEcb) { |
| if (params.getAlgorithmType() == EncryptAlgorithmType.AesCbc) { |
| return crypto.subtle.importKey |
| ("raw", keyBits.buf(), { name: "AES-CBC" }, false, |
| ["encrypt", "decrypt"]) |
| .then(function(key) { |
| return crypto.subtle.encrypt |
| ({ name: "AES-CBC", iv: params.getInitialVector().buf() }, |
| key, plainData.buf()); |
| }) |
| .then(function(result) { |
| return Promise.resolve(new Blob(new Uint8Array(result), false)); |
| }); |
| } |
| else |
| return Promise.reject(new Error("unsupported encryption mode")); |
| } |
| else { |
| if (params.getAlgorithmType() == EncryptAlgorithmType.AesEcb) { |
| // ECB ignores the initial vector. |
| var cipher = Crypto.createCipheriv("aes-128-ecb", keyBits.buf(), ""); |
| return SyncPromise.resolve(new Blob |
| (Buffer.concat([cipher.update(plainData.buf()), cipher.final()]), |
| false)); |
| } |
| else if (params.getAlgorithmType() == EncryptAlgorithmType.AesCbc) { |
| var cipher = Crypto.createCipheriv |
| ("aes-128-cbc", keyBits.buf(), params.getInitialVector().buf()); |
| return SyncPromise.resolve(new Blob |
| (Buffer.concat([cipher.update(plainData.buf()), cipher.final()]), |
| false)); |
| } |
| else |
| return SyncPromise.reject(new Error("unsupported encryption mode")); |
| } |
| }; |
| |
| /** |
| * Encrypt the plainData using the keyBits according the encrypt params. |
| * @param {Blob} keyBits The key value. |
| * @param {Blob} plainData The data to encrypt. |
| * @param {EncryptParams} params This encrypts according to |
| * params.getAlgorithmType() and other params as needed such as |
| * params.getInitialVector(). |
| * @return {Blob} The encrypted data. |
| * @throws Error If encryptPromise doesn't return a SyncPromise which is |
| * already fulfilled. |
| */ |
| AesAlgorithm.encrypt = function(keyBits, plainData, params) |
| { |
| return SyncPromise.getValue(this.encryptPromise |
| (keyBits, plainData, params, true)); |
| }; |
| |
| AesAlgorithm.BLOCK_SIZE = 16; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From ndn-group-encrypt src/encrypt-params https://github.com/named-data/ndn-group-encrypt |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Crypto = require('../../crypto.js'); /** @ignore */ |
| var Blob = require('../../util/blob.js').Blob; |
| |
| var EncryptAlgorithmType = function EncryptAlgorithmType() |
| { |
| } |
| |
| exports.EncryptAlgorithmType = EncryptAlgorithmType; |
| |
| // These correspond to the TLV codes. |
| EncryptAlgorithmType.AesEcb = 0; |
| EncryptAlgorithmType.AesCbc = 1; |
| EncryptAlgorithmType.RsaPkcs = 2; |
| EncryptAlgorithmType.RsaOaep = 3; |
| |
| /** |
| * An EncryptParams holds an algorithm type and other parameters used to |
| * encrypt and decrypt. Create an EncryptParams with the given parameters. |
| * @param {number} algorithmType The algorithm type from EncryptAlgorithmType, |
| * or null if not specified. |
| * @param {number} initialVectorLength (optional) The initial vector length, or |
| * 0 if the initial vector is not specified. If ommitted, the initial vector is |
| * not specified. |
| * @note This class is an experimental feature. The API may change. |
| * @constructor |
| */ |
| var EncryptParams = function EncryptParams(algorithmType, initialVectorLength) |
| { |
| this.algorithmType_ = algorithmType; |
| |
| if (initialVectorLength != null && initialVectorLength > 0) { |
| var initialVector = Crypto.randomBytes(initialVectorLength); |
| this.initialVector_ = new Blob(initialVector, false); |
| } |
| else |
| this.initialVector_ = new Blob(); |
| }; |
| |
| exports.EncryptParams = EncryptParams; |
| |
| /** |
| * Get the algorithmType. |
| * @return {number} The algorithm type from EncryptAlgorithmType, or null if not |
| * specified. |
| */ |
| EncryptParams.prototype.getAlgorithmType = function() |
| { |
| return this.algorithmType_; |
| }; |
| |
| /** |
| * Get the initial vector. |
| * @return {Blob} The initial vector. If not specified, isNull() is true. |
| */ |
| EncryptParams.prototype.getInitialVector = function() |
| { |
| return this.initialVector_; |
| }; |
| |
| /** |
| * Set the algorithm type. |
| * @param {number} algorithmType The algorithm type from EncryptAlgorithmType. |
| * If not specified, set to null. |
| * @return {EncryptParams} This EncryptParams so that you can chain calls to |
| * update values. |
| */ |
| EncryptParams.prototype.setAlgorithmType = function(algorithmType) |
| { |
| this.algorithmType_ = algorithmType; |
| return this; |
| }; |
| |
| /** |
| * Set the initial vector. |
| * @param {Blob} initialVector The initial vector. If not specified, set to the |
| * default Blob() where isNull() is true. |
| * @return {EncryptParams} This EncryptParams so that you can chain calls to |
| * update values. |
| */ |
| EncryptParams.prototype.setInitialVector = function(initialVector) |
| { |
| this.initialVector_ = |
| typeof initialVector === 'object' && initialVector instanceof Blob ? |
| initialVector : new Blob(initialVector); |
| return this; |
| }; |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From ndn-group-encrypt src/encryptor https://github.com/named-data/ndn-group-encrypt |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Crypto = require('../../crypto.js'); /** @ignore */ |
| var Name = require('../../name.js').Name; /** @ignore */ |
| var KeyLocator = require('../../key-locator.js').KeyLocator; /** @ignore */ |
| var KeyLocatorType = require('../../key-locator.js').KeyLocatorType; /** @ignore */ |
| var TlvWireFormat = require('../../encoding/tlv-wire-format.js').TlvWireFormat; /** @ignore */ |
| var Blob = require('../../util/blob.js').Blob; /** @ignore */ |
| var AesAlgorithm = require('./aes-algorithm.js').AesAlgorithm; /** @ignore */ |
| var RsaAlgorithm = require('./rsa-algorithm.js').RsaAlgorithm; /** @ignore */ |
| var EncryptParams = require('./encrypt-params.js').EncryptParams; /** @ignore */ |
| var EncryptAlgorithmType = require('./encrypt-params.js').EncryptAlgorithmType; /** @ignore */ |
| var EncryptedContent = require('../encrypted-content.js').EncryptedContent; /** @ignore */ |
| var SyncPromise = require('../../util/sync-promise.js').SyncPromise; |
| |
| /** |
| * Encryptor has static constants and utility methods for encryption, such as |
| * encryptData. |
| * @constructor |
| */ |
| var Encryptor = function Encryptor(value) |
| { |
| }; |
| |
| exports.Encryptor = Encryptor; |
| |
| Encryptor.NAME_COMPONENT_FOR = new Name.Component("FOR"); |
| Encryptor.NAME_COMPONENT_READ = new Name.Component("READ"); |
| Encryptor.NAME_COMPONENT_SAMPLE = new Name.Component("SAMPLE"); |
| Encryptor.NAME_COMPONENT_ACCESS = new Name.Component("ACCESS"); |
| Encryptor.NAME_COMPONENT_E_KEY = new Name.Component("E-KEY"); |
| Encryptor.NAME_COMPONENT_D_KEY = new Name.Component("D-KEY"); |
| Encryptor.NAME_COMPONENT_C_KEY = new Name.Component("C-KEY"); |
| |
| /** |
| * Prepare an encrypted data packet by encrypting the payload using the key |
| * according to the params. In addition, this prepares the encoded |
| * EncryptedContent with the encryption result using keyName and params. The |
| * encoding is set as the content of the data packet. If params defines an |
| * asymmetric encryption algorithm and the payload is larger than the maximum |
| * plaintext size, this encrypts the payload with a symmetric key that is |
| * asymmetrically encrypted and provided as a nonce in the content of the data |
| * packet. The packet's /<dataName>/ is updated to be <dataName>/FOR/<keyName>. |
| * @param {Data} data The data packet which is updated. |
| * @param {Blob} payload The payload to encrypt. |
| * @param {Name} keyName The key name for the EncryptedContent. |
| * @param {Blob} key The encryption key value. |
| * @param {EncryptParams} params The parameters for encryption. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which fulfills when the data packet |
| * is updated. |
| */ |
| Encryptor.encryptDataPromise = function |
| (data, payload, keyName, key, params, useSync) |
| { |
| data.getName().append(Encryptor.NAME_COMPONENT_FOR).append(keyName); |
| |
| var algorithmType = params.getAlgorithmType(); |
| |
| if (algorithmType == EncryptAlgorithmType.AesCbc || |
| algorithmType == EncryptAlgorithmType.AesEcb) { |
| return Encryptor.encryptSymmetricPromise_ |
| (payload, key, keyName, params, useSync) |
| .then(function(content) { |
| data.setContent(content.wireEncode(TlvWireFormat.get())); |
| return SyncPromise.resolve(); |
| }); |
| } |
| else if (algorithmType == EncryptAlgorithmType.RsaPkcs || |
| algorithmType == EncryptAlgorithmType.RsaOaep) { |
| // Node.js doesn't have a direct way to get the maximum plain text size, so |
| // try to encrypt the payload first and catch the error if it is too big. |
| return Encryptor.encryptAsymmetricPromise_ |
| (payload, key, keyName, params, useSync) |
| .then(function(content) { |
| data.setContent(content.wireEncode(TlvWireFormat.get())); |
| return SyncPromise.resolve(); |
| }, function(err) { |
| if (err.message.indexOf("data too large for key size") < 0) |
| // Not the expected error. |
| throw err; |
| |
| // The payload is larger than the maximum plaintext size. |
| // 128-bit nonce. |
| var nonceKeyBuffer = Crypto.randomBytes(16); |
| var nonceKey = new Blob(nonceKeyBuffer, false); |
| |
| var nonceKeyName = new Name(keyName); |
| nonceKeyName.append("nonce"); |
| |
| var symmetricParams = new EncryptParams |
| (EncryptAlgorithmType.AesCbc, AesAlgorithm.BLOCK_SIZE); |
| |
| var nonceContent; |
| return Encryptor.encryptSymmetricPromise_ |
| (payload, nonceKey, nonceKeyName, symmetricParams, useSync) |
| .then(function(localNonceContent) { |
| nonceContent = localNonceContent; |
| return Encryptor.encryptAsymmetricPromise_ |
| (nonceKey, key, keyName, params, useSync); |
| }) |
| .then(function(payloadContent) { |
| var nonceContentEncoding = nonceContent.wireEncode(); |
| var payloadContentEncoding = payloadContent.wireEncode(); |
| var content = new Buffer |
| (nonceContentEncoding.size() + payloadContentEncoding.size()); |
| payloadContentEncoding.buf().copy(content, 0); |
| nonceContentEncoding.buf().copy(content, payloadContentEncoding.size()); |
| |
| data.setContent(new Blob(content, false)); |
| return SyncPromise.resolve(); |
| }); |
| }); |
| } |
| else |
| return SyncPromise.reject(new Error("Unsupported encryption method")); |
| }; |
| |
| /** |
| * Prepare an encrypted data packet by encrypting the payload using the key |
| * according to the params. In addition, this prepares the encoded |
| * EncryptedContent with the encryption result using keyName and params. The |
| * encoding is set as the content of the data packet. If params defines an |
| * asymmetric encryption algorithm and the payload is larger than the maximum |
| * plaintext size, this encrypts the payload with a symmetric key that is |
| * asymmetrically encrypted and provided as a nonce in the content of the data |
| * packet. |
| * @param {Data} data The data packet which is updated. |
| * @param {Blob} payload The payload to encrypt. |
| * @param {Name} keyName The key name for the EncryptedContent. |
| * @param {Blob} key The encryption key value. |
| * @param {EncryptParams} params The parameters for encryption. |
| * @throws Error If encryptPromise doesn't return a SyncPromise which is |
| * already fulfilled. |
| */ |
| Encryptor.encryptData = function(data, payload, keyName, key, params) |
| { |
| return SyncPromise.getValue(Encryptor.encryptDataPromise |
| (data, payload, keyName, key, params, true)); |
| }; |
| |
| /** |
| * Encrypt the payload using the symmetric key according to params, and return |
| * an EncryptedContent. |
| * @param {Blob} payload The data to encrypt. |
| * @param {Blob} key The key value. |
| * @param {Name} keyName The key name for the EncryptedContent key locator. |
| * @param {EncryptParams} params The parameters for encryption. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which returns a new EncryptedContent. |
| */ |
| Encryptor.encryptSymmetricPromise_ = function |
| (payload, key, keyName, params, useSync) |
| { |
| var algorithmType = params.getAlgorithmType(); |
| var initialVector = params.getInitialVector(); |
| var keyLocator = new KeyLocator(); |
| keyLocator.setType(KeyLocatorType.KEYNAME); |
| keyLocator.setKeyName(keyName); |
| |
| if (algorithmType == EncryptAlgorithmType.AesCbc || |
| algorithmType == EncryptAlgorithmType.AesEcb) { |
| if (algorithmType == EncryptAlgorithmType.AesCbc) { |
| if (initialVector.size() != AesAlgorithm.BLOCK_SIZE) |
| return SyncPromise.reject(new Error("incorrect initial vector size")); |
| } |
| |
| return AesAlgorithm.encryptPromise(key, payload, params, useSync) |
| .then(function(encryptedPayload) { |
| var result = new EncryptedContent(); |
| result.setAlgorithmType(algorithmType); |
| result.setKeyLocator(keyLocator); |
| result.setPayload(encryptedPayload); |
| result.setInitialVector(initialVector); |
| return SyncPromise.resolve(result); |
| }); |
| } |
| else |
| return SyncPromise.reject(new Error("Unsupported encryption method")); |
| }; |
| |
| /** |
| * Encrypt the payload using the asymmetric key according to params, and |
| * return an EncryptedContent. |
| * @param {Blob} payload The data to encrypt. The size should be within range of |
| * the key. |
| * @param {Blob} key The key value. |
| * @param {Name} keyName The key name for the EncryptedContent key locator. |
| * @param {EncryptParams} params The parameters for encryption. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which returns a new EncryptedContent. |
| */ |
| Encryptor.encryptAsymmetricPromise_ = function |
| (payload, key, keyName, params, useSync) |
| { |
| var algorithmType = params.getAlgorithmType(); |
| var keyLocator = new KeyLocator(); |
| keyLocator.setType(KeyLocatorType.KEYNAME); |
| keyLocator.setKeyName(keyName); |
| |
| if (algorithmType == EncryptAlgorithmType.RsaPkcs || |
| algorithmType == EncryptAlgorithmType.RsaOaep) { |
| return RsaAlgorithm.encryptPromise(key, payload, params, useSync) |
| .then(function(encryptedPayload) { |
| var result = new EncryptedContent(); |
| result.setAlgorithmType(algorithmType); |
| result.setKeyLocator(keyLocator); |
| result.setPayload(encryptedPayload); |
| return SyncPromise.resolve(result); |
| }); |
| } |
| else |
| return SyncPromise.reject(new Error("Unsupported encryption method")); |
| }; |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From ndn-group-encrypt src/algo/rsa https://github.com/named-data/ndn-group-encrypt |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| // (This is ported from ndn::gep::algo::Rsa, and named RsaAlgorithm because |
| // "Rsa" is very short and not all the Common Client Libraries have namespaces.) |
| |
| /** @ignore */ |
| var constants = require('constants'); /** @ignore */ |
| var Crypto = require('../../crypto.js'); /** @ignore */ |
| var Blob = require('../../util/blob.js').Blob; /** @ignore */ |
| var DecryptKey = require('../decrypt-key.js').DecryptKey; /** @ignore */ |
| var EncryptKey = require('../encrypt-key.js').EncryptKey; /** @ignore */ |
| var EncryptAlgorithmType = require('./encrypt-params.js').EncryptAlgorithmType; /** @ignore */ |
| var DerNode = require('../../encoding/der/der-node.js').DerNode; /** @ignore */ |
| var OID = require('../../encoding/oid.js').OID; /** @ignore */ |
| var PrivateKeyStorage = require('../../security/identity/private-key-storage.js').PrivateKeyStorage; /** @ignore */ |
| var UseSubtleCrypto = require('../../use-subtle-crypto-node.js').UseSubtleCrypto; /** @ignore */ |
| var SyncPromise = require('../../util/sync-promise.js').SyncPromise; /** @ignore */ |
| var rsaKeygen = null; |
| try { |
| // This should be installed with: sudo npm install rsa-keygen |
| rsaKeygen = require('rsa-keygen'); |
| } |
| catch (e) {} |
| |
| /** |
| * The RsaAlgorithm class provides static methods to manipulate keys, encrypt |
| * and decrypt using RSA. |
| * @note This class is an experimental feature. The API may change. |
| * @constructor |
| */ |
| var RsaAlgorithm = function RsaAlgorithm() |
| { |
| }; |
| |
| exports.RsaAlgorithm = RsaAlgorithm; |
| |
| /** |
| * Generate a new random decrypt key for RSA based on the given params. |
| * @param {RsaKeyParams} params The key params with the key size (in bits). |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which returns the new DecryptKey |
| * (containing a PKCS8-encoded private key). |
| */ |
| RsaAlgorithm.generateKeyPromise = function(params, useSync) |
| { |
| if (UseSubtleCrypto() && !useSync) { |
| return crypto.subtle.generateKey |
| ({ name: "RSASSA-PKCS1-v1_5", modulusLength: params.getKeySize(), |
| publicExponent: new Uint8Array([0x01, 0x00, 0x01]), |
| hash: {name: "SHA-256"} }, |
| true, ["sign", "verify"]) |
| .then(function(key) { |
| // Export the private key to DER. |
| return crypto.subtle.exportKey("pkcs8", key.privateKey); |
| }) |
| .then(function(pkcs8Der) { |
| return Promise.resolve(new DecryptKey |
| (new Blob(new Uint8Array(pkcs8Der), false))); |
| }); |
| } |
| else { |
| if (!rsaKeygen) |
| return SyncPromise.reject(new Error |
| ("Need to install rsa-keygen: sudo npm install rsa-keygen")); |
| |
| try { |
| var keyPair = rsaKeygen.generate(params.getKeySize()); |
| // Get the PKCS1 private key DER from the PEM string and encode as PKCS8. |
| var privateKeyBase64 = keyPair.private_key.toString().replace |
| ("-----BEGIN RSA PRIVATE KEY-----", "").replace |
| ("-----END RSA PRIVATE KEY-----", ""); |
| var pkcs1PrivateKeyDer = new Buffer(privateKeyBase64, 'base64'); |
| var privateKey = PrivateKeyStorage.encodePkcs8PrivateKey |
| (pkcs1PrivateKeyDer, new OID(PrivateKeyStorage.RSA_ENCRYPTION_OID), |
| new DerNode.DerNull()).buf(); |
| |
| return SyncPromise.resolve(new DecryptKey(privateKey)); |
| } catch (err) { |
| return SyncPromise.reject(err); |
| } |
| } |
| }; |
| |
| /** |
| * Generate a new random decrypt key for RSA based on the given params. |
| * @param {RsaKeyParams} params The key params with the key size (in bits). |
| * @return {DecryptKey} The new decrypt key (containing a PKCS8-encoded private |
| * key). |
| * @throws Error If generateKeyPromise doesn't return a SyncPromise which is |
| * already fulfilled. |
| */ |
| RsaAlgorithm.generateKey = function(params) |
| { |
| return SyncPromise.getValue(this.generateKeyPromise(params, true)); |
| }; |
| |
| /** |
| * Derive a new encrypt key from the given decrypt key value. |
| * @param {Blob} keyBits The key value of the decrypt key (PKCS8-encoded private |
| * key). |
| * @return {EncryptKey} The new encrypt key (DER-encoded public key). |
| */ |
| RsaAlgorithm.deriveEncryptKey = function(keyBits) |
| { |
| var rsaPrivateKeyDer = RsaAlgorithm.getRsaPrivateKeyDer(keyBits); |
| |
| // Decode the PKCS #1 RSAPrivateKey. |
| var parsedNode = DerNode.parse(rsaPrivateKeyDer.buf(), 0); |
| var rsaPrivateKeyChildren = parsedNode.getChildren(); |
| var modulus = rsaPrivateKeyChildren[1]; |
| var publicExponent = rsaPrivateKeyChildren[2]; |
| |
| // Encode the PKCS #1 RSAPublicKey. |
| var rsaPublicKey = new DerNode.DerSequence(); |
| rsaPublicKey.addChild(modulus); |
| rsaPublicKey.addChild(publicExponent); |
| var rsaPublicKeyDer = rsaPublicKey.encode(); |
| |
| // Encode the SubjectPublicKeyInfo. |
| var algorithmIdentifier = new DerNode.DerSequence(); |
| algorithmIdentifier.addChild(new DerNode.DerOid(new OID |
| (PrivateKeyStorage.RSA_ENCRYPTION_OID))); |
| algorithmIdentifier.addChild(new DerNode.DerNull()); |
| var publicKey = new DerNode.DerSequence(); |
| publicKey.addChild(algorithmIdentifier); |
| publicKey.addChild(new DerNode.DerBitString(rsaPublicKeyDer.buf(), 0)); |
| |
| return new EncryptKey(publicKey.encode()); |
| }; |
| |
| /** |
| * Decrypt the encryptedData using the keyBits according the encrypt params. |
| * @param {Blob} keyBits The key value (PKCS8-encoded private key). |
| * @param {Blob} encryptedData The data to decrypt. |
| * @param {EncryptParams} params This decrypts according to |
| * params.getAlgorithmType(). |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which returns the decrypted Blob. |
| */ |
| RsaAlgorithm.decryptPromise = function(keyBits, encryptedData, params, useSync) |
| { |
| if (UseSubtleCrypto() && !useSync && |
| // Crypto.subtle doesn't implement PKCS1 padding. |
| params.getAlgorithmType() != EncryptAlgorithmType.RsaPkcs) { |
| if (params.getAlgorithmType() == EncryptAlgorithmType.RsaOaep) { |
| return crypto.subtle.importKey |
| ("pkcs8", keyBits.buf(), { name: "RSA-OAEP", hash: {name: "SHA-1"} }, |
| false, ["decrypt"]) |
| .then(function(privateKey) { |
| return crypto.subtle.decrypt |
| ({ name: "RSA-OAEP" }, privateKey, encryptedData.buf()); |
| }) |
| .then(function(result) { |
| return Promise.resolve(new Blob(new Uint8Array(result), false)); |
| }); |
| } |
| else |
| return Promise.reject(new Error("unsupported padding scheme")); |
| } |
| else { |
| // keyBits is PKCS #8 but we need the inner RSAPrivateKey. |
| var rsaPrivateKeyDer = RsaAlgorithm.getRsaPrivateKeyDer(keyBits); |
| |
| // Encode the key DER as a PEM private key as needed by Crypto. |
| var keyBase64 = rsaPrivateKeyDer.buf().toString('base64'); |
| var keyPem = "-----BEGIN RSA PRIVATE KEY-----\n"; |
| for (var i = 0; i < keyBase64.length; i += 64) |
| keyPem += (keyBase64.substr(i, 64) + "\n"); |
| keyPem += "-----END RSA PRIVATE KEY-----"; |
| |
| var padding; |
| if (params.getAlgorithmType() == EncryptAlgorithmType.RsaPkcs) |
| padding = constants.RSA_PKCS1_PADDING; |
| else if (params.getAlgorithmType() == EncryptAlgorithmType.RsaOaep) |
| padding = constants.RSA_PKCS1_OAEP_PADDING; |
| else |
| return SyncPromise.reject(new Error("unsupported padding scheme")); |
| |
| try { |
| // In Node.js, privateDecrypt requires version v0.12. |
| return SyncPromise.resolve(new Blob |
| (Crypto.privateDecrypt({ key: keyPem, padding: padding }, encryptedData.buf()), |
| false)); |
| } catch (err) { |
| return SyncPromise.reject(err); |
| } |
| } |
| }; |
| |
| /** |
| * Decrypt the encryptedData using the keyBits according the encrypt params. |
| * @param {Blob} keyBits The key value (PKCS8-encoded private key). |
| * @param {Blob} encryptedData The data to decrypt. |
| * @param {EncryptParams} params This decrypts according to |
| * params.getAlgorithmType(). |
| * @return {Blob} The decrypted data. |
| * @throws Error If decryptPromise doesn't return a SyncPromise which is |
| * already fulfilled. |
| */ |
| RsaAlgorithm.decrypt = function(keyBits, encryptedData, params) |
| { |
| return SyncPromise.getValue(this.decryptPromise |
| (keyBits, encryptedData, params, true)); |
| }; |
| |
| /** |
| * Encrypt the plainData using the keyBits according the encrypt params. |
| * @param {Blob} keyBits The key value (DER-encoded public key). |
| * @param {Blob} plainData The data to encrypt. |
| * @param {EncryptParams} params This encrypts according to |
| * params.getAlgorithmType(). |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise which returns the encrypted Blob. |
| */ |
| RsaAlgorithm.encryptPromise = function(keyBits, plainData, params, useSync) |
| { |
| if (UseSubtleCrypto() && !useSync && |
| // Crypto.subtle doesn't implement PKCS1 padding. |
| params.getAlgorithmType() != EncryptAlgorithmType.RsaPkcs) { |
| if (params.getAlgorithmType() == EncryptAlgorithmType.RsaOaep) { |
| return crypto.subtle.importKey |
| ("spki", keyBits.buf(), { name: "RSA-OAEP", hash: {name: "SHA-1"} }, |
| false, ["encrypt"]) |
| .then(function(publicKey) { |
| return crypto.subtle.encrypt |
| ({ name: "RSA-OAEP" }, publicKey, plainData.buf()); |
| }) |
| .then(function(result) { |
| return Promise.resolve(new Blob(new Uint8Array(result), false)); |
| }); |
| } |
| else |
| return Promise.reject(new Error("unsupported padding scheme")); |
| } |
| else { |
| // Encode the key DER as a PEM public key as needed by Crypto. |
| var keyBase64 = keyBits.buf().toString('base64'); |
| var keyPem = "-----BEGIN PUBLIC KEY-----\n"; |
| for (var i = 0; i < keyBase64.length; i += 64) |
| keyPem += (keyBase64.substr(i, 64) + "\n"); |
| keyPem += "-----END PUBLIC KEY-----"; |
| |
| var padding; |
| if (params.getAlgorithmType() == EncryptAlgorithmType.RsaPkcs) |
| padding = constants.RSA_PKCS1_PADDING; |
| else if (params.getAlgorithmType() == EncryptAlgorithmType.RsaOaep) |
| padding = constants.RSA_PKCS1_OAEP_PADDING; |
| else |
| return SyncPromise.reject(new Error("unsupported padding scheme")); |
| |
| try { |
| // In Node.js, publicEncrypt requires version v0.12. |
| return SyncPromise.resolve(new Blob |
| (Crypto.publicEncrypt({ key: keyPem, padding: padding }, plainData.buf()), |
| false)); |
| } catch (err) { |
| return SyncPromise.reject(err); |
| } |
| } |
| }; |
| |
| /** |
| * Encrypt the plainData using the keyBits according the encrypt params. |
| * @param {Blob} keyBits The key value (DER-encoded public key). |
| * @param {Blob} plainData The data to encrypt. |
| * @param {EncryptParams} params This encrypts according to |
| * params.getAlgorithmType(). |
| * @return {Blob} The encrypted data. |
| * @throws Error If encryptPromise doesn't return a SyncPromise which is |
| * already fulfilled. |
| */ |
| RsaAlgorithm.encrypt = function(keyBits, plainData, params) |
| { |
| return SyncPromise.getValue(this.encryptPromise |
| (keyBits, plainData, params, true)); |
| }; |
| |
| /** |
| * Decode the PKCS #8 private key, check that the algorithm is RSA, and return |
| * the inner RSAPrivateKey DER. |
| * @param {Blob} The DER-encoded PKCS #8 private key. |
| * @param {Blob} The DER-encoded RSAPrivateKey. |
| */ |
| RsaAlgorithm.getRsaPrivateKeyDer = function(pkcs8PrivateKeyDer) |
| { |
| var parsedNode = DerNode.parse(pkcs8PrivateKeyDer.buf(), 0); |
| var pkcs8Children = parsedNode.getChildren(); |
| var algorithmIdChildren = DerNode.getSequence(pkcs8Children, 1).getChildren(); |
| var oidString = algorithmIdChildren[0].toVal(); |
| |
| if (oidString != PrivateKeyStorage.RSA_ENCRYPTION_OID) |
| throw new Error("The PKCS #8 private key is not RSA_ENCRYPTION"); |
| |
| return pkcs8Children[2].getPayload(); |
| }; |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From ndn-group-encrypt src/consumer-db https://github.com/named-data/ndn-group-encrypt |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var SyncPromise = require('../util/sync-promise.js').SyncPromise; |
| |
| /** |
| * ConsumerDb is a base class the storage of decryption keys for the consumer. A |
| * subclass must implement the methods. For example, see Sqlite3ConsumerDb (for |
| * Nodejs) or IndexedDbConsumerDb (for the browser). |
| * @note This class is an experimental feature. The API may change. |
| * @constructor |
| */ |
| var ConsumerDb = function ConsumerDb() |
| { |
| }; |
| |
| exports.ConsumerDb = ConsumerDb; |
| |
| /** |
| * Create a new ConsumerDb.Error to report an error using ConsumerDb |
| * methods, wrapping the given error object. |
| * Call with: throw new ConsumerDb.Error(new Error("message")). |
| * @constructor |
| * @param {Error} error The exception created with new Error. |
| */ |
| ConsumerDb.Error = function ConsumerDbError(error) |
| { |
| if (error) { |
| error.__proto__ = ConsumerDb.Error.prototype; |
| return error; |
| } |
| } |
| |
| ConsumerDb.Error.prototype = new Error(); |
| ConsumerDb.Error.prototype.name = "ConsumerDbError"; |
| |
| /** |
| * Get the key with keyName from the database. |
| * @param {Name} keyName The key name. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns a Blob with the encoded |
| * key (or an isNull Blob if cannot find the key with keyName), or that is |
| * rejected with ConsumerDb.Error for a database error. |
| */ |
| ConsumerDb.prototype.getKeyPromise = function(keyName, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("ConsumerDb.getKeyPromise is not implemented")); |
| }; |
| |
| /** |
| * Add the key with keyName and keyBlob to the database. |
| * @param {Name} keyName The key name. |
| * @param {Blob} keyBlob The encoded key. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that fulfills when the key is added, |
| * or that is rejected with ConsumerDb.Error if a key with the same keyName |
| * already exists, or other database error. |
| */ |
| ConsumerDb.prototype.addKeyPromise = function(keyName, keyBlob, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("ConsumerDb.addKeyPromise is not implemented")); |
| }; |
| |
| /** |
| * Delete the key with keyName from the database. If there is no key with |
| * keyName, do nothing. |
| * @param {Name} keyName The key name. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that fulfills when the key is deleted |
| * (or there is no such key), or that is rejected with ConsumerDb.Error for a |
| * database error. |
| */ |
| ConsumerDb.prototype.deleteKeyPromise = function(keyName, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("ConsumerDb.addKeyPromise is not implemented")); |
| }; |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From ndn-group-encrypt src/consumer https://github.com/named-data/ndn-group-encrypt |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Blob = require('../util/blob.js').Blob; /** @ignore */ |
| var Name = require('../name.js').Name; /** @ignore */ |
| var Interest = require('../interest.js').Interest; /** @ignore */ |
| var NetworkNack = require('../network-nack.js').NetworkNack; /** @ignore */ |
| var Link = require('../link.js').Link; /** @ignore */ |
| var EncryptedContent = require('./encrypted-content.js').EncryptedContent; /** @ignore */ |
| var EncryptError = require('./encrypt-error.js').EncryptError; /** @ignore */ |
| var EncryptParams = require('./algo/encrypt-params.js').EncryptParams; /** @ignore */ |
| var EncryptAlgorithmType = require('./algo/encrypt-params.js').EncryptAlgorithmType; /** @ignore */ |
| var RsaAlgorithm = require('./algo/rsa-algorithm.js').RsaAlgorithm; /** @ignore */ |
| var AesAlgorithm = require('./algo/aes-algorithm.js').AesAlgorithm; /** @ignore */ |
| var Encryptor = require('./algo/encryptor.js').Encryptor; /** @ignore */ |
| var SyncPromise = require('../util/sync-promise.js').SyncPromise; /** @ignore */ |
| var NdnCommon = require('../util/ndn-common.js').NdnCommon; |
| |
| /** |
| * A Consumer manages fetched group keys used to decrypt a data packet in the |
| * group-based encryption protocol. |
| * Create a Consumer to use the given ConsumerDb, Face and other values. |
| * @param {Face} face The face used for data packet and key fetching. |
| * @param {KeyChain} keyChain The keyChain used to verify data packets. |
| * @param {Name} groupName The reading group name that the consumer belongs to. |
| * This makes a copy of the Name. |
| * @param {Name} consumerName The identity of the consumer. This makes a copy of |
| * the Name. |
| * @param {ConsumerDb} database The ConsumerDb database for storing decryption |
| * keys. |
| * @param {Link} cKeyLink (optional) The Link object to use in Interests for |
| * C-KEY retrieval. This makes a copy of the Link object. If the Link object's |
| * getDelegations().size() is zero, don't use it. If omitted, don't use a Link |
| * object. |
| * @param {Link} dKeyLink (optional) The Link object to use in Interests for |
| * D-KEY retrieval. This makes a copy of the Link object. If the Link object's |
| * getDelegations().size() is zero, don't use it. If omitted, don't use a Link |
| * object. |
| * @note This class is an experimental feature. The API may change. |
| * @constructor |
| */ |
| var Consumer = function Consumer |
| (face, keyChain, groupName, consumerName, database, cKeyLink, dKeyLink) |
| { |
| this.database_ = database; |
| this.keyChain_ = keyChain; |
| this.face_ = face; |
| this.groupName_ = new Name(groupName); |
| this.consumerName_ = new Name(consumerName); |
| this.cKeyLink_ = |
| (cKeyLink == undefined ? Consumer.NO_LINK : new Link(cKeyLink)); |
| this.dKeyLink_ = |
| (dKeyLink == undefined ? Consumer.NO_LINK : new Link(dKeyLink)); |
| |
| // The map key is the C-KEY name URI string. The value is the encoded key Blob. |
| // (Use a string because we can't use the Name object as the key in JavaScript.) |
| this.cKeyMap_ = {}; |
| // The map key is the D-KEY name URI string. The value is the encoded key Blob. |
| this.dKeyMap_ = {}; |
| }; |
| |
| exports.Consumer = Consumer; |
| |
| /** |
| * Express an Interest to fetch the content packet with contentName, and |
| * decrypt it, fetching keys as needed. |
| * @param {Name} contentName The name of the content packet. |
| * @param {function} onConsumeComplete When the content packet is fetched and |
| * decrypted, this calls onConsumeComplete(contentData, result) where |
| * contentData is the fetched Data packet and result is the decrypted plain |
| * text Blob. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onError This calls onError(errorCode, message) for an error, |
| * where errorCode is an error code from EncryptError.ErrorCode. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param link {Link} (optional) The Link object to use in Interests for data |
| * retrieval. This makes a copy of the Link object. If the Link object's |
| * getDelegations().size() is zero, don't use it. If omitted, don't use a Link |
| * object. |
| */ |
| Consumer.prototype.consume = function |
| (contentName, onConsumeComplete, onError, link) |
| { |
| if (link == undefined) |
| link = Consumer.NO_LINK; |
| |
| var interest = new Interest(contentName); |
| var thisConsumer = this; |
| // Copy the Link object since the passed link may become invalid. |
| this.sendInterest_ |
| (interest, 1, new Link(link), |
| function(validData) { |
| // Decrypt the content. |
| thisConsumer.decryptContent_(validData, function(plainText) { |
| try { |
| onConsumeComplete(validData, plainText); |
| } catch (ex) { |
| console.log("Error in onConsumeComplete: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| }, onError); |
| }, |
| onError); |
| }; |
| |
| /** |
| * Set the group name. |
| * @param {Name} groupName The reading group name that the consumer belongs to. |
| * This makes a copy of the Name. |
| */ |
| Consumer.prototype.setGroup = function(groupName) |
| { |
| this.groupName_ = new Name(groupName); |
| }; |
| |
| /** |
| * Add a new decryption key with keyName and keyBlob to the database. |
| * @param {Name} keyName The key name. |
| * @param {Blob} keyBlob The encoded key. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that fulfills when the key is added, |
| * or that is rejected with Error if the consumer name is not a prefix of the |
| * key name, or ConsumerDb.Error if a key with the same keyName already exists, |
| * or other database error. |
| */ |
| Consumer.prototype.addDecryptionKeyPromise = function(keyName, keyBlob, useSync) |
| { |
| if (!(this.consumerName_.match(keyName))) |
| return SyncPromise.reject(new Error |
| ("addDecryptionKey: The consumer name must be a prefix of the key name")); |
| |
| return this.database_.addKeyPromise(keyName, keyBlob, useSync); |
| }; |
| |
| /** |
| * Add a new decryption key with keyName and keyBlob to the database. |
| * @param {Name} keyName The key name. |
| * @param {Blob} keyBlob The encoded key. |
| * @param {function} onComplete (optional) This calls onComplete() when the key |
| * is added. (Some database libraries only use a callback, so onComplete is |
| * required to use these.) |
| * @param {function} onError (optional) If defined, then onComplete must be |
| * defined and if there is an exception, then this calls onError(exception) |
| * where exception is Error if the consumer name is not a prefix of the key |
| * name, or ConsumerDb.Error if a key with the same keyName already exists, |
| * or other database error. If onComplete is defined but onError is undefined, |
| * then this will log any thrown exception. (Some database libraries only use a |
| * callback, so onError is required to be notified of an exception.) |
| */ |
| Consumer.prototype.addDecryptionKey = function |
| (keyName, keyBlob, onComplete, onError) |
| { |
| return SyncPromise.complete(onComplete, onError, |
| this.addDecryptionKeyPromise(keyName, keyBlob, !onComplete)); |
| }; |
| |
| /** |
| * Consume.Error is used internally from promised-based methods to reject with |
| * an error object that has the errorCode and message returned through the |
| * onError callback. |
| * @param {number} errorCode An error code from EncryptError.ErrorCode. |
| * @param {string} message The error message. |
| */ |
| Consumer.Error = function ConsumerError(errorCode, message) |
| { |
| this.errorCode = errorCode; |
| this.message = message; |
| }; |
| |
| /** |
| * If exception is a ConsumerError, then call onError with the errorCode and |
| * message, otherwise call onError with ErrorCode.General. |
| */ |
| Consumer.Error.callOnError = function(onError, exception, messagePrefix) |
| { |
| if (!messagePrefix) |
| messagePrefix = ""; |
| |
| if (exception instanceof Consumer.Error) { |
| try { |
| onError(exception.errorCode, exception.message); |
| } catch (ex) { |
| console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| else { |
| try { |
| onError(EncryptError.ErrorCode.General, messagePrefix + exception); |
| } catch (ex) { |
| console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| } |
| |
| /** |
| * Decrypt encryptedContent using keyBits. |
| * @param {Blob|EncryptedContent} encryptedContent The EncryptedContent to |
| * decrypt, or a Blob which is first decoded as an EncryptedContent. |
| * @param {Blob} keyBits The key value. |
| * @return {Promise|SyncPromise} A promise that returns the decrypted Blob, or |
| * that is rejected with Consumer.Error or other error. |
| */ |
| Consumer.decryptPromise_ = function(encryptedContent, keyBits) |
| { |
| return SyncPromise.resolve() |
| .then(function() { |
| if (typeof encryptedContent == 'object' && encryptedContent instanceof Blob) { |
| // Decode as EncryptedContent. |
| var encryptedBlob = encryptedContent; |
| encryptedContent = new EncryptedContent(); |
| encryptedContent.wireDecode(encryptedBlob); |
| } |
| |
| var payload = encryptedContent.getPayload(); |
| |
| if (encryptedContent.getAlgorithmType() == EncryptAlgorithmType.AesCbc) { |
| // Prepare the parameters. |
| var decryptParams = new EncryptParams(EncryptAlgorithmType.AesCbc); |
| decryptParams.setInitialVector(encryptedContent.getInitialVector()); |
| |
| // Decrypt the content. |
| return AesAlgorithm.decryptPromise(keyBits, payload, decryptParams); |
| } |
| else if (encryptedContent.getAlgorithmType() == EncryptAlgorithmType.RsaOaep) { |
| // Prepare the parameters. |
| var decryptParams = new EncryptParams(EncryptAlgorithmType.RsaOaep); |
| |
| // Decrypt the content. |
| return RsaAlgorithm.decryptPromise(keyBits, payload, decryptParams); |
| } |
| else |
| return SyncPromise.reject(new Consumer.Error |
| (EncryptError.ErrorCode.UnsupportedEncryptionScheme, |
| "" + encryptedContent.getAlgorithmType())); |
| }); |
| }; |
| |
| /** |
| * Decrypt encryptedContent using keyBits. |
| * @param {Blob|EncryptedContent} encryptedContent The EncryptedContent to |
| * decrypt, or a Blob which is first decoded as an EncryptedContent. |
| * @param {Blob} keyBits The key value. |
| * @param {function} onPlainText When the data packet is decrypted, this calls |
| * onPlainText(decryptedBlob) with the decrypted Blob. |
| * @param {function} onError This calls onError(errorCode, message) for an error, |
| * where errorCode is an error code from EncryptError.ErrorCode. |
| */ |
| Consumer.decrypt_ = function(encryptedContent, keyBits, onPlainText, onError) |
| { |
| Consumer.decryptPromise_(encryptedContent, keyBits) |
| .then(function(decryptedBlob) { |
| onPlainText(decryptedBlob); |
| }, function(ex) { |
| Consumer.Error.callOnError(onError, ex); |
| }); |
| }; |
| |
| /** |
| * Decrypt the data packet. |
| * @param {Data} data The data packet. This does not verify the packet. |
| * @param {function} onPlainText When the data packet is decrypted, this calls |
| * onPlainText(decryptedBlob) with the decrypted Blob. |
| * @param {function} onError This calls onError(errorCode, message) for an error, |
| * where errorCode is an error code from EncryptError.ErrorCode. |
| */ |
| Consumer.prototype.decryptContent_ = function(data, onPlainText, onError) |
| { |
| // Get the encrypted content. |
| var dataEncryptedContent = new EncryptedContent(); |
| try { |
| dataEncryptedContent.wireDecode(data.getContent()); |
| } catch (ex) { |
| Consumer.Error.callOnError(onError, ex, "Error decoding EncryptedContent: "); |
| return; |
| } |
| var cKeyName = dataEncryptedContent.getKeyLocator().getKeyName(); |
| |
| // Check if the content key is already in the store. |
| var cKey = this.cKeyMap_[cKeyName.toUri()]; |
| if (cKey) |
| this.decrypt_(dataEncryptedContent, cKey, onPlainText, onError); |
| else { |
| // Retrieve the C-KEY Data from the network. |
| var interestName = new Name(cKeyName); |
| interestName.append(Encryptor.NAME_COMPONENT_FOR).append(this.groupName_); |
| var interest = new Interest(interestName); |
| var thisConsumer = this; |
| this.sendInterest_ |
| (interest, 1, this.cKeyLink_, |
| function(validCKeyData) { |
| thisConsumer.decryptCKey_(validCKeyData, function(cKeyBits) { |
| thisConsumer.cKeyMap_[cKeyName.toUri()] = cKeyBits; |
| Consumer.decrypt_ |
| (dataEncryptedContent, cKeyBits, onPlainText, onError); |
| }, onError); |
| }, |
| onError); |
| } |
| }; |
| |
| /** |
| * Decrypt cKeyData. |
| * @param {Data} cKeyData The C-KEY data packet. |
| * @param {function} onPlainText When the data packet is decrypted, this calls |
| * onPlainText(decryptedBlob) with the decrypted Blob. |
| * @param {function} onError This calls onError(errorCode, message) for an error, |
| * where errorCode is an error code from EncryptError.ErrorCode. |
| */ |
| Consumer.prototype.decryptCKey_ = function(cKeyData, onPlainText, onError) |
| { |
| // Get the encrypted content. |
| var cKeyContent = cKeyData.getContent(); |
| var cKeyEncryptedContent = new EncryptedContent(); |
| try { |
| cKeyEncryptedContent.wireDecode(cKeyContent); |
| } catch (ex) { |
| Consumer.Error.callOnError(onError, ex, "Error decoding EncryptedContent: "); |
| return; |
| } |
| var eKeyName = cKeyEncryptedContent.getKeyLocator().getKeyName(); |
| var dKeyName = eKeyName.getPrefix(-3); |
| dKeyName.append(Encryptor.NAME_COMPONENT_D_KEY).append(eKeyName.getSubName(-2)); |
| |
| // Check if the decryption key is already in the store. |
| var dKey = this.dKeyMap_[dKeyName.toUri()]; |
| if (dKey) |
| this.decrypt_(cKeyEncryptedContent, dKey, onPlainText, onError); |
| else { |
| // Get the D-Key Data. |
| var interestName = new Name(dKeyName); |
| interestName.append(Encryptor.NAME_COMPONENT_FOR).append(this.consumerName_); |
| var interest = new Interest(interestName); |
| var thisConsumer = this; |
| this.sendInterest_ |
| (interest, 1, this.dKeyLink_, |
| function(validDKeyData) { |
| thisConsumer.decryptDKeyPromise_(validDKeyData) |
| .then(function(dKeyBits) { |
| thisConsumer.dKeyMap_[dKeyName.toUri()] = dKeyBits; |
| Consumer.decrypt_ |
| (cKeyEncryptedContent, dKeyBits, onPlainText, onError); |
| }, function(ex) { |
| Consumer.Error.callOnError(onError, ex, "decryptDKey error: "); |
| }); |
| }, |
| onError); |
| } |
| }; |
| |
| /** |
| * Decrypt dKeyData. |
| * @param {Data} dKeyData The D-KEY data packet. |
| * @return {Promise|SyncPromise} A promise that returns the decrypted Blob, or |
| * that is rejected with Consumer.Error or other error. |
| */ |
| Consumer.prototype.decryptDKeyPromise_ = function(dKeyData) |
| { |
| var dataContent; |
| var encryptedNonce; |
| var encryptedPayloadBlob; |
| var thisConsumer = this; |
| |
| return SyncPromise.resolve() |
| .then(function() { |
| // Get the encrypted content. |
| dataContent = dKeyData.getContent(); |
| |
| // Process the nonce. |
| // dataContent is a sequence of the two EncryptedContent. |
| encryptedNonce = new EncryptedContent(); |
| encryptedNonce.wireDecode(dataContent); |
| var consumerKeyName = encryptedNonce.getKeyLocator().getKeyName(); |
| |
| // Get consumer decryption key. |
| return thisConsumer.getDecryptionKeyPromise_(consumerKeyName); |
| }) |
| .then(function(consumerKeyBlob) { |
| if (consumerKeyBlob.size() == 0) |
| return SyncPromise.reject(new Consumer.Error |
| (EncryptError.ErrorCode.NoDecryptKey, |
| "The desired consumer decryption key in not in the database")); |
| |
| // Process the D-KEY. |
| // Use the size of encryptedNonce to find the start of encryptedPayload. |
| var encryptedPayloadBuffer = dataContent.buf().slice |
| (encryptedNonce.wireEncode().size()); |
| encryptedPayloadBlob = new Blob(encryptedPayloadBuffer, false); |
| if (encryptedPayloadBlob.size() == 0) |
| return SyncPromise.reject(new Consumer.Error |
| (EncryptError.ErrorCode.InvalidEncryptedFormat, |
| "The data packet does not satisfy the D-KEY packet format")); |
| |
| // Decrypt the D-KEY. |
| return Consumer.decryptPromise_(encryptedNonce, consumerKeyBlob); |
| }) |
| .then(function(nonceKeyBits) { |
| return Consumer.decryptPromise_(encryptedPayloadBlob, nonceKeyBits); |
| }); |
| }; |
| |
| /** |
| * Express the interest, call verifyData for the fetched Data packet and call |
| * onVerified if verify succeeds. If verify fails, call |
| * onError(EncryptError.ErrorCode.Validation, "verifyData failed"). If the |
| * interest times out, re-express nRetrials times. If the interest times out |
| * nRetrials times, or for a network Nack, call |
| * onError(EncryptError.ErrorCode.DataRetrievalFailure, interest.getName().toUri()). |
| * @param {Interest} interest The Interest to express. |
| * @param {number} nRetrials The number of retrials left after a timeout. |
| * @param {Link} link The Link object to use in the Interest. This does not make |
| * a copy of the Link object. If the Link object's getDelegations().size() is |
| * zero, don't use it. |
| * @param {function} onVerified When the fetched Data packet validation |
| * succeeds, this calls onVerified(data). |
| * @param {function} onError This calls onError(errorCode, message) for an error, |
| * where errorCode is an error code from EncryptError.ErrorCode. |
| */ |
| Consumer.prototype.sendInterest_ = function |
| (interest, nRetrials, link, onVerified, onError) |
| { |
| // Prepare the callback functions. |
| var thisConsumer = this; |
| var onData = function(contentInterest, contentData) { |
| try { |
| thisConsumer.keyChain_.verifyData |
| (contentData, onVerified, |
| function(d, reason) { |
| try { |
| onError |
| (EncryptError.ErrorCode.Validation, "verifyData failed. Reason: " + |
| reason); |
| } catch (ex) { |
| console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| }); |
| } catch (ex) { |
| Consumer.Error.callOnError(onError, ex, "verifyData error: "); |
| } |
| }; |
| |
| function onNetworkNack(interest, networkNack) { |
| // We have run out of options. Report a retrieval failure. |
| try { |
| onError(EncryptError.ErrorCode.DataRetrievalFailure, |
| interest.getName().toUri()); |
| } catch (ex) { |
| console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| |
| var onTimeout = function(interest) { |
| if (nRetrials > 0) |
| thisConsumer.sendInterest_(interest, nRetrials - 1, link, onVerified, onError); |
| else |
| onNetworkNack(interest, new NetworkNack()); |
| }; |
| |
| var request; |
| if (link.getDelegations().size() === 0) |
| // We can use the supplied interest without copying. |
| request = interest; |
| else { |
| // Copy the supplied interest and add the Link. |
| request = new Interest(interest); |
| // This will use a cached encoding if available. |
| request.setLinkWireEncoding(link.wireEncode()); |
| } |
| |
| try { |
| this.face_.expressInterest(request, onData, onTimeout, onNetworkNack); |
| } catch (ex) { |
| Consumer.Error.callOnError(onError, ex, "expressInterest error: "); |
| } |
| }; |
| |
| /** |
| * Get the encoded blob of the decryption key with decryptionKeyName from the |
| * database. |
| * @param {Name} decryptionKeyName The key name. |
| * @return {Promise|SyncPromise} A promise that returns a Blob with the encoded |
| * key (or an isNull Blob if cannot find the key with decryptionKeyName), or |
| * that is rejected with ConsumerDb.Error for a database error. |
| */ |
| Consumer.prototype.getDecryptionKeyPromise_ = function(decryptionKeyName) |
| { |
| return this.database_.getKeyPromise(decryptionKeyName); |
| }; |
| |
| Consumer.NO_LINK = new Link(); |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From ndn-group-encrypt src/decrypt-key https://github.com/named-data/ndn-group-encrypt |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Blob = require('../util/blob.js').Blob; |
| |
| /** |
| * A DecryptKey supplies the key for decrypt. |
| * Create a DecryptKey with the given key value. |
| * @param {Blob|DecryptKey} value If value is another DecryptKey then copy it. |
| * Otherwise, value is the key value. |
| * @note This class is an experimental feature. The API may change. |
| * @constructor |
| */ |
| var DecryptKey = function DecryptKey(value) |
| { |
| if (typeof value === 'object' && value instanceof DecryptKey) { |
| // Make a deep copy. |
| this.keyBits_ = value.keyBits_; |
| } |
| else { |
| var keyBits = value; |
| this.keyBits_ = typeof keyBits === 'object' && keyBits instanceof Blob ? |
| keyBits : new Blob(keyBits); |
| } |
| }; |
| |
| exports.DecryptKey = DecryptKey; |
| |
| /** |
| * Get the key value. |
| * @return {Blob} The key value. |
| */ |
| DecryptKey.prototype.getKeyBits = function() { return this.keyBits_; }; |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From ndn-group-encrypt src/error-code https://github.com/named-data/ndn-group-encrypt |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * EncryptError holds the ErrorCode enum for errors from the encrypt library. |
| */ |
| var EncryptError = function EncryptError() |
| { |
| }; |
| |
| exports.EncryptError = EncryptError; |
| |
| EncryptError.ErrorCode = { |
| Timeout: 1, |
| Validation: 2, |
| UnsupportedEncryptionScheme: 32, |
| InvalidEncryptedFormat: 33, |
| NoDecryptKey: 34, |
| EncryptionFailure: 35, |
| DataRetrievalFailure: 36, |
| General: 100 |
| }; |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From ndn-group-encrypt src/encrypt-key https://github.com/named-data/ndn-group-encrypt |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Blob = require('../util/blob.js').Blob; |
| |
| /** |
| * An EncryptKey supplies the key for encrypt. |
| * Create an EncryptKey with the given key value. |
| * @param {Blob|EncryptKey} value If value is another EncryptKey then copy it. |
| * Otherwise, value is the key value. |
| * @note This class is an experimental feature. The API may change. |
| * @constructor |
| */ |
| var EncryptKey = function EncryptKey(value) |
| { |
| if (typeof value === 'object' && value instanceof EncryptKey) { |
| // Make a deep copy. |
| this.keyBits_ = value.keyBits_; |
| } |
| else { |
| var keyBits = value; |
| this.keyBits_ = typeof keyBits === 'object' && keyBits instanceof Blob ? |
| keyBits : new Blob(keyBits); |
| } |
| }; |
| |
| exports.EncryptKey = EncryptKey; |
| |
| /** |
| * Get the key value. |
| * @return {Blob} The key value. |
| */ |
| EncryptKey.prototype.getKeyBits = function() { return this.keyBits_; }; |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From ndn-group-encrypt src/encrypted-content https://github.com/named-data/ndn-group-encrypt |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var KeyLocator = require('../key-locator.js').KeyLocator; /** @ignore */ |
| var WireFormat = require('../encoding/wire-format.js').WireFormat; /** @ignore */ |
| var Blob = require('../util/blob.js').Blob; |
| |
| /** |
| * An EncryptedContent holds an encryption type, a payload and other fields |
| * representing encrypted content. |
| * @param {EncryptedContent} (optional) If value is another EncryptedContent |
| * then copy it. If value is omitted then create an EncryptedContent with |
| * unspecified values. |
| * @note This class is an experimental feature. The API may change. |
| * @constructor |
| */ |
| var EncryptedContent = function EncryptedContent(value) |
| { |
| if (typeof value === 'object' && value instanceof EncryptedContent) { |
| // Make a deep copy. |
| this.algorithmType_ = value.algorithmType_; |
| this.keyLocator_ = new KeyLocator(value.keyLocator_); |
| this.initialVector_ = value.initialVector_; |
| this.payload_ = value.payload_; |
| } |
| else { |
| this.algorithmType_ = null; |
| this.keyLocator_ = new KeyLocator(); |
| this.initialVector_ = new Blob(); |
| this.payload_ = new Blob(); |
| } |
| }; |
| |
| exports.EncryptedContent = EncryptedContent; |
| |
| /** |
| * Get the algorithm type from EncryptAlgorithmType. |
| * @return {number} The algorithm type from EncryptAlgorithmType, or null if |
| * not specified. |
| */ |
| EncryptedContent.prototype.getAlgorithmType = function() |
| { |
| return this.algorithmType_; |
| }; |
| |
| /** |
| * Get the key locator. |
| * @return {KeyLocator} The key locator. If not specified, getType() is null. |
| */ |
| EncryptedContent.prototype.getKeyLocator = function() |
| { |
| return this.keyLocator_; |
| }; |
| |
| /** |
| * Get the initial vector. |
| * @return {Blob} The initial vector. If not specified, isNull() is true. |
| */ |
| EncryptedContent.prototype.getInitialVector = function() |
| { |
| return this.initialVector_; |
| }; |
| |
| /** |
| * Get the payload. |
| * @return {Blob} The payload. If not specified, isNull() is true. |
| */ |
| EncryptedContent.prototype.getPayload = function() |
| { |
| return this.payload_; |
| }; |
| |
| /** |
| * Set the algorithm type. |
| * @param {number} algorithmType The algorithm type from EncryptAlgorithmType. |
| * If not specified, set to null. |
| * @return {EncryptedContent} This EncryptedContent so that you can chain calls |
| * to update values. |
| */ |
| EncryptedContent.prototype.setAlgorithmType = function(algorithmType) |
| { |
| this.algorithmType_ = algorithmType; |
| return this; |
| }; |
| |
| /** |
| * Set the key locator. |
| * @param {KeyLocator} keyLocator The key locator. This makes a copy of the |
| * object. If not specified, set to the default KeyLocator(). |
| * @return {EncryptedContent} This EncryptedContent so that you can chain calls |
| * to update values. |
| */ |
| EncryptedContent.prototype.setKeyLocator = function(keyLocator) |
| { |
| this.keyLocator_ = typeof keyLocator === 'object' && |
| keyLocator instanceof KeyLocator ? |
| new KeyLocator(keyLocator) : new KeyLocator(); |
| return this; |
| }; |
| |
| /** |
| * Set the initial vector. |
| * @param {Blob} initialVector The initial vector. If not specified, set to the |
| * default Blob() where isNull() is true. |
| * @return {EncryptedContent} This EncryptedContent so that you can chain calls |
| * to update values. |
| */ |
| EncryptedContent.prototype.setInitialVector = function(initialVector) |
| { |
| this.initialVector_ = |
| typeof initialVector === 'object' && initialVector instanceof Blob ? |
| initialVector : new Blob(initialVector); |
| return this; |
| }; |
| |
| /** |
| * Set the encrypted payload. |
| * @param {Blob} payload The payload. If not specified, set to the default Blob() |
| * where isNull() is true. |
| * @return {EncryptedContent} This EncryptedContent so that you can chain calls |
| * to update values. |
| */ |
| EncryptedContent.prototype.setPayload = function(payload) |
| { |
| this.payload_ = typeof payload === 'object' && payload instanceof Blob ? |
| payload : new Blob(payload); |
| return this; |
| }; |
| |
| /** |
| * Encode this EncryptedContent for a particular wire format. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * this object. If omitted, use WireFormat.getDefaultWireFormat(). |
| * @return {Blob} The encoded buffer in a Blob object. |
| */ |
| EncryptedContent.prototype.wireEncode = function(wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| return wireFormat.encodeEncryptedContent(this); |
| }; |
| |
| /** |
| * Decode the input using a particular wire format and update this |
| * EncryptedContent. |
| * @param {Blob|Buffer} input The buffer with the bytes to decode. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to decode |
| * this object. If omitted, use WireFormat.getDefaultWireFormat(). |
| */ |
| EncryptedContent.prototype.wireDecode = function(input, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| if (typeof input === 'object' && input instanceof Blob) |
| // Input is a blob, so get its buf() and set copy false. |
| wireFormat.decodeEncryptedContent(this, input.buf(), false); |
| else |
| wireFormat.decodeEncryptedContent(this, input, true); |
| }; |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From ndn-group-encrypt src/group-manager-db https://github.com/named-data/ndn-group-encrypt |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var SyncPromise = require('../util/sync-promise.js').SyncPromise; |
| |
| /** |
| * GroupManagerDb is a base class for the storage of data used by the |
| * GroupManager. It contains two tables to store Schedules and Members. |
| * This is an abstract base class. A subclass must implement the methods. |
| * For example, see Sqlite3GroupManagerDb (for Nodejs) or IndexedDbGroupManagerDb |
| * (for the browser). |
| * @note This class is an experimental feature. The API may change. |
| * @constructor |
| */ |
| var GroupManagerDb = function GroupManagerDb() |
| { |
| }; |
| |
| exports.GroupManagerDb = GroupManagerDb; |
| |
| /** |
| * Create a new GroupManagerDb.Error to report an error using GroupManagerDb |
| * methods, wrapping the given error object. |
| * Call with: throw new GroupManagerDb.Error(new Error("message")). |
| * @constructor |
| * @param {Error} error The exception created with new Error. |
| */ |
| GroupManagerDb.Error = function GroupManagerDbError(error) |
| { |
| if (error) { |
| error.__proto__ = GroupManagerDb.Error.prototype; |
| return error; |
| } |
| } |
| |
| GroupManagerDb.Error.prototype = new Error(); |
| GroupManagerDb.Error.prototype.name = "GroupManagerDbError"; |
| |
| ////////////////////////////////////////////////////// Schedule management. |
| |
| /** |
| * Check if there is a schedule with the given name. |
| * @param {string} name The name of the schedule. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns true if there is a |
| * schedule (else false), or that is rejected with GroupManagerDb.Error for a |
| * database error. |
| */ |
| GroupManagerDb.prototype.hasSchedulePromise = function(name, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("GroupManagerDb.hasSchedulePromise is not implemented")); |
| }; |
| |
| /** |
| * List all the names of the schedules. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns a new array of string |
| * with the names of all schedules, or that is rejected with |
| * GroupManagerDb.Error for a database error. |
| */ |
| GroupManagerDb.prototype.listAllScheduleNamesPromise = function(useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("GroupManagerDb.listAllScheduleNamesPromise is not implemented")); |
| }; |
| |
| /** |
| * Get a schedule with the given name. |
| * @param {string} name The name of the schedule. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns a new Schedule object, |
| * or that is rejected with GroupManagerDb.Error if the schedule does not exist |
| * or other database error. |
| */ |
| GroupManagerDb.prototype.getSchedulePromise = function(name, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("GroupManagerDb.getSchedulePromise is not implemented")); |
| }; |
| |
| /** |
| * For each member using the given schedule, get the name and public key DER |
| * of the member's key. |
| * @param {string} name The name of the schedule. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns a new array of object |
| * (where "keyName" is the Name of the public key and "publicKey" is the Blob of |
| * the public key DER), or that is rejected with GroupManagerDb.Error for a |
| * database error. Note that the member's identity name is keyName.getPrefix(-1). |
| * If the schedule name is not found, the list is empty. |
| */ |
| GroupManagerDb.prototype.getScheduleMembersPromise = function |
| (name, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("GroupManagerDb.getScheduleMembersPromise is not implemented")); |
| }; |
| |
| /** |
| * Add a schedule with the given name. |
| * @param {string} name The name of the schedule. The name cannot be empty. |
| * @param {Schedule} schedule The Schedule to add. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that fulfills when the schedule is |
| * added, or that is rejected with GroupManagerDb.Error if a schedule with the |
| * same name already exists, if the name is empty, or other database error. |
| */ |
| GroupManagerDb.prototype.addSchedulePromise = function(name, schedule, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("GroupManagerDb.addSchedulePromise is not implemented")); |
| }; |
| |
| /** |
| * Delete the schedule with the given name. Also delete members which use this |
| * schedule. If there is no schedule with the name, then do nothing. |
| * @param {string} name The name of the schedule. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that fulfills when the schedule is |
| * deleted (or there is no such schedule), or that is rejected with |
| * GroupManagerDb.Error for a database error. |
| */ |
| GroupManagerDb.prototype.deleteSchedulePromise = function(name, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("GroupManagerDb.deleteSchedulePromise is not implemented")); |
| }; |
| |
| /** |
| * Rename a schedule with oldName to newName. |
| * @param {string} oldName The name of the schedule to be renamed. |
| * @param {string} newName The new name of the schedule. The name cannot be empty. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that fulfills when the schedule is |
| * renamed, or that is rejected with GroupManagerDb.Error if a schedule with |
| * newName already exists, if the schedule with oldName does not exist, if |
| * newName is empty, or other database error. |
| */ |
| GroupManagerDb.prototype.renameSchedulePromise = function |
| (oldName, newName, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("GroupManagerDb.renameSchedulePromise is not implemented")); |
| }; |
| |
| /** |
| * Update the schedule with name and replace the old object with the given |
| * schedule. Otherwise, if no schedule with name exists, a new schedule |
| * with name and the given schedule will be added to database. |
| * @param {string} name The name of the schedule. The name cannot be empty. |
| * @param {Schedule} schedule The Schedule to update or add. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that fulfills when the schedule is |
| * updated, or that is rejected with GroupManagerDb.Error if the name is empty, |
| * or other database error. |
| */ |
| GroupManagerDb.prototype.updateSchedulePromise = function |
| (name, schedule, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("GroupManagerDb.updateSchedulePromise is not implemented")); |
| }; |
| |
| ////////////////////////////////////////////////////// Member management. |
| |
| /** |
| * Check if there is a member with the given identity name. |
| * @param {Name} identity The member's identity name. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns true if there is a |
| * member (else false), or that is rejected with GroupManagerDb.Error for a |
| * database error. |
| */ |
| GroupManagerDb.prototype.hasMemberPromise = function(identity, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("GroupManagerDb.hasMemberPromise is not implemented")); |
| }; |
| |
| /** |
| * List all the members. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns a new array of Name with |
| * the names of all members, or that is rejected with GroupManagerDb.Error for a |
| * database error. |
| */ |
| GroupManagerDb.prototype.listAllMembersPromise = function(useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("GroupManagerDb.listAllMembersPromise is not implemented")); |
| }; |
| |
| /** |
| * Get the name of the schedule for the given member's identity name. |
| * @param {Name} identity The member's identity name. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns the string schedule name, |
| * or that is rejected with GroupManagerDb.Error if there's no member with the |
| * given identity name in the database, or other database error. |
| */ |
| GroupManagerDb.prototype.getMemberSchedulePromise = function(identity, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("GroupManagerDb.getMemberSchedulePromise is not implemented")); |
| }; |
| |
| /** |
| * Add a new member with the given key named keyName into a schedule named |
| * scheduleName. The member's identity name is keyName.getPrefix(-1). |
| * @param {string} scheduleName The schedule name. |
| * @param {Name} keyName The name of the key. |
| * @param {Blob} key A Blob of the public key DER. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that fulfills when the member is |
| * added, or that is rejected with GroupManagerDb.Error if there's no schedule |
| * named scheduleName, if the member's identity name already exists, or other |
| * database error. |
| */ |
| GroupManagerDb.prototype.addMemberPromise = function |
| (scheduleName, keyName, key, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("GroupManagerDb.addMemberPromise is not implemented")); |
| }; |
| |
| /** |
| * Change the name of the schedule for the given member's identity name. |
| * @param {Name} identity The member's identity name. |
| * @param {string} scheduleName The new schedule name. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that fulfills when the member is |
| * updated, or that is rejected with GroupManagerDb.Error if there's no member |
| * with the given identity name in the database, or there's no schedule named |
| * scheduleName, or other database error. |
| */ |
| GroupManagerDb.prototype.updateMemberSchedulePromise = function |
| (identity, scheduleName, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("GroupManagerDb.updateMemberSchedulePromise is not implemented")); |
| }; |
| |
| /** |
| * Delete a member with the given identity name. If there is no member with |
| * the identity name, then do nothing. |
| * @param {Name} identity The member's identity name. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that fulfills when the member is |
| * deleted (or there is no such member), or that is rejected with |
| * GroupManagerDb.Error for a database error. |
| */ |
| GroupManagerDb.prototype.deleteMemberPromise = function(identity, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("GroupManagerDb.deleteMemberPromise is not implemented")); |
| }; |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From ndn-group-encrypt src/group-manager https://github.com/named-data/ndn-group-encrypt |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Name = require('../name.js').Name; /** @ignore */ |
| var Data = require('../data.js').Data; /** @ignore */ |
| var SyncPromise = require('../util/sync-promise.js').SyncPromise; /** @ignore */ |
| var IdentityCertificate = require('../security/certificate/identity-certificate.js').IdentityCertificate; /** @ignore */ |
| var SecurityException = require('../security/security-exception.js').SecurityException; /** @ignore */ |
| var RsaKeyParams = require('../security/key-params.js').RsaKeyParams; /** @ignore */ |
| var EncryptParams = require('./algo/encrypt-params.js').EncryptParams; /** @ignore */ |
| var EncryptAlgorithmType = require('./algo/encrypt-params.js').EncryptAlgorithmType; /** @ignore */ |
| var Encryptor = require('./algo/encryptor.js').Encryptor; /** @ignore */ |
| var RsaAlgorithm = require('./algo/rsa-algorithm.js').RsaAlgorithm; /** @ignore */ |
| var Interval = require('./interval.js').Interval; /** @ignore */ |
| var Schedule = require('./schedule.js').Schedule; |
| |
| /** |
| * A GroupManager manages keys and schedules for group members in a particular |
| * namespace. |
| * Create a group manager with the given values. The group manager namespace |
| * is <prefix>/read/<dataType> . |
| * @param {Name} prefix The prefix for the group manager namespace. |
| * @param {Name} dataType The data type for the group manager namespace. |
| * @param {GroupManagerDb} database The GroupManagerDb for storing the group |
| * management information (including user public keys and schedules). |
| * @param {number} keySize The group key will be an RSA key with keySize bits. |
| * @param {number} freshnessHours The number of hours of the freshness period of |
| * data packets carrying the keys. |
| * @param {KeyChain} keyChain The KeyChain to use for signing data packets. This |
| * signs with the default identity. |
| * @note This class is an experimental feature. The API may change. |
| * @constructor |
| */ |
| var GroupManager = function GroupManager |
| (prefix, dataType, database, keySize, freshnessHours, keyChain) |
| { |
| this.namespace_ = new Name(prefix).append(Encryptor.NAME_COMPONENT_READ) |
| .append(dataType); |
| this.database_ = database; |
| this.keySize_ = keySize; |
| this.freshnessHours_ = freshnessHours; |
| |
| this.keyChain_ = keyChain; |
| }; |
| |
| exports.GroupManager = GroupManager; |
| |
| /** |
| * Create a group key for the interval into which timeSlot falls. This creates |
| * a group key if it doesn't exist, and encrypts the key using the public key of |
| * each eligible member. |
| * @param {number} timeSlot The time slot to cover as milliseconds since |
| * Jan 1, 1970 UTC. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns a List of Data packets |
| * (where the first is the E-KEY data packet with the group's public key and the |
| * rest are the D-KEY data packets with the group's private key encrypted with |
| * the public key of each eligible member), or that is rejected with |
| * GroupManagerDb.Error for a database error or SecurityException for an error |
| * using the security KeyChain. |
| */ |
| GroupManager.prototype.getGroupKeyPromise = function(timeSlot, useSync) |
| { |
| var memberKeys = []; |
| var result = []; |
| var thisManager = this; |
| var privateKeyBlob; |
| var publicKeyBlob; |
| var startTimeStamp; |
| var endTimeStamp; |
| |
| // Get the time interval. |
| return this.calculateIntervalPromise_(timeSlot, memberKeys, useSync) |
| .then(function(finalInterval) { |
| if (finalInterval.isValid() == false) |
| return SyncPromise.resolve(result); |
| |
| startTimeStamp = Schedule.toIsoString(finalInterval.getStartTime()); |
| endTimeStamp = Schedule.toIsoString(finalInterval.getEndTime()); |
| |
| // Generate the private and public keys. |
| return thisManager.generateKeyPairPromise_(useSync) |
| .then(function(keyPair) { |
| privateKeyBlob = keyPair.privateKeyBlob; |
| publicKeyBlob = keyPair.publicKeyBlob; |
| |
| // Add the first element to the result. |
| // The E-KEY (public key) data packet name convention is: |
| // /<data_type>/E-KEY/[start-ts]/[end-ts] |
| return thisManager.createEKeyDataPromise_ |
| (startTimeStamp, endTimeStamp, publicKeyBlob, useSync); |
| }) |
| .then(function(data) { |
| result.push(data); |
| |
| // Encrypt the private key with the public key from each member's certificate. |
| |
| // Process the memberKeys entry at i, and recursively call to process the |
| // next entry. Return a promise which is resolved when all are processed. |
| // (We have to make a recursive function to use Promises.) |
| function processMemberKey(i) { |
| if (i >= memberKeys.length) |
| // Finished. |
| return SyncPromise.resolve(); |
| |
| var keyName = memberKeys[i].keyName; |
| var certificateKey = memberKeys[i].publicKey; |
| |
| return thisManager.createDKeyDataPromise_ |
| (startTimeStamp, endTimeStamp, keyName, privateKeyBlob, certificateKey, |
| useSync) |
| .then(function(data) { |
| result.push(data); |
| |
| return processMemberKey(i + 1); |
| }); |
| } |
| |
| return processMemberKey(0); |
| }) |
| .then(function() { |
| return SyncPromise.resolve(result); |
| }); |
| }); |
| }; |
| |
| /** |
| * Add a schedule with the given scheduleName. |
| * @param {string} scheduleName The name of the schedule. The name cannot be |
| * empty. |
| * @param {Schedule} schedule The Schedule to add. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that fulfills when the schedule is |
| * added, or that is rejected with GroupManagerDb.Error if a schedule with the |
| * same name already exists, if the name is empty, or other database error. |
| */ |
| GroupManager.prototype.addSchedulePromise = function |
| (scheduleName, schedule, useSync) |
| { |
| return this.database_.addSchedulePromise(scheduleName, schedule, useSync); |
| }; |
| |
| /** |
| * Delete the schedule with the given scheduleName. Also delete members which |
| * use this schedule. If there is no schedule with the name, then do nothing. |
| * @param {string} scheduleName The name of the schedule. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that fulfills when the schedule is |
| * deleted (or there is no such schedule), or that is rejected with |
| * GroupManagerDb.Error for a database error. |
| */ |
| GroupManager.prototype.deleteSchedulePromise = function(scheduleName, useSync) |
| { |
| return this.database_.deleteSchedulePromise(scheduleName, useSync); |
| }; |
| |
| /** |
| * Update the schedule with scheduleName and replace the old object with the |
| * given schedule. Otherwise, if no schedule with name exists, a new schedule |
| * with name and the given schedule will be added to database. |
| * @param {string} scheduleName The name of the schedule. The name cannot be |
| * empty. |
| * @param {Schedule} schedule The Schedule to update or add. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that fulfills when the schedule is |
| * updated, or that is rejected with GroupManagerDb.Error if the name is empty, |
| * or other database error. |
| */ |
| GroupManager.prototype.updateSchedulePromise = function |
| (name, scheduleName, useSync) |
| { |
| return this.database_.updateSchedulePromise(scheduleName, schedule, useSync); |
| }; |
| |
| /** |
| * Add a new member with the given memberCertificate into a schedule named |
| * scheduleName. If cert is an IdentityCertificate made from memberCertificate, |
| * then the member's identity name is cert.getPublicKeyName().getPrefix(-1). |
| * @param {string} scheduleName The schedule name. |
| * @param {Data} memberCertificate The member's certificate. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that fulfills when the member is |
| * added, or that is rejected with GroupManagerDb.Error if there's no schedule |
| * named scheduleName, if the member's identity name already exists, or other |
| * database error. Or a promise that is rejected with DerDecodingException for |
| * an error decoding memberCertificate as a certificate. |
| */ |
| GroupManager.prototype.addMemberPromise = function |
| (scheduleName, memberCertificate, useSync) |
| { |
| var cert = new IdentityCertificate(memberCertificate); |
| return this.database_.addMemberPromise |
| (scheduleName, cert.getPublicKeyName(), cert.getPublicKeyInfo().getKeyDer(), |
| useSync); |
| }; |
| |
| /** |
| * Remove a member with the given identity name. If there is no member with |
| * the identity name, then do nothing. |
| * @param {Name} identity The member's identity name. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that fulfills when the member is |
| * removed (or there is no such member), or that is rejected with |
| * GroupManagerDb.Error for a database error. |
| */ |
| GroupManager.prototype.removeMemberPromise = function(identity, useSync) |
| { |
| return this.database_.deleteMemberPromise(identity, useSync); |
| }; |
| |
| /** |
| * Change the name of the schedule for the given member's identity name. |
| * @param {Name} identity The member's identity name. |
| * @param {string} scheduleName The new schedule name. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that fulfills when the member is |
| * updated, or that is rejected with GroupManagerDb.Error if there's no member |
| * with the given identity name in the database, or there's no schedule named |
| * scheduleName. |
| */ |
| GroupManager.prototype.updateMemberSchedulePromise = function |
| (identity, scheduleName, useSync) |
| { |
| return this.database_.updateMemberSchedulePromise |
| (identity, scheduleName, useSync); |
| }; |
| |
| /** |
| * Calculate an Interval that covers the timeSlot. |
| * @param {number} timeSlot The time slot to cover as milliseconds since |
| * Jan 1, 1970 UTC. |
| * @param {Array<object>} memberKeys First clear memberKeys then fill it with |
| * the info of members who are allowed to access the interval. memberKeys is an |
| * array of object where "keyName" is the Name of the public key and "publicKey" |
| * is the Blob of the public key DER. The memberKeys entries are sorted by |
| * the entry keyName. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns a new nterval covering |
| * the time slot, or that is rejected with GroupManagerDb.Error for a database |
| * error. |
| */ |
| GroupManager.prototype.calculateIntervalPromise_ = function |
| (timeSlot, memberKeys, useSync) |
| { |
| // Prepare. |
| var positiveResult = new Interval(); |
| var negativeResult = new Interval(); |
| // Clear memberKeys. |
| memberKeys.splice(0, memberKeys.length); |
| var thisManager = this; |
| |
| // Get the all intervals from the schedules. |
| return this.database_.listAllScheduleNamesPromise(useSync) |
| .then(function(scheduleNames) { |
| // Process the scheduleNames entry at i, and recursively call to process the |
| // next entry. Return a promise which is resolved when all are processed. |
| // (We have to make a recursive function to use Promises.) |
| function processSchedule(i) { |
| if (i >= scheduleNames.length) |
| // Finished. |
| return SyncPromise.resolve(); |
| |
| var scheduleName = scheduleNames[i]; |
| |
| return thisManager.database_.getSchedulePromise(scheduleName, useSync) |
| .then(function(schedule) { |
| var result = schedule.getCoveringInterval(timeSlot); |
| var tempInterval = result.interval; |
| |
| if (result.isPositive) { |
| if (!positiveResult.isValid()) |
| positiveResult = tempInterval; |
| positiveResult.intersectWith(tempInterval); |
| |
| return thisManager.database_.getScheduleMembersPromise |
| (scheduleName, useSync) |
| .then(function(map) { |
| // Add each entry in map to memberKeys. |
| for (var iMap = 0; iMap < map.length; ++iMap) |
| GroupManager.memberKeysAdd_(memberKeys, map[iMap]); |
| |
| return processSchedule(i + 1); |
| }); |
| } |
| else { |
| if (!negativeResult.isValid()) |
| negativeResult = tempInterval; |
| negativeResult.intersectWith(tempInterval); |
| |
| return processSchedule(i + 1); |
| } |
| }); |
| } |
| |
| return processSchedule(0); |
| }) |
| .then(function() { |
| if (!positiveResult.isValid()) |
| // Return an invalid interval when there is no member which has an |
| // interval covering the time slot. |
| return SyncPromise.resolve(new Interval(false)); |
| |
| // Get the final interval result. |
| var finalInterval; |
| if (negativeResult.isValid()) |
| finalInterval = positiveResult.intersectWith(negativeResult); |
| else |
| finalInterval = positiveResult; |
| |
| return SyncPromise.resolve(finalInterval); |
| }); |
| }; |
| |
| /** |
| * Add entry to memberKeys, sorted by entry.keyName. If there is already an |
| * entry with keyName, then don't add. |
| */ |
| GroupManager.memberKeysAdd_ = function(memberKeys, entry) |
| { |
| // Find the index of the first node where the keyName is not less than |
| // entry.keyName. |
| var i = 0; |
| while (i < memberKeys.length) { |
| var comparison = memberKeys[i].keyName.compare(entry.keyName); |
| if (comparison == 0) |
| // A duplicate, so don't add. |
| return; |
| |
| if (comparison > 0) |
| break; |
| i += 1; |
| } |
| |
| memberKeys.splice(i, 0, entry); |
| }; |
| |
| /** |
| * Generate an RSA key pair according to keySize_. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns an object where |
| * "privateKeyBlob" is the encoding Blob of the private key and "publicKeyBlob" |
| * is the encoding Blob of the public key. |
| */ |
| GroupManager.prototype.generateKeyPairPromise_ = function(useSync) |
| { |
| var params = new RsaKeyParams(this.keySize_); |
| |
| return RsaAlgorithm.generateKeyPromise(params) |
| .then(function(privateKey) { |
| var privateKeyBlob = privateKey.getKeyBits(); |
| var publicKey = RsaAlgorithm.deriveEncryptKey(privateKeyBlob); |
| var publicKeyBlob = publicKey.getKeyBits(); |
| |
| return SyncPromise.resolve |
| ({ privateKeyBlob: privateKeyBlob, publicKeyBlob: publicKeyBlob }); |
| }); |
| }; |
| |
| /** |
| * Create an E-KEY Data packet for the given public key. |
| * @param {string} startTimeStamp The start time stamp string to put in the name. |
| * @param {string} endTimeStamp The end time stamp string to put in the name. |
| * @param {Blob} publicKeyBlob A Blob of the public key DER. |
| * @return The Data packet. |
| * @throws SecurityException for an error using the security KeyChain. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns the Data packet, or that |
| * is rejected with SecurityException for an error using the security KeyChain. |
| */ |
| GroupManager.prototype.createEKeyDataPromise_ = function |
| (startTimeStamp, endTimeStamp, publicKeyBlob, useSync) |
| { |
| var name = new Name(this.namespace_); |
| name.append(Encryptor.NAME_COMPONENT_E_KEY).append(startTimeStamp) |
| .append(endTimeStamp); |
| |
| var data = new Data(name); |
| data.getMetaInfo().setFreshnessPeriod |
| (this.freshnessHours_ * GroupManager.MILLISECONDS_IN_HOUR); |
| data.setContent(publicKeyBlob); |
| |
| return this.keyChain_.signPromise(data); |
| }; |
| |
| /** |
| * Create a D-KEY Data packet with an EncryptedContent for the given private |
| * key, encrypted with the certificate key. |
| * @param {string} startTimeStamp The start time stamp string to put in the name. |
| * @param {string} endTimeStamp The end time stamp string to put in the name. |
| * @param {Name} keyName The key name to put in the data packet name and the |
| * EncryptedContent key locator. |
| * @param {Blob} privateKeyBlob A Blob of the encoded private key. |
| * @param {Blob} certificateKey The certificate key encoding, used to encrypt |
| * the private key. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns the Data packet, or that |
| * is rejected with SecurityException for an error using the security KeyChain. |
| */ |
| GroupManager.prototype.createDKeyDataPromise_ = function |
| (startTimeStamp, endTimeStamp, keyName, privateKeyBlob, certificateKey, |
| useSync) |
| { |
| var name = new Name(this.namespace_); |
| name.append(Encryptor.NAME_COMPONENT_D_KEY); |
| name.append(startTimeStamp).append(endTimeStamp); |
| var data = new Data(name); |
| data.getMetaInfo().setFreshnessPeriod |
| (this.freshnessHours_ * GroupManager.MILLISECONDS_IN_HOUR); |
| var encryptParams = new EncryptParams(EncryptAlgorithmType.RsaOaep); |
| var thisManager = this; |
| |
| return Encryptor.encryptDataPromise |
| (data, privateKeyBlob, keyName, certificateKey, encryptParams, useSync) |
| .catch(function(ex) { |
| // Consolidate errors such as InvalidKeyException. |
| return SyncPromise.reject(SecurityException(new Error |
| ("createDKeyData: Error in encryptData: " + ex))); |
| }) |
| .then(function() { |
| return thisManager.keyChain_.signPromise(data); |
| }); |
| }; |
| |
| GroupManager.MILLISECONDS_IN_HOUR = 3600 * 1000; |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From ndn-group-encrypt src/interval https://github.com/named-data/ndn-group-encrypt |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * An Interval defines a time duration which contains a start timestamp and an |
| * end timestamp. Create an Interval with one of these forms: |
| * Interval(isValid). |
| * Interval(startTime, endTime). |
| * Interval(interval). |
| * @param {boolean} isValid True to create a valid empty interval, false to |
| * create an invalid interval. |
| * @param {number} startTime The start time as milliseconds since Jan 1, 1970 UTC. |
| * The start time must be less than the end time. To create an empty interval |
| * (start time equals end time), use the constructor Interval(true). |
| * @param {number} endTime The end time as milliseconds since Jan 1, 1970 UTC. |
| * @param {Interval} interval The other interval with values to copy. |
| * @note This class is an experimental feature. The API may change. |
| * @constructor |
| */ |
| var Interval = function Interval(value, endTime) |
| { |
| if (typeof value === 'object' && value instanceof Interval) { |
| // Make a copy. |
| this.startTime_ = value.startTime_; |
| this.endTime_ = value.endTime_; |
| this.isValid_ = value.isValid_; |
| } |
| else if (typeof value === 'number') { |
| var startTime = value; |
| |
| if (!(startTime < endTime)) |
| throw new Error("Interval start time must be less than the end time"); |
| |
| this.startTime_ = startTime; |
| this.endTime_ = endTime; |
| this.isValid_ = true; |
| } |
| else { |
| var isValid = (value ? true : false); |
| |
| this.startTime_ = -Number.MAX_VALUE; |
| this.endTime_ = -Number.MAX_VALUE; |
| this.isValid_ = isValid; |
| } |
| }; |
| |
| exports.Interval = Interval; |
| |
| /** |
| * Set this interval to have the same values as the other interval. |
| * @param {Interval} other The other Interval with values to copy. |
| */ |
| Interval.prototype.set = function(other) |
| { |
| this.startTime_ = other.startTime_; |
| this.endTime_ = other.endTime_; |
| this.isValid_ = other.isValid_; |
| }; |
| |
| /** |
| * Check if the time point is in this interval. |
| * @param {number} timePoint The time point to check as milliseconds since |
| * Jan 1, 1970 UTC. |
| * @return {boolean} True if timePoint is in this interval. |
| * @throws Error if this Interval is invalid. |
| */ |
| Interval.prototype.covers = function(timePoint) |
| { |
| if (!this.isValid_) |
| throw new Error("Interval.covers: This Interval is invalid"); |
| |
| if (this.isEmpty()) |
| return false; |
| else |
| return this.startTime_ <= timePoint && timePoint < this.endTime_; |
| }; |
| |
| /** |
| * Set this Interval to the intersection of this and the other interval. |
| * This and the other interval should be valid but either can be empty. |
| * @param {Interval} interval The other Interval to intersect with. |
| * @return {Interval} This Interval. |
| * @throws Error if this Interval or the other interval is invalid. |
| */ |
| Interval.prototype.intersectWith = function(interval) |
| { |
| if (!this.isValid_) |
| throw new Error("Interval.intersectWith: This Interval is invalid"); |
| if (!interval.isValid_) |
| throw new Error("Interval.intersectWith: The other Interval is invalid"); |
| |
| if (this.isEmpty() || interval.isEmpty()) { |
| // If either is empty, the result is empty. |
| this.startTime_ = this.endTime_; |
| return this; |
| } |
| |
| if (this.startTime_ >= interval.endTime_ || this.endTime_ <= interval.startTime_) { |
| // The two intervals don't have an intersection, so the result is empty. |
| this.startTime_ = this.endTime_; |
| return this; |
| } |
| |
| // Get the start time. |
| if (this.startTime_ <= interval.startTime_) |
| this.startTime_ = interval.startTime_; |
| |
| // Get the end time. |
| if (this.endTime_ > interval.endTime_) |
| this.endTime_ = interval.endTime_; |
| |
| return this; |
| }; |
| |
| /** |
| * Set this Interval to the union of this and the other interval. |
| * This and the other interval should be valid but either can be empty. |
| * This and the other interval should have an intersection. (Contiguous |
| * intervals are not allowed.) |
| * @param {Interval} interval The other Interval to union with. |
| * @return {Interval} This Interval. |
| * @throws Error if this Interval or the other interval is invalid, or if the |
| * two intervals do not have an intersection. |
| */ |
| Interval.prototype.unionWith = function(interval) |
| { |
| if (!this.isValid_) |
| throw new Error("Interval.intersectWith: This Interval is invalid"); |
| if (!interval.isValid_) |
| throw new Error("Interval.intersectWith: The other Interval is invalid"); |
| |
| if (this.isEmpty()) { |
| // This interval is empty, so use the other. |
| this.startTime_ = interval.startTime_; |
| this.endTime_ = interval.endTime_; |
| return this; |
| } |
| |
| if (interval.isEmpty()) |
| // The other interval is empty, so keep using this one. |
| return this; |
| |
| if (this.startTime_ >= interval.endTime_ || this.endTime_ <= interval.startTime_) |
| throw new Error |
| ("Interval.unionWith: The two intervals do not have an intersection"); |
| |
| // Get the start time. |
| if (this.startTime_ > interval.startTime_) |
| this.startTime_ = interval.startTime_; |
| |
| // Get the end time. |
| if (this.endTime_ < interval.endTime_) |
| this.endTime_ = interval.endTime_; |
| |
| return this; |
| }; |
| |
| /** |
| * Get the start time. |
| * @return {number} The start time as milliseconds since Jan 1, 1970 UTC. |
| * @throws Error if this Interval is invalid. |
| */ |
| Interval.prototype.getStartTime = function() |
| { |
| if (!this.isValid_) |
| throw new Error("Interval.getStartTime: This Interval is invalid"); |
| return this.startTime_; |
| }; |
| |
| /** |
| * Get the end time. |
| * @return {number} The end time as milliseconds since Jan 1, 1970 UTC. |
| * @throws Error if this Interval is invalid. |
| */ |
| Interval.prototype.getEndTime = function() |
| { |
| if (!this.isValid_) |
| throw new Error("Interval.getEndTime: This Interval is invalid"); |
| return this.endTime_; |
| }; |
| |
| /** |
| * Check if this Interval is valid. |
| * @return {boolean} True if this interval is valid, false if invalid. |
| */ |
| Interval.prototype.isValid = function() { return this.isValid_; }; |
| |
| /** |
| * Check if this Interval is empty. |
| * @return {boolean} True if this Interval is empty (start time equals end time), |
| * false if not. |
| * @throws Error if this Interval is invalid. |
| */ |
| Interval.prototype.isEmpty = function() |
| { |
| if (!this.isValid_) |
| throw new Error("Interval.isEmpty: This Interval is invalid"); |
| return this.startTime_ == this.endTime_; |
| }; |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From ndn-group-encrypt src/producer-db https://github.com/named-data/ndn-group-encrypt |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var SyncPromise = require('../util/sync-promise.js').SyncPromise; |
| |
| /** |
| * ProducerDb is a base class for the storage of keys for the producer. It contains |
| * one table that maps time slots (to the nearest hour) to the content key |
| * created for that time slot. A subclass must implement the methods. For |
| * example, see Sqlite3ProducerDb (for Nodejs) or IndexedDbProducerDb (for the |
| * browser). |
| * @note This class is an experimental feature. The API may change. |
| * @constructor |
| */ |
| var ProducerDb = function ProducerDb() |
| { |
| }; |
| |
| exports.ProducerDb = ProducerDb; |
| |
| /** |
| * Create a new ProducerDb.Error to report an error using ProducerDb |
| * methods, wrapping the given error object. |
| * Call with: throw new ProducerDb.Error(new Error("message")). |
| * @constructor |
| * @param {Error} error The exception created with new Error. |
| */ |
| ProducerDb.Error = function ProducerDbError(error) |
| { |
| if (error) { |
| error.__proto__ = ProducerDb.Error.prototype; |
| return error; |
| } |
| } |
| |
| ProducerDb.Error.prototype = new Error(); |
| ProducerDb.Error.prototype.name = "ProducerDbError"; |
| |
| /** |
| * Check if a content key exists for the hour covering timeSlot. |
| * @param {number} timeSlot The time slot as milliseconds since Jan 1, 1970 UTC. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns true if there is a |
| * content key for timeSlot (else false), or that is rejected with |
| * ProducerDb.Error for a database error. |
| */ |
| ProducerDb.prototype.hasContentKeyPromise = function(timeSlot, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("ProducerDb.hasContentKeyPromise is not implemented")); |
| }; |
| |
| /** |
| * Get the content key for the hour covering timeSlot. |
| * @param {number} timeSlot The time slot as milliseconds since Jan 1, 1970 UTC. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that returns a Blob with the encoded |
| * key, or that is rejected with ProducerDb.Error if there is no key covering |
| * timeSlot, or other database error |
| */ |
| ProducerDb.prototype.getContentKeyPromise = function(timeSlot, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("ProducerDb.getContentKeyPromise is not implemented")); |
| }; |
| |
| /** |
| * Add key as the content key for the hour covering timeSlot. |
| * @param {number} timeSlot The time slot as milliseconds since Jan 1, 1970 UTC. |
| * @param {Blob} key The encoded key. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that fulfills when the key is added, |
| * or that is rejected with ProducerDb.Error if a key for the same hour already |
| * exists in the database, or other database error. |
| */ |
| ProducerDb.prototype.addContentKeyPromise = function |
| (timeSlot, key, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("ProducerDb.addContentKeyPromise is not implemented")); |
| }; |
| |
| /** |
| * Delete the content key for the hour covering timeSlot. If there is no key for |
| * the time slot, do nothing. |
| * @param {number} timeSlot The time slot as milliseconds since Jan 1, 1970 UTC. |
| * @param {boolean} useSync (optional) If true then return a SyncPromise which |
| * is already fulfilled. If omitted or false, this may return a SyncPromise or |
| * an async Promise. |
| * @return {Promise|SyncPromise} A promise that fulfills when the key is deleted |
| * (or there is no such key), or that is rejected with ProducerDb.Error for a |
| * database error. |
| */ |
| ProducerDb.prototype.deleteContentKeyPromise = function(timeSlot, useSync) |
| { |
| return SyncPromise.reject(new Error |
| ("ProducerDb.deleteContentKeyPromise is not implemented")); |
| }; |
| |
| /** |
| * Get the hour-based time slot. |
| * @param {number} timeSlot The time slot as milliseconds since Jan 1, 1970 UTC. |
| * @return {number} The hour-based time slot as hours since Jan 1, 1970 UTC. |
| */ |
| ProducerDb.getFixedTimeSlot = function(timeSlot) |
| { |
| return Math.floor(Math.round(timeSlot) / 3600000.0); |
| }; |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From ndn-group-encrypt src/producer https://github.com/named-data/ndn-group-encrypt |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Name = require('../name.js').Name; /** @ignore */ |
| var Interest = require('../interest.js').Interest; /** @ignore */ |
| var Data = require('../data.js').Data; /** @ignore */ |
| var Link = require('../link.js').Link; /** @ignore */ |
| var NetworkNack = require('../network-nack.js').NetworkNack; /** @ignore */ |
| var Exclude = require('../exclude.js').Exclude; /** @ignore */ |
| var Encryptor = require('./algo/encryptor.js').Encryptor; /** @ignore */ |
| var EncryptParams = require('./algo/encrypt-params.js').EncryptParams; /** @ignore */ |
| var EncryptAlgorithmType = require('./algo/encrypt-params.js').EncryptAlgorithmType; /** @ignore */ |
| var AesKeyParams = require('../security/key-params.js').AesKeyParams; /** @ignore */ |
| var AesAlgorithm = require('./algo/aes-algorithm.js').AesAlgorithm; /** @ignore */ |
| var Schedule = require('./schedule.js').Schedule; /** @ignore */ |
| var EncryptError = require('./encrypt-error.js').EncryptError; /** @ignore */ |
| var NdnCommon = require('../util/ndn-common.js').NdnCommon; /** @ignore */ |
| var SyncPromise = require('../util/sync-promise.js').SyncPromise; |
| |
| /** |
| * A Producer manages content keys used to encrypt a data packet in the |
| * group-based encryption protocol. |
| * Create a Producer to use the given ProducerDb, Face and other values. |
| * |
| * A producer can produce data with a naming convention: |
| * /<prefix>/SAMPLE/<dataType>/[timestamp] |
| * |
| * The produced data packet is encrypted with a content key, |
| * which is stored in the ProducerDb database. |
| * |
| * A producer also needs to produce data containing a content key |
| * encrypted with E-KEYs. A producer can retrieve E-KEYs through the face, |
| * and will re-try for at most repeatAttemps times when E-KEY retrieval fails. |
| * |
| * @param {Name} prefix The producer name prefix. This makes a copy of the Name. |
| * @param {Name} dataType The dataType portion of the producer name. This makes |
| * a copy of the Name. |
| * @param {Face} face The face used to retrieve keys. |
| * @param {KeyChain} keyChain The keyChain used to sign data packets. |
| * @param {ProducerDb} database The ProducerDb database for storing keys. |
| * @param {number} repeatAttempts (optional) The maximum retry for retrieving |
| * keys. If omitted, use a default value of 3. |
| * @param {Link} keyRetrievalLink (optional) The Link object to use in Interests |
| * for key retrieval. This makes a copy of the Link object. If the Link object's |
| * getDelegations().size() is zero, don't use it. If omitted, don't use a Link |
| * object. |
| * @note This class is an experimental feature. The API may change. |
| * @constructor |
| */ |
| var Producer = function Producer |
| (prefix, dataType, face, keyChain, database, repeatAttempts, keyRetrievalLink) |
| { |
| this.face_ = face; |
| this.keyChain_ = keyChain; |
| this.database_ = database; |
| this.maxRepeatAttempts_ = (repeatAttempts == undefined ? 3 : repeatAttempts); |
| this.keyRetrievalLink_ = |
| (keyRetrievalLink == undefined ? Producer.NO_LINK : new Link(keyRetrievalLink)); |
| |
| // The map key is the key name URI string. The value is an object with fields |
| // "keyName" and "keyInfo" where "keyName" is the same Name used for the key |
| // name URI string, and "keyInfo" is the Producer.KeyInfo_. |
| // (Use a string because we can't use the Name object as the key in JavaScript.) |
| // (Also put the original Name in the value because we need to iterate over |
| // eKeyInfo_ and we don't want to rebuild the Name from the name URI string.) |
| this.eKeyInfo_ = {}; |
| // The map key is the time stamp. The value is a Producer.KeyRequest_. |
| this.keyRequests_ = {}; |
| |
| var fixedPrefix = new Name(prefix); |
| var fixedDataType = new Name(dataType); |
| |
| // Fill ekeyInfo_ with all permutations of dataType, including the 'E-KEY' |
| // component of the name. This will be used in createContentKey to send |
| // interests without reconstructing names every time. |
| fixedPrefix.append(Encryptor.NAME_COMPONENT_READ); |
| while (fixedDataType.size() > 0) { |
| var nodeName = new Name(fixedPrefix); |
| nodeName.append(fixedDataType); |
| nodeName.append(Encryptor.NAME_COMPONENT_E_KEY); |
| |
| this.eKeyInfo_[nodeName.toUri()] = |
| { keyName: nodeName, keyInfo: new Producer.KeyInfo_() }; |
| fixedDataType = fixedDataType.getPrefix(-1); |
| } |
| fixedPrefix.append(dataType); |
| this.namespace_ = new Name(prefix); |
| this.namespace_.append(Encryptor.NAME_COMPONENT_SAMPLE); |
| this.namespace_.append(dataType); |
| }; |
| |
| exports.Producer = Producer; |
| |
| /** |
| * Create the content key corresponding to the timeSlot. This first checks if |
| * the content key exists. For an existing content key, this returns the |
| * content key name directly. If the key does not exist, this creates one and |
| * encrypts it using the corresponding E-KEYs. The encrypted content keys are |
| * passed to the onEncryptedKeys callback. |
| * @param {number} timeSlot The time slot as milliseconds since Jan 1, 1970 UTC. |
| * @param {function} onEncryptedKeys If this creates a content key, then this |
| * calls onEncryptedKeys(keys) where keys is a list of encrypted content key |
| * Data packets. If onEncryptedKeys is null, this does not use it. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onContentKeyName This calls onContentKeyName(contentKeyName) |
| * with the content key name for the time slot. If onContentKeyName is null, |
| * this does not use it. (A callback is needed because of async database |
| * operations.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onError (optional) This calls onError(errorCode, message) |
| * for an error, where errorCode is from EncryptError.ErrorCode and message is a |
| * string. If omitted, use a default callback which does nothing. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| */ |
| Producer.prototype.createContentKey = function |
| (timeSlot, onEncryptedKeys, onContentKeyName, onError) |
| { |
| if (!onError) |
| onError = Producer.defaultOnError; |
| |
| var hourSlot = Producer.getRoundedTimeSlot_(timeSlot); |
| |
| // Create the content key name. |
| var contentKeyName = new Name(this.namespace_); |
| contentKeyName.append(Encryptor.NAME_COMPONENT_C_KEY); |
| contentKeyName.append(Schedule.toIsoString(hourSlot)); |
| |
| var contentKeyBits; |
| var thisProducer = this; |
| |
| // Check if we have created the content key before. |
| this.database_.hasContentKeyPromise(timeSlot) |
| .then(function(exists) { |
| if (exists) { |
| if (onContentKeyName != null) |
| onContentKeyName(contentKeyName); |
| return; |
| } |
| |
| // We haven't created the content key. Create one and add it into the database. |
| var aesParams = new AesKeyParams(128); |
| contentKeyBits = AesAlgorithm.generateKey(aesParams).getKeyBits(); |
| thisProducer.database_.addContentKeyPromise(timeSlot, contentKeyBits) |
| .then(function() { |
| // Now we need to retrieve the E-KEYs for content key encryption. |
| var timeCount = Math.round(timeSlot); |
| thisProducer.keyRequests_[timeCount] = |
| new Producer.KeyRequest_(thisProducer.getEKeyInfoSize_()); |
| var keyRequest = thisProducer.keyRequests_[timeCount]; |
| |
| // Check if the current E-KEYs can cover the content key. |
| var timeRange = new Exclude(); |
| Producer.excludeAfter |
| (timeRange, new Name.Component(Schedule.toIsoString(timeSlot))); |
| for (var keyNameUri in thisProducer.eKeyInfo_) { |
| // For each current E-KEY. |
| var entry = thisProducer.eKeyInfo_[keyNameUri]; |
| var keyInfo = entry.keyInfo; |
| if (timeSlot < keyInfo.beginTimeSlot || timeSlot >= keyInfo.endTimeSlot) { |
| // The current E-KEY cannot cover the content key, so retrieve one. |
| keyRequest.repeatAttempts[keyNameUri] = 0; |
| thisProducer.sendKeyInterest_ |
| (new Interest(entry.keyName).setExclude(timeRange).setChildSelector(1), |
| timeSlot, onEncryptedKeys, onError); |
| } |
| else { |
| // The current E-KEY can cover the content key. |
| // Encrypt the content key directly. |
| var eKeyName = new Name(entry.keyName); |
| eKeyName.append(Schedule.toIsoString(keyInfo.beginTimeSlot)); |
| eKeyName.append(Schedule.toIsoString(keyInfo.endTimeSlot)); |
| thisProducer.encryptContentKeyPromise_ |
| (keyInfo.keyBits, eKeyName, timeSlot, onEncryptedKeys, onError); |
| } |
| } |
| |
| if (onContentKeyName != null) |
| onContentKeyName(contentKeyName); |
| }); |
| }); |
| }; |
| |
| /** |
| * Encrypt the given content with the content key that covers timeSlot, and |
| * update the data packet with the encrypted content and an appropriate data |
| * name. |
| * @param {Data} data An empty Data object which is updated. |
| * @param {number} timeSlot The time slot as milliseconds since Jan 1, 1970 UTC. |
| * @param {Blob} content The content to encrypt. |
| * @param {function} onComplete This calls onComplete() when the data packet has |
| * been updated. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onError (optional) This calls onError(errorCode, message) |
| * for an error, where errorCode is from EncryptError.ErrorCode and message is a |
| * string. If omitted, use a default callback which does nothing. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| */ |
| Producer.prototype.produce = function |
| (data, timeSlot, content, onComplete, onError) |
| { |
| if (!onError) |
| onError = Producer.defaultOnError; |
| |
| var thisProducer = this; |
| |
| // Get a content key. |
| this.createContentKey(timeSlot, null, function(contentKeyName) { |
| thisProducer.database_.getContentKeyPromise(timeSlot) |
| .then(function(contentKey) { |
| // Produce data. |
| var dataName = new Name(thisProducer.namespace_); |
| dataName.append(Schedule.toIsoString(timeSlot)); |
| |
| data.setName(dataName); |
| var params = new EncryptParams(EncryptAlgorithmType.AesCbc, 16); |
| return Encryptor.encryptData |
| (data, content, contentKeyName, contentKey, params); |
| }) |
| .then(function() { |
| return thisProducer.keyChain_.signPromise(data); |
| }) |
| .then(function() { |
| try { |
| onComplete(); |
| } catch (ex) { |
| console.log("Error in onComplete: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| }, function(error) { |
| try { |
| onError(EncryptError.ErrorCode.General, "" + error); |
| } catch (ex) { |
| console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| }); |
| }, onError); |
| }; |
| |
| /** |
| * The default onError callback which does nothing. |
| */ |
| Producer.defaultOnError = function(errorCode, message) |
| { |
| // Do nothing. |
| }; |
| |
| Producer.KeyInfo_ = function ProducerKeyInfo() |
| { |
| this.beginTimeSlot = 0.0; |
| this.endTimeSlot = 0.0; |
| this.keyBits = null; // Blob |
| }; |
| |
| Producer.KeyRequest_ = function ProducerKeyRequest(interests) |
| { |
| this.interestCount = interests; // number |
| // The map key is the name URI string. The value is an int count. |
| // (Use a string because we can't use the Name object as the key in JavaScript.) |
| this.repeatAttempts = {}; |
| this.encryptedKeys = []; // of Data |
| }; |
| |
| /** |
| * Round timeSlot to the nearest whole hour, so that we can store content keys |
| * uniformly (by start of the hour). |
| * @param {number} timeSlot The time slot as milliseconds since Jan 1, 1970 UTC. |
| * @return {number} The start of the hour as milliseconds since Jan 1, 1970 UTC. |
| */ |
| Producer.getRoundedTimeSlot_ = function(timeSlot) |
| { |
| return Math.round(Math.floor(Math.round(timeSlot) / 3600000.0) * 3600000.0); |
| } |
| |
| /** |
| * Send an interest with the given name through the face with callbacks to |
| * handleCoveringKey_, handleTimeout_ and handleNetworkNack_. |
| * @param {Interest} interest The interest to send. |
| * @param {number} timeSlot The time slot, passed to handleCoveringKey_, |
| * handleTimeout_ and handleNetworkNack_. |
| * @param {function} onEncryptedKeys The OnEncryptedKeys callback, passed to |
| * handleCoveringKey_, handleTimeout_ and handleNetworkNack_. |
| * @param {function} onError This calls onError(errorCode, message) for an error. |
| */ |
| Producer.prototype.sendKeyInterest_ = function |
| (interest, timeSlot, onEncryptedKeys, onError) |
| { |
| var thisProducer = this; |
| |
| function onKey(interest, data) { |
| thisProducer.handleCoveringKey_ |
| (interest, data, timeSlot, onEncryptedKeys, onError); |
| } |
| |
| function onTimeout(interest) { |
| thisProducer.handleTimeout_(interest, timeSlot, onEncryptedKeys, onError); |
| } |
| |
| function onNetworkNack(interest, networkNack) { |
| thisProducer.handleNetworkNack_ |
| (interest, networkNack, timeSlot, onEncryptedKeys, onError); |
| } |
| |
| var request; |
| if (this.keyRetrievalLink_.getDelegations().size() === 0) |
| // We can use the supplied interest without copying. |
| request = interest; |
| else { |
| // Copy the supplied interest and add the Link. |
| request = new Interest(interest); |
| // This will use a cached encoding if available. |
| request.setLinkWireEncoding(this.keyRetrievalLink_.wireEncode()); |
| } |
| |
| this.face_.expressInterest(request, onKey, onTimeout, onNetworkNack); |
| }; |
| |
| /** |
| * This is called from an expressInterest timeout to update the state of |
| * keyRequest. Re-express the interest if the number of retrials is less than |
| * the max limit. |
| * @param {Interest} interest The timed-out interest. |
| * @param {number} timeSlot The time slot as milliseconds since Jan 1, 1970 UTC. |
| * @param {function} onEncryptedKeys When there are no more interests to process, |
| * this calls onEncryptedKeys(keys) where keys is a list of encrypted content |
| * key Data packets. If onEncryptedKeys is null, this does not use it. |
| * @param {function} onError This calls onError(errorCode, message) for an error. |
| */ |
| Producer.prototype.handleTimeout_ = function |
| (interest, timeSlot, onEncryptedKeys, onError) |
| { |
| var timeCount = Math.round(timeSlot); |
| var keyRequest = this.keyRequests_[timeCount]; |
| |
| var interestName = interest.getName(); |
| var interestNameUri = interestName.toUri(); |
| |
| if (keyRequest.repeatAttempts[interestNameUri] < this.maxRepeatAttempts_) { |
| // Increase the retrial count. |
| ++keyRequest.repeatAttempts[interestNameUri]; |
| this.sendKeyInterest_(interest, timeSlot, onEncryptedKeys, onError); |
| } |
| else |
| // Treat an eventual timeout as a network Nack. |
| this.handleNetworkNack_ |
| (interest, new NetworkNack(), timeSlot, onEncryptedKeys, onError); |
| }; |
| |
| /** |
| * This is called from an expressInterest OnNetworkNack to handle a network |
| * Nack for the E-KEY requested through the Interest. Decrease the outstanding |
| * E-KEY interest count for the C-KEY corresponding to the timeSlot. |
| * @param {Interest} interest The interest given to expressInterest. |
| * @param {NetworkNack} networkNack The returned NetworkNack (unused). |
| * @param {number} timeSlot The time slot as milliseconds since Jan 1, 1970 UTC. |
| * @param {function} onEncryptedKeys When there are no more interests to process, |
| * this calls onEncryptedKeys(keys) where keys is a list of encrypted content |
| * key Data packets. If onEncryptedKeys is null, this does not use it. |
| */ |
| Producer.prototype.handleNetworkNack_ = function |
| (interest, networkNack, timeSlot, onEncryptedKeys, onError) |
| { |
| // We have run out of options.... |
| var timeCount = Math.round(timeSlot); |
| this.updateKeyRequest_ |
| (this.keyRequests_[timeCount], timeCount, onEncryptedKeys); |
| }; |
| |
| /** |
| * Decrease the count of outstanding E-KEY interests for the C-KEY for |
| * timeCount. If the count decreases to 0, invoke onEncryptedKeys. |
| * @param {Producer.KeyRequest_} keyRequest The KeyRequest with the |
| * interestCount to update. |
| * @param {number} timeCount The time count for indexing keyRequests_. |
| * @param {function} onEncryptedKeys When there are no more interests to |
| * process, this calls onEncryptedKeys(keys) where keys is a list of encrypted |
| * content key Data packets. If onEncryptedKeys is null, this does not use it. |
| */ |
| Producer.prototype.updateKeyRequest_ = function |
| (keyRequest, timeCount, onEncryptedKeys) |
| { |
| --keyRequest.interestCount; |
| if (keyRequest.interestCount == 0 && onEncryptedKeys != null) { |
| try { |
| onEncryptedKeys(keyRequest.encryptedKeys); |
| } catch (ex) { |
| console.log("Error in onEncryptedKeys: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| delete this.keyRequests_[timeCount]; |
| } |
| }; |
| |
| /** |
| * This is called from an expressInterest OnData to check that the encryption |
| * key contained in data fits the timeSlot. This sends a refined interest if |
| * required. |
| * @param {Interest} interest The interest given to expressInterest. |
| * @param {Data} data The fetched Data packet. |
| * @param {number} timeSlot The time slot as milliseconds since Jan 1, 1970 UTC. |
| * @param {function} onEncryptedKeys When there are no more interests to process, |
| * this calls onEncryptedKeys(keys) where keys is a list of encrypted content |
| * key Data packets. If onEncryptedKeys is null, this does not use it. |
| * @param {function} onError This calls onError(errorCode, message) for an error. |
| */ |
| Producer.prototype.handleCoveringKey_ = function |
| (interest, data, timeSlot, onEncryptedKeys, onError) |
| { |
| var timeCount = Math.round(timeSlot); |
| var keyRequest = this.keyRequests_[timeCount]; |
| |
| var interestName = interest.getName(); |
| var interestNameUrl = interestName.toUri(); |
| var keyName = data.getName(); |
| |
| var begin = Schedule.fromIsoString |
| (keyName.get(Producer.START_TIME_STAMP_INDEX).getValue().toString()); |
| var end = Schedule.fromIsoString |
| (keyName.get(Producer.END_TIME_STAMP_INDEX).getValue().toString()); |
| |
| if (timeSlot >= end) { |
| // If the received E-KEY covers some earlier period, try to retrieve an |
| // E-KEY covering a later one. |
| var timeRange = new Exclude(interest.getExclude()); |
| Producer.excludeBefore(timeRange, keyName.get(Producer.START_TIME_STAMP_INDEX)); |
| keyRequest.repeatAttempts[interestNameUrl] = 0; |
| this.sendKeyInterest_ |
| (new Interest(interestName).setExclude(timeRange).setChildSelector(1), |
| timeSlot, onEncryptedKeys, onError); |
| } |
| else { |
| // If the received E-KEY covers the content key, encrypt the content. |
| var encryptionKey = data.getContent(); |
| var thisProducer = this; |
| this.encryptContentKeyPromise_ |
| (encryptionKey, keyName, timeSlot, onEncryptedKeys, onError) |
| .then(function(success) { |
| if (success) { |
| var keyInfo = thisProducer.eKeyInfo_[interestNameUrl].keyInfo; |
| keyInfo.beginTimeSlot = begin; |
| keyInfo.endTimeSlot = end; |
| keyInfo.keyBits = encryptionKey; |
| } |
| }); |
| } |
| }; |
| |
| /** |
| * Get the content key from the database_ and encrypt it for the timeSlot |
| * using encryptionKey. |
| * @param {Blob} encryptionKey The encryption key value. |
| * @param {Name} eKeyName The key name for the EncryptedContent. |
| * @param {number} timeSlot The time slot as milliseconds since Jan 1, 1970 UTC. |
| * @param {function} onEncryptedKeys When there are no more interests to process, |
| * this calls onEncryptedKeys(keys) where keys is a list of encrypted content |
| * key Data packets. If onEncryptedKeys is null, this does not use it. |
| * @param {function} onError This calls onError(errorCode, message) for an error. |
| * @return {Promise} A promise that returns true if encryption succeeds, |
| * otherwise false. |
| */ |
| Producer.prototype.encryptContentKeyPromise_ = function |
| (encryptionKey, eKeyName, timeSlot, onEncryptedKeys, onError) |
| { |
| var timeCount = Math.round(timeSlot); |
| var keyRequest = this.keyRequests_[timeCount]; |
| |
| var keyName = new Name(this.namespace_); |
| keyName.append(Encryptor.NAME_COMPONENT_C_KEY); |
| keyName.append(Schedule.toIsoString(Producer.getRoundedTimeSlot_(timeSlot))); |
| |
| var cKeyData; |
| var thisProducer = this; |
| |
| return this.database_.getContentKeyPromise(timeSlot) |
| .then(function(contentKey) { |
| cKeyData = new Data(); |
| cKeyData.setName(keyName); |
| var params = new EncryptParams(EncryptAlgorithmType.RsaOaep); |
| return Encryptor.encryptDataPromise |
| (cKeyData, contentKey, eKeyName, encryptionKey, params); |
| }) |
| .then(function() { |
| return SyncPromise.resolve(true); |
| }, function(error) { |
| try { |
| onError(EncryptError.ErrorCode.EncryptionFailure, |
| "encryptData failed: " + error); |
| } catch (ex) { |
| console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| return SyncPromise.resolve(false); |
| }) |
| .then(function(success) { |
| if (success) { |
| return thisProducer.keyChain_.signPromise(cKeyData) |
| .then(function() { |
| keyRequest.encryptedKeys.push(cKeyData); |
| thisProducer.updateKeyRequest_(keyRequest, timeCount, onEncryptedKeys); |
| return SyncPromise.resolve(true); |
| }); |
| } |
| else |
| return SyncPromise.resolve(false); |
| }); |
| }; |
| |
| Producer.prototype.getEKeyInfoSize_ = function() |
| { |
| // Note: This is really a method to find the key count in any object, but we |
| // don't want to claim that it is a tested and general utility method. |
| var size = 0; |
| for (key in this.eKeyInfo_) { |
| if (this.eKeyInfo_.hasOwnProperty(key)) |
| ++size; |
| } |
| |
| return size; |
| }; |
| |
| // TODO: Move this to be the main representation inside the Exclude object. |
| /** |
| * Create a new ExcludeEntry. |
| * @param {Name.Component} component |
| * @param {boolean} anyFollowsComponent |
| */ |
| Producer.ExcludeEntry = function ExcludeEntry(component, anyFollowsComponent) |
| { |
| this.component_ = component; |
| this.anyFollowsComponent_ = anyFollowsComponent; |
| }; |
| |
| /** |
| * Create a list of ExcludeEntry from the Exclude object. |
| * @param {Exclude} exclude The Exclude object to read. |
| * @return {Array<ExcludeEntry>} A new array of ExcludeEntry. |
| */ |
| Producer.getExcludeEntries = function(exclude) |
| { |
| var entries = []; |
| |
| for (var i = 0; i < exclude.size(); ++i) { |
| if (exclude.get(i) == Exclude.ANY) { |
| if (entries.length == 0) |
| // Add a "beginning ANY". |
| entries.push(new Producer.ExcludeEntry(new Name.Component(), true)); |
| else |
| // Set anyFollowsComponent of the final component. |
| entries[entries.length - 1].anyFollowsComponent_ = true; |
| } |
| else |
| entries.push(new Producer.ExcludeEntry(exclude.get(i), false)); |
| } |
| |
| return entries; |
| }; |
| |
| /** |
| * Set the Exclude object from the array of ExcludeEntry. |
| * @param {Exclude} exclude The Exclude object to update. |
| * @param {Array<ExcludeEntry>} entries The array of ExcludeEntry. |
| */ |
| Producer.setExcludeEntries = function(exclude, entries) |
| { |
| exclude.clear(); |
| |
| for (var i = 0; i < entries.length; ++i) { |
| var entry = entries[i]; |
| |
| if (i == 0 && entry.component_.getValue().size() == 0 && |
| entry.anyFollowsComponent_) |
| // This is a "beginning ANY". |
| exclude.appendAny(); |
| else { |
| exclude.appendComponent(entry.component_); |
| if (entry.anyFollowsComponent_) |
| exclude.appendAny(); |
| } |
| } |
| }; |
| |
| /** |
| * Get the latest entry in the array whose component_ is less than or equal to |
| * component. |
| * @param {Array<ExcludeEntry>} entries The array of ExcludeEntry. |
| * @param {Name.Component} component The component to compare. |
| * @return {number} The index of the found entry, or -1 if not found. |
| */ |
| Producer.findEntryBeforeOrAt = function(entries, component) |
| { |
| var i = entries.length - 1; |
| while (i >= 0) { |
| if (entries[i].component_.compare(component) <= 0) |
| break; |
| --i; |
| } |
| |
| return i; |
| }; |
| |
| /** |
| * Exclude all components in the range beginning at "from". |
| * @param {Exclude} exclude The Exclude object to update. |
| * @param {Name.Component} from The first component in the exclude range. |
| */ |
| Producer.excludeAfter = function(exclude, from) |
| { |
| var entries = Producer.getExcludeEntries(exclude); |
| |
| var iNewFrom; |
| var iFoundFrom = Producer.findEntryBeforeOrAt(entries, from); |
| if (iFoundFrom < 0) { |
| // There is no entry before "from" so insert at the beginning. |
| entries.splice(0, 0, new Producer.ExcludeEntry(from, true)); |
| iNewFrom = 0; |
| } |
| else { |
| var foundFrom = entries[iFoundFrom]; |
| |
| if (!foundFrom.anyFollowsComponent_) { |
| if (foundFrom.component_.equals(from)) { |
| // There is already an entry with "from", so just set the "ANY" flag. |
| foundFrom.anyFollowsComponent_ = true; |
| iNewFrom = iFoundFrom; |
| } |
| else { |
| // Insert following the entry before "from". |
| entries.splice(iFoundFrom + 1, 0, new Producer.ExcludeEntry(from, true)); |
| iNewFrom = iFoundFrom + 1; |
| } |
| } |
| else |
| // The entry before "from" already has an "ANY" flag, so do nothing. |
| iNewFrom = iFoundFrom; |
| } |
| |
| // Remove intermediate entries since they are inside the range. |
| var iRemoveBegin = iNewFrom + 1; |
| var nRemoveNeeded = entries.length - iRemoveBegin; |
| entries.splice(iRemoveBegin, nRemoveNeeded); |
| |
| Producer.setExcludeEntries(exclude, entries); |
| }; |
| |
| /** |
| * Exclude all components in the range ending at "to". |
| * @param {Exclude} exclude The Exclude object to update. |
| * @param {Name.Component} to The last component in the exclude range. |
| */ |
| Producer.excludeBefore = function(exclude, to) |
| { |
| Producer.excludeRange(exclude, new Name.Component(), to); |
| }; |
| |
| /** |
| * Exclude all components in the range beginning at "from" and ending at "to". |
| * @param {Exclude} exclude The Exclude object to update. |
| * @param {Name.Component} from The first component in the exclude range. |
| * @param {Name.Component} to The last component in the exclude range. |
| */ |
| Producer.excludeRange = function(exclude, from, to) |
| { |
| if (from.compare(to) >= 0) { |
| if (from.compare(to) == 0) |
| throw new Error |
| ("excludeRange: from == to. To exclude a single component, sue excludeOne."); |
| else |
| throw new Error |
| ("excludeRange: from must be less than to. Invalid range: [" + |
| from.toEscapedString() + ", " + to.toEscapedString() + "]"); |
| } |
| |
| var entries = Producer.getExcludeEntries(exclude); |
| |
| var iNewFrom; |
| var iFoundFrom = Producer.findEntryBeforeOrAt(entries, from); |
| if (iFoundFrom < 0) { |
| // There is no entry before "from" so insert at the beginning. |
| entries.splice(0, 0, new Producer.ExcludeEntry(from, true)); |
| iNewFrom = 0; |
| } |
| else { |
| var foundFrom = entries[iFoundFrom]; |
| |
| if (!foundFrom.anyFollowsComponent_) { |
| if (foundFrom.component_.equals(from)) { |
| // There is already an entry with "from", so just set the "ANY" flag. |
| foundFrom.anyFollowsComponent_ = true; |
| iNewFrom = iFoundFrom; |
| } |
| else { |
| // Insert following the entry before "from". |
| entries.splice(iFoundFrom + 1, 0, new Producer.ExcludeEntry(from, true)); |
| iNewFrom = iFoundFrom + 1; |
| } |
| } |
| else |
| // The entry before "from" already has an "ANY" flag, so do nothing. |
| iNewFrom = iFoundFrom; |
| } |
| |
| // We have at least one "from" before "to", so we know this will find an entry. |
| var iFoundTo = Producer.findEntryBeforeOrAt(entries, to); |
| var foundTo = entries[iFoundTo]; |
| if (iFoundTo == iNewFrom) |
| // Insert the "to" immediately after the "from". |
| entries.splice(iNewFrom + 1, 0, new Producer.ExcludeEntry(to, false)); |
| else { |
| var iRemoveEnd; |
| if (!foundTo.anyFollowsComponent_) { |
| if (foundTo.component_.equals(to)) |
| // The "to" entry already exists. Remove up to it. |
| iRemoveEnd = iFoundTo; |
| else { |
| // Insert following the previous entry, which will be removed. |
| entries.splice(iFoundTo + 1, 0, new Producer.ExcludeEntry(to, false)); |
| iRemoveEnd = iFoundTo + 1; |
| } |
| } |
| else |
| // "to" follows a component which is already followed by "ANY", meaning |
| // the new range now encompasses it, so remove the component. |
| iRemoveEnd = iFoundTo + 1; |
| |
| // Remove intermediate entries since they are inside the range. |
| var iRemoveBegin = iNewFrom + 1; |
| var nRemoveNeeded = iRemoveEnd - iRemoveBegin; |
| entries.splice(iRemoveBegin, nRemoveNeeded); |
| } |
| |
| Producer.setExcludeEntries(exclude, entries); |
| }; |
| |
| Producer.START_TIME_STAMP_INDEX = -2; |
| Producer.END_TIME_STAMP_INDEX = -1; |
| Producer.NO_LINK = new Link(); |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From ndn-group-encrypt src/repetitive-interval https://github.com/named-data/ndn-group-encrypt |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Interval = require('./interval.js').Interval; |
| |
| /** |
| * A RepetitiveInterval is an advanced interval which can repeat and can be used |
| * to find a simple Interval that a time point falls in. Create a |
| * RepetitiveInterval with one of these forms: |
| * RepetitiveInterval() A RepetitiveInterval with one day duration, non-repeating.. |
| * RepetitiveInterval(startDate, endDate, intervalStartHour, intervalEndHour, nRepeats, repeatUnit). |
| * RepetitiveInterval(repetitiveInterval). |
| * @param {number} startDate The start date as milliseconds since Jan 1, 1970 UTC. |
| * startDate must be earlier than or same as endDate. Or if repeatUnit is |
| * RepetitiveInterval.RepeatUnit.NONE, then it must equal endDate. |
| * @param {number} endDate The end date as milliseconds since Jan 1, 1970 UTC. |
| * @param {number} intervalStartHour The start hour in the day, from 0 to 23. |
| * intervalStartHour must be less than intervalEndHour. |
| * @param {number} intervalEndHour The end hour in the day from 1 to 24. |
| * @param {number} nRepeats (optional) Repeat the interval nRepeats repetitions, |
| * every unit, until endDate. If ommitted, use 0. |
| * @param {number} repeatUnit (optional) The unit of the repetition, from |
| * RepetitiveInterval.RepeatUnit. If ommitted, use NONE. If this is NONE or |
| * ommitted, then startDate must equal endDate. |
| * @note This class is an experimental feature. The API may change. |
| * @constructor |
| */ |
| var RepetitiveInterval = function RepetitiveInterval |
| (startDate, endDate, intervalStartHour, intervalEndHour, nRepeats, repeatUnit) |
| { |
| if (typeof startDate === 'object' && startDate instanceof RepetitiveInterval) { |
| // Make a copy. |
| repetitiveInterval = startDate; |
| |
| this.startDate_ = repetitiveInterval.startDate_; |
| this.endDate_ = repetitiveInterval.endDate_; |
| this.intervalStartHour_ = repetitiveInterval.intervalStartHour_; |
| this.intervalEndHour_ = repetitiveInterval.intervalEndHour_; |
| this.nRepeats_ = repetitiveInterval.nRepeats_; |
| this.repeatUnit_ = repetitiveInterval.repeatUnit_; |
| } |
| else if (typeof startDate === 'number') { |
| if (nRepeats == undefined) |
| nRepeats = 0; |
| if (repeatUnit == undefined) |
| repeatUnit = RepetitiveInterval.RepeatUnit.NONE; |
| |
| this.startDate_ = RepetitiveInterval.toDateOnlyMilliseconds_(startDate); |
| this.endDate_ = RepetitiveInterval.toDateOnlyMilliseconds_(endDate); |
| this.intervalStartHour_ = Math.round(intervalStartHour); |
| this.intervalEndHour_ = Math.round(intervalEndHour); |
| this.nRepeats_ = Math.round(nRepeats); |
| this.repeatUnit_ = repeatUnit; |
| |
| // Validate. |
| if (!(this.intervalStartHour_ < this.intervalEndHour_)) |
| throw new Error("ReptitiveInterval: startHour must be less than endHour"); |
| if (!(this.startDate_ <= this.endDate_)) |
| throw new Error |
| ("ReptitiveInterval: startDate must be earlier than or same as endDate"); |
| if (!(this.intervalStartHour_ >= 0)) |
| throw new Error("ReptitiveInterval: intervalStartHour must be non-negative"); |
| if (!(this.intervalEndHour_ >= 1 && this.intervalEndHour_ <= 24)) |
| throw new Error("ReptitiveInterval: intervalEndHour must be from 1 to 24"); |
| if (this.repeatUnit_ == RepetitiveInterval.RepeatUnit.NONE) { |
| if (!(this.startDate_ == this.endDate_)) |
| throw new Error |
| ("ReptitiveInterval: With RepeatUnit.NONE, startDate must equal endDate"); |
| } |
| } |
| else { |
| // The default constructor. |
| this.startDate_ = -Number.MAX_VALUE; |
| this.endDate_ = -Number.MAX_VALUE; |
| this.intervalStartHour_ = 0; |
| this.intervalEndHour_ = 24; |
| this.nRepeats_ = 0; |
| this.repeatUnit_ = RepetitiveInterval.RepeatUnit.NONE; |
| } |
| }; |
| |
| exports.RepetitiveInterval = RepetitiveInterval; |
| |
| RepetitiveInterval.RepeatUnit = { |
| NONE: 0, |
| DAY: 1, |
| MONTH: 2, |
| YEAR: 3 |
| }; |
| |
| /** |
| * Get an interval that covers the time point. If there is no interval |
| * covering the time point, this returns false for isPositive and returns a |
| * negative interval. |
| * @param {number} timePoint The time point as milliseconds since Jan 1, 1970 UTC. |
| * @return {object} An associative array with fields |
| * (isPositive, interval) where |
| * isPositive is true if the returned interval is |
| * positive or false if negative, and interval is the Interval covering the time |
| * point or a negative interval if not found. |
| */ |
| RepetitiveInterval.prototype.getInterval = function(timePoint) |
| { |
| var isPositive; |
| var startTime; |
| var endTime; |
| |
| if (!this.hasIntervalOnDate_(timePoint)) { |
| // There is no interval on the date of timePoint. |
| startTime = RepetitiveInterval.toDateOnlyMilliseconds_(timePoint); |
| endTime = RepetitiveInterval.toDateOnlyMilliseconds_(timePoint) + |
| 24 * RepetitiveInterval.MILLISECONDS_IN_HOUR; |
| isPositive = false; |
| } |
| else { |
| // There is an interval on the date of timePoint. |
| startTime = RepetitiveInterval.toDateOnlyMilliseconds_(timePoint) + |
| this.intervalStartHour_ * RepetitiveInterval.MILLISECONDS_IN_HOUR; |
| endTime = RepetitiveInterval.toDateOnlyMilliseconds_(timePoint) + |
| this.intervalEndHour_ * RepetitiveInterval.MILLISECONDS_IN_HOUR; |
| |
| // check if in the time duration |
| if (timePoint < startTime) { |
| endTime = startTime; |
| startTime = RepetitiveInterval.toDateOnlyMilliseconds_(timePoint); |
| isPositive = false; |
| } |
| else if (timePoint > endTime) { |
| startTime = endTime; |
| endTime = RepetitiveInterval.toDateOnlyMilliseconds_(timePoint) + |
| RepetitiveInterval.MILLISECONDS_IN_DAY; |
| isPositive = false; |
| } |
| else |
| isPositive = true; |
| } |
| |
| return { isPositive: isPositive, interval: new Interval(startTime, endTime) }; |
| }; |
| |
| /** |
| * Compare this to the other RepetitiveInterval. |
| * @param {RepetitiveInterval} other The other RepetitiveInterval to compare to. |
| * @return {number} -1 if this is less than the other, 1 if greater and 0 if equal. |
| */ |
| RepetitiveInterval.prototype.compare = function(other) |
| { |
| if (this.startDate_ < other.startDate_) |
| return -1; |
| if (this.startDate_ > other.startDate_) |
| return 1; |
| |
| if (this.endDate_ < other.endDate_) |
| return -1; |
| if (this.endDate_ > other.endDate_) |
| return 1; |
| |
| if (this.intervalStartHour_ < other.intervalStartHour_) |
| return -1; |
| if (this.intervalStartHour_ > other.intervalStartHour_) |
| return 1; |
| |
| if (this.intervalEndHour_ < other.intervalEndHour_) |
| return -1; |
| if (this.intervalEndHour_ > other.intervalEndHour_) |
| return 1; |
| |
| if (this.nRepeats_ < other.nRepeats_) |
| return -1; |
| if (this.nRepeats_ > other.nRepeats_) |
| return 1; |
| |
| if (this.repeatUnit_ < other.repeatUnit_) |
| return -1; |
| if (this.repeatUnit_ > other.repeatUnit_) |
| return 1; |
| |
| return 0; |
| }; |
| |
| /** |
| * Get the start date. |
| * @return {number} The start date as milliseconds since Jan 1, 1970 UTC. |
| */ |
| RepetitiveInterval.prototype.getStartDate = function() |
| { |
| return this.startDate_; |
| }; |
| |
| /** |
| * Get the end date. |
| * @return {number} The end date as milliseconds since Jan 1, 1970 UTC. |
| */ |
| RepetitiveInterval.prototype.getEndDate = function() |
| { |
| return this.endDate_; |
| }; |
| |
| /** |
| * Get the interval start hour. |
| * @return {number} The interval start hour. |
| */ |
| RepetitiveInterval.prototype.getIntervalStartHour = function() |
| { |
| return this.intervalStartHour_; |
| } |
| |
| /** |
| * Get the interval end hour. |
| * @return {number} The interval end hour. |
| */ |
| RepetitiveInterval.prototype.getIntervalEndHour = function() |
| { |
| return this.intervalEndHour_; |
| }; |
| |
| /** |
| * Get the number of repeats. |
| * @return {number} The number of repeats. |
| */ |
| RepetitiveInterval.prototype.getNRepeats = function() |
| { |
| return this.nRepeats_; |
| }; |
| |
| /** |
| * Get the repeat unit. |
| * @return {number} The repeat unit, from RepetitiveInterval.RepeatUnit. |
| */ |
| RepetitiveInterval.prototype.getRepeatUnit = function() |
| { |
| return this.repeatUnit_; |
| }; |
| |
| /** |
| * Check if the date of the time point is in any interval. |
| * @param {number} timePoint The time point as milliseconds since Jan 1, 1970 UTC. |
| * @return {boolean} True if the date of the time point is in any interval. |
| */ |
| RepetitiveInterval.prototype.hasIntervalOnDate_ = function(timePoint) |
| { |
| var timePointDateMilliseconds = RepetitiveInterval.toDateOnlyMilliseconds_(timePoint); |
| |
| if (timePointDateMilliseconds < this.startDate_ || |
| timePointDateMilliseconds > this.endDate_) |
| return false; |
| |
| if (this.repeatUnit_ == RepetitiveInterval.RepeatUnit.NONE) |
| return true; |
| else if (this.repeatUnit_ == RepetitiveInterval.RepeatUnit.DAY) { |
| var durationDays = (timePointDateMilliseconds - this.startDate_) / |
| RepetitiveInterval.MILLISECONDS_IN_DAY; |
| if (durationDays % this.nRepeats_ == 0) |
| return true; |
| } |
| else { |
| var timePointDate = new Date(timePointDateMilliseconds); |
| var startDate = new Date(this.startDate_); |
| |
| if (this.repeatUnit_ == RepetitiveInterval.RepeatUnit.MONTH && |
| timePointDate.getUTCDate() == startDate.getUTCDate()) { |
| var yearDifference = |
| timePointDate.getUTCFullYear() - startDate.getUTCFullYear(); |
| var monthDifference = 12 * yearDifference + |
| timePointDate.getUTCMonth() - startDate.getUTCMonth(); |
| if (monthDifference % this.nRepeats_ == 0) |
| return true; |
| } |
| else if (this.repeatUnit_ == RepetitiveInterval.RepeatUnit.YEAR && |
| timePointDate.getUTCDate() == startDate.getUTCDate() && |
| timePointDate.getUTCMonth() == startDate.getUTCMonth()) { |
| var difference = timePointDate.getUTCFullYear() - startDate.getUTCFullYear(); |
| if (difference % this.nRepeats_ == 0) |
| return true; |
| } |
| } |
| |
| return false; |
| }; |
| |
| /** |
| * Return a time point on the beginning of the date (without hours, minutes, etc.) |
| * @param {number} timePoint The time point as milliseconds since Jan 1, 1970 UTC. |
| * @return {number} A time point as milliseconds since Jan 1, 1970 UTC. |
| */ |
| RepetitiveInterval.toDateOnlyMilliseconds_ = function(timePoint) |
| { |
| var result = Math.round(timePoint); |
| result -= result % RepetitiveInterval.MILLISECONDS_IN_DAY; |
| return result; |
| }; |
| |
| RepetitiveInterval.MILLISECONDS_IN_HOUR = 3600 * 1000; |
| RepetitiveInterval.MILLISECONDS_IN_DAY = 24 * 3600 * 1000; |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From ndn-group-encrypt src/schedule https://github.com/named-data/ndn-group-encrypt |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Interval = require('./interval.js').Interval; /** @ignore */ |
| var RepetitiveInterval = require('./repetitive-interval.js').RepetitiveInterval; /** @ignore */ |
| var Tlv = require('../encoding/tlv/tlv.js').Tlv; /** @ignore */ |
| var TlvEncoder = require('../encoding/tlv/tlv-encoder.js').TlvEncoder; /** @ignore */ |
| var TlvDecoder = require('../encoding/tlv/tlv-decoder.js').TlvDecoder; /** @ignore */ |
| var Blob = require('../util/blob.js').Blob; |
| |
| /** |
| * Schedule is used to manage the times when a member can access data using two |
| * sets of RepetitiveInterval as follows. whiteIntervalList is an ordered |
| * set for the times a member is allowed to access to data, and |
| * blackIntervalList is for the times a member is not allowed. |
| * Create a Schedule with one of these forms: |
| * Schedule() A Schedule with empty whiteIntervalList and blackIntervalList. |
| * Schedule(schedule). A copy of the given schedule. |
| * @note This class is an experimental feature. The API may change. |
| * @constructor |
| */ |
| var Schedule = function Schedule(value) |
| { |
| if (typeof value === 'object' && value instanceof Schedule) { |
| // Make a copy. |
| var schedule = value; |
| |
| // RepetitiveInterval is immutable, so we don't need to make a deep copy. |
| this.whiteIntervalList_ = schedule.whiteIntervalList_.slice(0); |
| this.blackIntervalList_ = schedule.blackIntervalList_.slice(0); |
| } |
| else { |
| // The default constructor. |
| this.whiteIntervalList_ = []; |
| this.blackIntervalList_ = []; |
| } |
| }; |
| |
| exports.Schedule = Schedule; |
| |
| /** |
| * Add the repetitiveInterval to the whiteIntervalList. |
| * @param {RepetitiveInterval} repetitiveInterval The RepetitiveInterval to add. |
| * If the list already contains the same RepetitiveInterval, this does nothing. |
| * @return {Schedule} This Schedule so you can chain calls to add. |
| */ |
| Schedule.prototype.addWhiteInterval = function(repetitiveInterval) |
| { |
| // RepetitiveInterval is immutable, so we don't need to make a copy. |
| Schedule.sortedSetAdd_(this.whiteIntervalList_, repetitiveInterval); |
| return this; |
| }; |
| |
| /** |
| * Add the repetitiveInterval to the blackIntervalList. |
| * @param {RepetitiveInterval} repetitiveInterval The RepetitiveInterval to add. |
| * If the list already contains the same RepetitiveInterval, this does nothing. |
| * @return {Schedule} This Schedule so you can chain calls to add. |
| */ |
| Schedule.prototype.addBlackInterval = function(repetitiveInterval) |
| { |
| // RepetitiveInterval is immutable, so we don't need to make a copy. |
| Schedule.sortedSetAdd_(this.blackIntervalList_, repetitiveInterval); |
| return this; |
| }; |
| |
| /** |
| * Get the interval that covers the time stamp. This iterates over the two |
| * repetitive interval sets and find the shortest interval that allows a group |
| * member to access the data. If there is no interval covering the time stamp, |
| * this returns false for isPositive and a negative interval. |
| * @param {number} timeStamp The time stamp as milliseconds since Jan 1, 1970 UTC. |
| * @return {object} An associative array with fields |
| * (isPositive, interval) where |
| * isPositive is true if the returned interval is positive or false if negative, |
| * and interval is the Interval covering the time stamp, or a negative interval |
| * if not found. |
| */ |
| Schedule.prototype.getCoveringInterval = function(timeStamp) |
| { |
| var blackPositiveResult = new Interval(true); |
| var whitePositiveResult = new Interval(true); |
| |
| var blackNegativeResult = new Interval(); |
| var whiteNegativeResult = new Interval(); |
| |
| // Get the black result. |
| Schedule.calculateIntervalResult_ |
| (this.blackIntervalList_, timeStamp, blackPositiveResult, blackNegativeResult); |
| |
| // If the black positive result is not empty, then isPositive must be false. |
| if (!blackPositiveResult.isEmpty()) |
| return { isPositive: false, interval: blackPositiveResult }; |
| |
| // Get the whiteResult. |
| Schedule.calculateIntervalResult_ |
| (this.whiteIntervalList_, timeStamp, whitePositiveResult, whiteNegativeResult); |
| |
| if (whitePositiveResult.isEmpty() && !whiteNegativeResult.isValid()) { |
| // There is no white interval covering the time stamp. |
| // Return false and a 24-hour interval. |
| var timeStampDateOnly = |
| RepetitiveInterval.toDateOnlyMilliseconds_(timeStamp); |
| return { isPositive: false, |
| interval: new Interval |
| (timeStampDateOnly, |
| timeStampDateOnly + RepetitiveInterval.MILLISECONDS_IN_DAY) }; |
| } |
| |
| if (!whitePositiveResult.isEmpty()) { |
| // There is white interval covering the time stamp. |
| // Return true and calculate the intersection. |
| if (blackNegativeResult.isValid()) |
| return { isPositive: true, |
| interval: whitePositiveResult.intersectWith(blackNegativeResult) }; |
| else |
| return { isPositive: true, interval: whitePositiveResult }; |
| } |
| else |
| // There is no white interval covering the time stamp. |
| // Return false. |
| return { isPositive: false, interval: whiteNegativeResult }; |
| }; |
| |
| /** |
| * Encode this Schedule. |
| * @return {Blob} The encoded buffer. |
| */ |
| Schedule.prototype.wireEncode = function() |
| { |
| // For now, don't use WireFormat and hardcode to use TLV since the encoding |
| // doesn't go out over the wire, only into the local SQL database. |
| var encoder = new TlvEncoder(256); |
| var saveLength = encoder.getLength(); |
| |
| // Encode backwards. |
| // Encode the blackIntervalList. |
| var saveLengthForList = encoder.getLength(); |
| for (var i = this.blackIntervalList_.length - 1; i >= 0; i--) |
| Schedule.encodeRepetitiveInterval_(this.blackIntervalList_[i], encoder); |
| encoder.writeTypeAndLength |
| (Tlv.Encrypt_BlackIntervalList, encoder.getLength() - saveLengthForList); |
| |
| // Encode the whiteIntervalList. |
| saveLengthForList = encoder.getLength(); |
| for (var i = this.whiteIntervalList_.length - 1; i >= 0; i--) |
| Schedule.encodeRepetitiveInterval_(this.whiteIntervalList_[i], encoder); |
| encoder.writeTypeAndLength |
| (Tlv.Encrypt_WhiteIntervalList, encoder.getLength() - saveLengthForList); |
| |
| encoder.writeTypeAndLength |
| (Tlv.Encrypt_Schedule, encoder.getLength() - saveLength); |
| |
| return new Blob(encoder.getOutput(), false); |
| }; |
| |
| /** |
| * Decode the input and update this Schedule object. |
| * @param {Blob|Buffer} input The input buffer to decode. For Buffer, this reads |
| * from position() to limit(), but does not change the position. |
| * @throws DecodingException For invalid encoding. |
| */ |
| Schedule.prototype.wireDecode = function(input) |
| { |
| // If input is a blob, get its buf(). |
| var decodeBuffer = typeof input === 'object' && input instanceof Blob ? |
| input.buf() : input; |
| |
| // For now, don't use WireFormat and hardcode to use TLV since the encoding |
| // doesn't go out over the wire, only into the local SQL database. |
| var decoder = new TlvDecoder(decodeBuffer); |
| |
| var endOffset = decoder.readNestedTlvsStart(Tlv.Encrypt_Schedule); |
| |
| // Decode the whiteIntervalList. |
| this.whiteIntervalList_ = []; |
| var listEndOffset = decoder.readNestedTlvsStart(Tlv.Encrypt_WhiteIntervalList); |
| while (decoder.getOffset() < listEndOffset) |
| Schedule.sortedSetAdd_ |
| (this.whiteIntervalList_, Schedule.decodeRepetitiveInterval_(decoder)); |
| decoder.finishNestedTlvs(listEndOffset); |
| |
| // Decode the blackIntervalList. |
| this.blackIntervalList_ = []; |
| listEndOffset = decoder.readNestedTlvsStart(Tlv.Encrypt_BlackIntervalList); |
| while (decoder.getOffset() < listEndOffset) |
| Schedule.sortedSetAdd_ |
| (this.blackIntervalList_, Schedule.decodeRepetitiveInterval_(decoder)); |
| decoder.finishNestedTlvs(listEndOffset); |
| |
| decoder.finishNestedTlvs(endOffset); |
| }; |
| |
| /** |
| * Insert element into the list, sorted using element.compare(). If it is a |
| * duplicate of an existing list element, don't add it. |
| */ |
| Schedule.sortedSetAdd_ = function(list, element) |
| { |
| // Find the index of the first element where it is not less than element. |
| var i = 0; |
| while (i < list.length) { |
| var comparison = list[i].compare(element); |
| if (comparison == 0) |
| // Don't add a duplicate. |
| return; |
| if (!(comparison < 0)) |
| break; |
| |
| ++i; |
| } |
| |
| list.splice(i, 0, element); |
| }; |
| |
| /** |
| * Encode the RepetitiveInterval as NDN-TLV to the encoder. |
| * @param {RepetitiveInterval} repetitiveInterval The RepetitiveInterval to encode. |
| * @param {TlvEncoder} encoder The TlvEncoder to receive the encoding. |
| */ |
| Schedule.encodeRepetitiveInterval_ = function(repetitiveInterval, encoder) |
| { |
| var saveLength = encoder.getLength(); |
| |
| // Encode backwards. |
| // The RepeatUnit enum has the same values as the encoding. |
| encoder.writeNonNegativeIntegerTlv |
| (Tlv.Encrypt_RepeatUnit, repetitiveInterval.getRepeatUnit()); |
| encoder.writeNonNegativeIntegerTlv |
| (Tlv.Encrypt_NRepeats, repetitiveInterval.getNRepeats()); |
| encoder.writeNonNegativeIntegerTlv |
| (Tlv.Encrypt_IntervalEndHour, repetitiveInterval.getIntervalEndHour()); |
| encoder.writeNonNegativeIntegerTlv |
| (Tlv.Encrypt_IntervalStartHour, repetitiveInterval.getIntervalStartHour()); |
| // Use Blob to convert the string to UTF8 encoding. |
| encoder.writeBlobTlv(Tlv.Encrypt_EndDate, |
| new Blob(Schedule.toIsoString(repetitiveInterval.getEndDate())).buf()); |
| encoder.writeBlobTlv(Tlv.Encrypt_StartDate, |
| new Blob(Schedule.toIsoString(repetitiveInterval.getStartDate())).buf()); |
| |
| encoder.writeTypeAndLength |
| (Tlv.Encrypt_RepetitiveInterval, encoder.getLength() - saveLength); |
| }; |
| |
| /** |
| * Decode the input as an NDN-TLV RepetitiveInterval. |
| * @param {TlvDecoder} decoder The decoder with the input to decode. |
| * @return {RepetitiveInterval} A new RepetitiveInterval with the decoded result. |
| */ |
| Schedule.decodeRepetitiveInterval_ = function(decoder) |
| { |
| var endOffset = decoder.readNestedTlvsStart(Tlv.Encrypt_RepetitiveInterval); |
| |
| // Use Blob to convert UTF8 to a string. |
| var startDate = Schedule.fromIsoString |
| (new Blob(decoder.readBlobTlv(Tlv.Encrypt_StartDate), true).toString()); |
| var endDate = Schedule.fromIsoString |
| (new Blob(decoder.readBlobTlv(Tlv.Encrypt_EndDate), true).toString()); |
| var startHour = decoder.readNonNegativeIntegerTlv(Tlv.Encrypt_IntervalStartHour); |
| var endHour = decoder.readNonNegativeIntegerTlv(Tlv.Encrypt_IntervalEndHour); |
| var nRepeats = decoder.readNonNegativeIntegerTlv(Tlv.Encrypt_NRepeats); |
| |
| // The RepeatUnit enum has the same values as the encoding. |
| var repeatUnit = decoder.readNonNegativeIntegerTlv(Tlv.Encrypt_RepeatUnit); |
| |
| decoder.finishNestedTlvs(endOffset); |
| return new RepetitiveInterval |
| (startDate, endDate, startHour, endHour, nRepeats, repeatUnit); |
| }; |
| |
| /** |
| * A helper function to calculate black interval results or white interval |
| * results. |
| * @param {Array} list The set of RepetitiveInterval, which can be the white |
| * list or the black list. |
| * @param {number} timeStamp The time stamp as milliseconds since Jan 1, 1970 UTC. |
| * @param {Interval} positiveResult The positive result which is updated. |
| * @param {Interval} negativeResult The negative result which is updated. |
| */ |
| Schedule.calculateIntervalResult_ = function |
| (list, timeStamp, positiveResult, negativeResult) |
| { |
| for (var i = 0; i < list.length; ++i) { |
| var element = list[i]; |
| |
| var result = element.getInterval(timeStamp); |
| var tempInterval = result.interval; |
| if (result.isPositive == true) |
| positiveResult.unionWith(tempInterval); |
| else { |
| if (!negativeResult.isValid()) |
| negativeResult.set(tempInterval); |
| else |
| negativeResult.intersectWith(tempInterval); |
| } |
| } |
| }; |
| |
| /** |
| * Convert a UNIX timestamp to ISO time representation with the "T" in the middle. |
| * @param {number} msSince1970 Timestamp as milliseconds since Jan 1, 1970 UTC. |
| * @return {string} The string representation. |
| */ |
| Schedule.toIsoString = function(msSince1970) |
| { |
| var utcTime = new Date(Math.round(msSince1970)); |
| return utcTime.getUTCFullYear() + |
| Schedule.to2DigitString(utcTime.getUTCMonth() + 1) + |
| Schedule.to2DigitString(utcTime.getUTCDate()) + |
| "T" + |
| Schedule.to2DigitString(utcTime.getUTCHours()) + |
| Schedule.to2DigitString(utcTime.getUTCMinutes()) + |
| Schedule.to2DigitString(utcTime.getUTCSeconds()); |
| }; |
| |
| /** |
| * A private method to zero pad an integer to 2 digits. |
| * @param {number} x The number to pad. Assume it is a non-negative integer. |
| * @return {string} The padded string. |
| */ |
| Schedule.to2DigitString = function(x) |
| { |
| var result = x.toString(); |
| return result.length === 1 ? "0" + result : result; |
| }; |
| |
| /** |
| * Convert an ISO time representation with the "T" in the middle to a UNIX |
| * timestamp. |
| * @param {string} timeString The ISO time representation. |
| * @return {number} The timestamp as milliseconds since Jan 1, 1970 UTC. |
| */ |
| Schedule.fromIsoString = function(timeString) |
| { |
| if (timeString.length != 15 || timeString.substr(8, 1) != 'T') |
| throw new Error("fromIsoString: Format is not the expected yyyymmddThhmmss"); |
| |
| return Date.UTC |
| (parseInt(timeString.substr(0, 4)), |
| parseInt(timeString.substr(4, 2) - 1), |
| parseInt(timeString.substr(6, 2)), |
| parseInt(timeString.substr(9, 2)), |
| parseInt(timeString.substr(11, 2)), |
| parseInt(timeString.substr(13, 2))); |
| }; |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| // Don't require modules since this is meant for the browser, not Node.js. |
| |
| /** |
| * IndexedDbConsumerDb extends ConsumerDb to implement the storage of decryption |
| * keys for the consumer using the browser's IndexedDB service. |
| * Create an IndexedDbConsumerDb to use the given IndexedDB database name. |
| * @param {string} databaseName IndexedDB database name. |
| * @note This class is an experimental feature. The API may change. |
| * @constructor |
| */ |
| var IndexedDbConsumerDb = function IndexedDbConsumerDb(databaseName) |
| { |
| ConsumerDb.call(this); |
| |
| this.database = new Dexie(databaseName); |
| this.database.version(1).stores({ |
| // "keyName" is the key name URI // string |
| // (Note: In SQLite3, the key name is the TLV encoded bytes, but we can't |
| // index on a byte array in IndexedDb.) |
| // "key" is the key bytes // Uint8Array |
| decryptionKeys: "keyName" |
| }); |
| this.database.open(); |
| }; |
| |
| IndexedDbConsumerDb.prototype = new ConsumerDb(); |
| IndexedDbConsumerDb.prototype.name = "IndexedDbConsumerDb"; |
| |
| /** |
| * Get the key with keyName from the database. |
| * @param {Name} keyName The key name. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that returns a Blob with the encoded key (or an |
| * isNull Blob if cannot find the key with keyName), or that is |
| * rejected with ConsumerDb.Error for a database error. |
| */ |
| IndexedDbConsumerDb.prototype.getKeyPromise = function(keyName, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new ConsumerDb.Error(new Error |
| ("IndexedDbConsumerDb.getKeyPromise is only supported for async"))); |
| |
| return this.database.decryptionKeys.get(keyName.toUri()) |
| .then(function(decryptionKeysEntry) { |
| if (decryptionKeysEntry) |
| return Promise.resolve(new Blob(decryptionKeysEntry.key)); |
| else |
| return Promise.resolve(new Blob()); |
| }) |
| .catch(function(ex) { |
| return Promise.reject(new ConsumerDb.Error(new Error |
| ("IndexedDbConsumerDb.getKeyPromise: Error: " + ex))); |
| }); |
| }; |
| |
| /** |
| * Add the key with keyName and keyBlob to the database. |
| * @param {Name} keyName The key name. |
| * @param {Blob} keyBlob The encoded key. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that fulfills when the key is added, or that |
| * is rejected with ConsumerDb.Error if a key with the same keyName already |
| * exists, or other database error. |
| */ |
| IndexedDbConsumerDb.prototype.addKeyPromise = function(keyName, keyBlob, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new ConsumerDb.Error(new Error |
| ("IndexedDbConsumerDb.addKeyPromise is only supported for async"))); |
| |
| // Add rejects if the primary key already exists. |
| return this.database.decryptionKeys.add |
| ({ keyName: keyName.toUri(), key: keyBlob.buf() }) |
| .catch(function(ex) { |
| return Promise.reject(new ConsumerDb.Error(new Error |
| ("IndexedDbConsumerDb.addKeyPromise: Error: " + ex))); |
| }); |
| }; |
| |
| /** |
| * Delete the key with keyName from the database. If there is no key with |
| * keyName, do nothing. |
| * @param {Name} keyName The key name. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that fulfills when the key is deleted (or there |
| * is no such key), or that is rejected with ConsumerDb.Error for a database |
| * error. |
| */ |
| IndexedDbConsumerDb.prototype.deleteKeyPromise = function(keyName, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new ConsumerDb.Error(new Error |
| ("IndexedDbConsumerDb.deleteKeyPromise is only supported for async"))); |
| |
| return this.database.decryptionKeys.delete(keyName.toUri()) |
| .catch(function(ex) { |
| return Promise.reject(new ConsumerDb.Error(new Error |
| ("IndexedDbConsumerDb.deleteKeyPromise: Error: " + ex))); |
| }); |
| }; |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| // Don't require modules since this is meant for the browser, not Node.js. |
| |
| /** |
| * IndexedDbGroupManagerDb extends GroupManagerDb to implement the storage of |
| * data used by the GroupManager using the browser's IndexedDB service. |
| * Create an IndexedDbGroupManagerDb to use the given IndexedDB database name. |
| * @param {string} databaseName IndexedDB database name. |
| * @note This class is an experimental feature. The API may change. |
| * @constructor |
| */ |
| var IndexedDbGroupManagerDb = function IndexedDbGroupManagerDb(databaseName) |
| { |
| GroupManagerDb.call(this); |
| |
| this.database = new Dexie(databaseName); |
| this.database.version(1).stores({ |
| // "scheduleId" is the schedule ID, auto incremented // number |
| // "scheduleName" is the schedule name, unique // string |
| // "schedule" is the TLV-encoded schedule // Uint8Array |
| schedules: "++scheduleId, &scheduleName", |
| |
| // "memberNameUri" is the member name URI // string |
| // (Note: In SQLite3, the member name index is the TLV encoded bytes, but |
| // we can't index on a byte array in IndexedDb.) |
| // (Note: The SQLite3 table also has an auto-incremented member ID primary |
| // key, but is not used so we omit it to simplify.) |
| // "memberName" is the TLV-encoded member name (same as memberNameUri // Uint8Array |
| // "scheduleId" is the schedule ID, linked to the schedules table // number |
| // (Note: The SQLite3 table has a foreign key to the schedules table with |
| // cascade update and delete, but we have to handle it manually.) |
| // "keyName" is the TLV-encoded key name // Uint8Array |
| // "publicKey" is the encoded key bytes // Uint8Array |
| members: "memberNameUri, scheduleId" |
| }); |
| this.database.open(); |
| }; |
| |
| IndexedDbGroupManagerDb.prototype = new GroupManagerDb(); |
| IndexedDbGroupManagerDb.prototype.name = "IndexedDbGroupManagerDb"; |
| |
| ////////////////////////////////////////////////////// Schedule management. |
| |
| /** |
| * Check if there is a schedule with the given name. |
| * @param {string} name The name of the schedule. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that returns true if there is a schedule (else |
| * false), or that is rejected with GroupManagerDb.Error for a database error. |
| */ |
| IndexedDbGroupManagerDb.prototype.hasSchedulePromise = function(name, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.hasSchedulePromise is only supported for async"))); |
| |
| return this.getScheduleIdPromise_(name) |
| .then(function(scheduleId) { |
| return Promise.resolve(scheduleId != -1); |
| }); |
| }; |
| |
| /** |
| * List all the names of the schedules. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that returns a new array of string with the names |
| * of all schedules, or that is rejected with GroupManagerDb.Error for a |
| * database error. |
| */ |
| IndexedDbGroupManagerDb.prototype.listAllScheduleNamesPromise = function(useSync) |
| { |
| if (useSync) |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.listAllScheduleNamesPromise is only supported for async"))); |
| |
| var list = []; |
| return this.database.schedules.each(function(entry) { |
| list.push(entry.scheduleName); |
| }) |
| .then(function() { |
| return Promise.resolve(list); |
| }) |
| .catch(function(ex) { |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.listAllScheduleNamesPromise: Error: " + ex))); |
| }); |
| }; |
| |
| /** |
| * Get a schedule with the given name. |
| * @param {string} name The name of the schedule. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that returns a new Schedule object, or that is |
| * rejected with GroupManagerDb.Error if the schedule does not exist or other |
| * database error. |
| */ |
| IndexedDbGroupManagerDb.prototype.getSchedulePromise = function(name, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.getSchedulePromise is only supported for async"))); |
| |
| var thisManager = this; |
| // Use getScheduleIdPromise_ to handle the search on the non-primary key. |
| return this.getScheduleIdPromise_(name) |
| .then(function(scheduleId) { |
| if (scheduleId != -1) { |
| return thisManager.database.schedules.get(scheduleId) |
| .then(function(entry) { |
| // We expect entry to be found, and don't expect an error decoding. |
| var schedule = new Schedule(); |
| schedule.wireDecode(new Blob(entry.schedule, false)); |
| return Promise.resolve(schedule); |
| }) |
| .catch(function(ex) { |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.getSchedulePromise: Error: " + ex))); |
| }); |
| } |
| else |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.getSchedulePromise: Cannot get the result from the database"))); |
| }); |
| }; |
| |
| /** |
| * For each member using the given schedule, get the name and public key DER |
| * of the member's key. |
| * @param {string} name The name of the schedule. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that returns a new array of object (where |
| * "keyName" is the Name of the public key and "publicKey" is the Blob of the |
| * public key DER), or that is rejected with GroupManagerDb.Error for a database |
| * error. Note that the member's identity name is keyName.getPrefix(-1). If the |
| * schedule name is not found, the list is empty. |
| */ |
| IndexedDbGroupManagerDb.prototype.getScheduleMembersPromise = function |
| (name, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.getScheduleMembersPromise is only supported for async"))); |
| |
| var list = []; |
| var thisManager = this; |
| // There is only one matching schedule ID, so we can just look it up instead |
| // of doing a more complicated join. |
| return this.getScheduleIdPromise_(name) |
| .then(function(scheduleId) { |
| if (scheduleId == -1) |
| // Return the empty list. |
| return Promise.resolve(list); |
| |
| var onEntryError = null; |
| return thisManager.database.members.where("scheduleId").equals(scheduleId) |
| .each(function(entry) { |
| try { |
| var keyName = new Name(); |
| keyName.wireDecode(new Blob(entry.keyName, false), TlvWireFormat.get()); |
| |
| list.push({ keyName: keyName, publicKey: new Blob(entry.publicKey, false) }); |
| } catch (ex) { |
| // We don't expect this to happen. |
| onEntryError = new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.getScheduleMembersPromise: Error decoding name: " + ex)); |
| } |
| }) |
| .then(function() { |
| if (onEntryError) |
| // We got an error decoding. |
| return Promise.reject(onEntryError); |
| else |
| return Promise.resolve(list); |
| }, function(ex) { |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.getScheduleMembersPromise: Error: " + ex))); |
| }); |
| }); |
| }; |
| |
| /** |
| * Add a schedule with the given name. |
| * @param {string} name The name of the schedule. The name cannot be empty. |
| * @param {Schedule} schedule The Schedule to add. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that fulfills when the schedule is added, or that |
| * is rejected with GroupManagerDb.Error if a schedule with the same name |
| * already exists, if the name is empty, or other database error. |
| */ |
| IndexedDbGroupManagerDb.prototype.addSchedulePromise = function |
| (name, schedule, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.addSchedulePromise is only supported for async"))); |
| |
| if (name.length == 0) |
| return Promise.reject(new GroupManagerDb.Error |
| ("IndexedDbGroupManagerDb.addSchedulePromise: The schedule name cannot be empty")); |
| |
| // Add rejects if the primary key already exists. |
| return this.database.schedules.add |
| ({ scheduleName: name, schedule: schedule.wireEncode().buf() }) |
| .catch(function(ex) { |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.addContentKeyPromise: Error: " + ex))); |
| }); |
| }; |
| |
| /** |
| * Delete the schedule with the given name. Also delete members which use this |
| * schedule. If there is no schedule with the name, then do nothing. |
| * @param {string} name The name of the schedule. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that fulfills when the schedule is deleted (or |
| * there is no such schedule), or that is rejected with GroupManagerDb.Error for |
| * a database error. |
| */ |
| IndexedDbGroupManagerDb.prototype.deleteSchedulePromise = function |
| (name, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.deleteSchedulePromise is only supported for async"))); |
| |
| var scheduleId; |
| var thisManager = this; |
| return this.getScheduleIdPromise_(name) |
| .then(function(localScheduleId) { |
| scheduleId = localScheduleId; |
| |
| // Get the members which use this schedule. |
| return thisManager.database.members.where("scheduleId").equals(scheduleId).toArray(); |
| }) |
| .then(function(membersEntries) { |
| // Delete the members. |
| var promises = membersEntries.map(function(entry) { |
| return thisManager.database.members.delete(entry.memberNameUri); |
| }); |
| return Promise.all(promises); |
| }) |
| .then(function() { |
| // Now delete the schedule. |
| return thisManager.database.schedules.delete(scheduleId); |
| }) |
| .catch(function(ex) { |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.deleteSchedulePromise: Error: " + ex))); |
| }); |
| }; |
| |
| /** |
| * Rename a schedule with oldName to newName. |
| * @param {string} oldName The name of the schedule to be renamed. |
| * @param {string} newName The new name of the schedule. The name cannot be empty. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that fulfills when the schedule is renamed, or |
| * that is rejected with GroupManagerDb.Error if a schedule with newName already |
| * exists, if the schedule with oldName does not exist, if newName is empty, or |
| * other database error. |
| */ |
| IndexedDbGroupManagerDb.prototype.renameSchedulePromise = function |
| (oldName, newName, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.renameSchedulePromise is only supported for async"))); |
| |
| if (newName.length == 0) |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.renameSchedule: The schedule newName cannot be empty"))); |
| |
| var thisManager = this; |
| return this.getScheduleIdPromise_(oldName) |
| .then(function(scheduleId) { |
| if (scheduleId == -1) |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.renameSchedule: The schedule oldName does not exist"))); |
| |
| return thisManager.database.schedules.update |
| (scheduleId, { scheduleName: newName }) |
| .catch(function(ex) { |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.renameSchedulePromise: Error: " + ex))); |
| }); |
| }); |
| }; |
| |
| /** |
| * Update the schedule with name and replace the old object with the given |
| * schedule. Otherwise, if no schedule with name exists, a new schedule |
| * with name and the given schedule will be added to database. |
| * @param {string} name The name of the schedule. The name cannot be empty. |
| * @param {Schedule} schedule The Schedule to update or add. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that fulfills when the schedule is updated, or |
| * that is rejected with GroupManagerDb.Error if the name is empty, or other |
| * database error. |
| */ |
| IndexedDbGroupManagerDb.prototype.updateSchedulePromise = function |
| (name, schedule, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.updateSchedulePromise is only supported for async"))); |
| |
| var thisManager = this; |
| return this.getScheduleIdPromise_(name) |
| .then(function(scheduleId) { |
| if (scheduleId == -1) |
| return thisManager.addSchedulePromise(name, schedule); |
| |
| return thisManager.database.schedules.update |
| (scheduleId, { schedule: schedule.wireEncode().buf() }) |
| .catch(function(ex) { |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.updateSchedulePromise: Error: " + ex))); |
| }); |
| }); |
| }; |
| |
| ////////////////////////////////////////////////////// Member management. |
| |
| /** |
| * Check if there is a member with the given identity name. |
| * @param {Name} identity The member's identity name. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that returns true if there is a member (else |
| * false), or that is rejected with GroupManagerDb.Error for a database error. |
| */ |
| IndexedDbGroupManagerDb.prototype.hasMemberPromise = function(identity, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.hasMemberPromise is only supported for async"))); |
| |
| return this.database.members.get(identity.toUri()) |
| .then(function(entry) { |
| return Promise.resolve(entry != undefined); |
| }) |
| .catch(function(ex) { |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.hasMemberPromise: Error: " + ex))); |
| }); |
| }; |
| |
| /** |
| * List all the members. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that returns a new array of Name with the names |
| * of all members, or that is rejected with GroupManagerDb.Error for a |
| * database error. |
| */ |
| IndexedDbGroupManagerDb.prototype.listAllMembersPromise = function(useSync) |
| { |
| if (useSync) |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.listAllMembersPromise is only supported for async"))); |
| |
| var list = []; |
| var onEntryError = null; |
| return this.database.members.each(function(entry) { |
| try { |
| var identity = new Name(); |
| identity.wireDecode(new Blob(entry.memberName, false), TlvWireFormat.get()); |
| list.push(identity); |
| } catch (ex) { |
| // We don't expect this to happen. |
| onEntryError = new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.listAllMembersPromise: Error decoding name: " + ex)); |
| } |
| }) |
| .then(function() { |
| if (onEntryError) |
| // We got an error decoding. |
| return Promise.reject(onEntryError); |
| else |
| return Promise.resolve(list); |
| }, function(ex) { |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.listAllMembersPromise: Error: " + ex))); |
| }); |
| }; |
| |
| /** |
| * Get the name of the schedule for the given member's identity name. |
| * @param {Name} identity The member's identity name. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that returns the string schedule name, or that is |
| * rejected with GroupManagerDb.Error if there's no member with the given |
| * identity name in the database, or other database error. |
| */ |
| IndexedDbGroupManagerDb.prototype.getMemberSchedulePromise = function |
| (identity, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.getMemberSchedulePromise is only supported for async"))); |
| |
| var thisManager = this; |
| return this.database.members.get(identity.toUri()) |
| .then(function(membersEntry) { |
| if (!membersEntry) |
| throw new Error("The member identity name does not exist in the database"); |
| |
| return thisManager.database.schedules.get(membersEntry.scheduleId); |
| }) |
| .then(function(schedulesEntry) { |
| if (!schedulesEntry) |
| throw new Error |
| ("The schedule ID for the member identity name does not exist in the database"); |
| |
| return Promise.resolve(schedulesEntry.scheduleName); |
| }) |
| .catch(function(ex) { |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.getScheduleIdPromise_: Error: " + ex))); |
| }); |
| }; |
| |
| /** |
| * Add a new member with the given key named keyName into a schedule named |
| * scheduleName. The member's identity name is keyName.getPrefix(-1). |
| * @param {string} scheduleName The schedule name. |
| * @param {Name} keyName The name of the key. |
| * @param {Blob} key A Blob of the public key DER. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that fulfills when the member is added, or that |
| * is rejected with GroupManagerDb.Error if there's no schedule named |
| * scheduleName, if the member's identity name already exists, or other database |
| * error. |
| */ |
| IndexedDbGroupManagerDb.prototype.addMemberPromise = function |
| (scheduleName, keyName, key, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.addMemberPromise is only supported for async"))); |
| |
| var thisManager = this; |
| return this.getScheduleIdPromise_(scheduleName) |
| .then(function(scheduleId) { |
| if (scheduleId == -1) |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.addMemberPromise: The schedule does not exist"))); |
| |
| // Needs to be changed in the future. |
| var memberName = keyName.getPrefix(-1); |
| |
| // Add rejects if the primary key already exists. |
| return thisManager.database.members.add |
| ({ memberNameUri: memberName.toUri(), |
| memberName: memberName.wireEncode(TlvWireFormat.get()).buf(), |
| scheduleId: scheduleId, |
| keyName: keyName.wireEncode(TlvWireFormat.get()).buf(), |
| publicKey: key.buf() }) |
| .catch(function(ex) { |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.addMemberPromise: Error: " + ex))); |
| }); |
| }); |
| }; |
| |
| /** |
| * Change the name of the schedule for the given member's identity name. |
| * @param {Name} identity The member's identity name. |
| * @param {string} scheduleName The new schedule name. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that fulfills when the member is updated, or that |
| * is rejected with GroupManagerDb.Error if there's no member with the given |
| * identity name in the database, or there's no schedule named scheduleName, or |
| * other database error. |
| */ |
| IndexedDbGroupManagerDb.prototype.updateMemberSchedulePromise = function |
| (identity, scheduleName, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.updateMemberSchedulePromise is only supported for async"))); |
| |
| var thisManager = this; |
| return this.getScheduleIdPromise_(scheduleName) |
| .then(function(scheduleId) { |
| if (scheduleId == -1) |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.updateMemberSchedulePromise: The schedule does not exist"))); |
| |
| return thisManager.database.members.update |
| (identity.toUri(), { scheduleId: scheduleId }) |
| .catch(function(ex) { |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.updateMemberSchedulePromise: Error: " + ex))); |
| }); |
| }); |
| }; |
| |
| /** |
| * Delete a member with the given identity name. If there is no member with |
| * the identity name, then do nothing. |
| * @param {Name} identity The member's identity name. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that fulfills when the member is deleted (or |
| * there is no such member), or that is rejected with GroupManagerDb.Error for a |
| * database error. |
| */ |
| IndexedDbGroupManagerDb.prototype.deleteMemberPromise = function |
| (identity, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.deleteMemberPromise is only supported for async"))); |
| |
| return this.database.members.delete(identity.toUri()) |
| .catch(function(ex) { |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.deleteMemberPromise: Error: " + ex))); |
| }); |
| }; |
| |
| /** |
| * Get the ID for the schedule. |
| * @param {string} name The schedule name. |
| * @return {Promise} A promise that returns the ID (or -1 if not found), or that |
| * is rejected with GroupManagerDb.Error for a database error. |
| */ |
| IndexedDbGroupManagerDb.prototype.getScheduleIdPromise_ = function(name) |
| { |
| // The scheduleName is not the primary key, so use 'where' instead of 'get'. |
| var id = -1; |
| return this.database.schedules.where("scheduleName").equals(name) |
| .each(function(entry) { |
| id = entry.scheduleId; |
| }) |
| .then(function() { |
| return Promise.resolve(id); |
| }) |
| .catch(function(ex) { |
| return Promise.reject(new GroupManagerDb.Error(new Error |
| ("IndexedDbGroupManagerDb.getScheduleIdPromise_: Error: " + ex))); |
| }); |
| }; |
| /** |
| * Copyright (C) 2015-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| // Don't require modules since this is meant for the browser, not Node.js. |
| |
| /** |
| * IndexedDbProducerDb extends ProducerDb to implement storage of keys for the |
| * producer using the browser's IndexedDB service. It contains one table that |
| * maps time slots (to the nearest hour) to the content key created for that |
| * time slot. |
| * Create an IndexedDbProducerDb to use the given IndexedDB database name. |
| * @param {string} databaseName IndexedDB database name. |
| * @note This class is an experimental feature. The API may change. |
| * @constructor |
| */ |
| var IndexedDbProducerDb = function IndexedDbProducerDb(databaseName) |
| { |
| ProducerDb.call(this); |
| |
| this.database = new Dexie(databaseName); |
| this.database.version(1).stores({ |
| // "timeSlot" is the hour-based time slot as hours since Jan 1, 1970 UTC. // number |
| // "key" is the encoded key // Uint8Array |
| contentKeys: "timeSlot" |
| }); |
| this.database.open(); |
| }; |
| |
| IndexedDbProducerDb.prototype = new ProducerDb(); |
| IndexedDbProducerDb.prototype.name = "IndexedDbProducerDb"; |
| |
| /** |
| * Check if a content key exists for the hour covering timeSlot. |
| * @param {number} timeSlot The time slot as milliseconds since Jan 1, 1970 UTC. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that returns true if there is a content key for |
| * timeSlot (else false), or that is rejected with ProducerDb.Error for a |
| * database error. |
| */ |
| IndexedDbProducerDb.prototype.hasContentKeyPromise = function(timeSlot, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new ProducerDb.Error(new Error |
| ("IndexedDbProducerDb.hasContentKeyPromise is only supported for async"))); |
| |
| var fixedTimeSlot = ProducerDb.getFixedTimeSlot(timeSlot); |
| |
| return this.database.contentKeys.get(fixedTimeSlot) |
| .then(function(contentKeysEntry) { |
| return Promise.resolve(contentKeysEntry != undefined); |
| }) |
| .catch(function(ex) { |
| return Promise.reject(new ProducerDb.Error(new Error |
| ("IndexedDbProducerDb.hasContentKeyPromise: Error: " + ex))); |
| }); |
| }; |
| |
| /** |
| * Get the content key for the hour covering timeSlot. |
| * @param {number} timeSlot The time slot as milliseconds since Jan 1, 1970 UTC. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that returns a Blob with the encoded key, or that |
| * is rejected with ProducerDb.Error if there is no key covering timeSlot, or |
| * other database error |
| */ |
| IndexedDbProducerDb.prototype.getContentKeyPromise = function(timeSlot, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new ProducerDb.Error(new Error |
| ("IndexedDbProducerDb.getContentKeyPromise is only supported for async"))); |
| |
| var fixedTimeSlot = ProducerDb.getFixedTimeSlot(timeSlot); |
| |
| return this.database.contentKeys.get(fixedTimeSlot) |
| .then(function(contentKeysEntry) { |
| if (contentKeysEntry) |
| return Promise.resolve(new Blob(contentKeysEntry.key)); |
| else |
| return Promise.reject(new ProducerDb.Error(new Error |
| ("IndexedDbProducerDb.getContentKeyPromise: Cannot get the key from the database"))); |
| }, function(ex) { |
| return Promise.reject(new ProducerDb.Error(new Error |
| ("IndexedDbProducerDb.getContentKeyPromise: Error: " + ex))); |
| }); |
| }; |
| |
| /** |
| * Add key as the content key for the hour covering timeSlot. |
| * @param {number} timeSlot The time slot as milliseconds since Jan 1, 1970 UTC. |
| * @param {Blob} key The encoded key. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that fulfills when the key is added, or that |
| * is rejected with ProducerDb.Error if a key for the same hour already exists |
| * in the database, or other database error. |
| */ |
| IndexedDbProducerDb.prototype.addContentKeyPromise = function |
| (timeSlot, key, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new ProducerDb.Error(new Error |
| ("IndexedDbProducerDb.addContentKeyPromise is only supported for async"))); |
| |
| var fixedTimeSlot = ProducerDb.getFixedTimeSlot(timeSlot); |
| |
| // Add rejects if the primary key already exists. |
| return this.database.contentKeys.add |
| ({ timeSlot: fixedTimeSlot, key: key.buf() }) |
| .catch(function(ex) { |
| return Promise.reject(new ProducerDb.Error(new Error |
| ("IndexedDbProducerDb.addContentKeyPromise: Error: " + ex))); |
| }); |
| }; |
| |
| /** |
| * Delete the content key for the hour covering timeSlot. If there is no key for |
| * the time slot, do nothing. |
| * @param {number} timeSlot The time slot as milliseconds since Jan 1, 1970 UTC. |
| * @param {boolean} useSync (optional) If true then return a rejected promise |
| * since this only supports async code. |
| * @return {Promise} A promise that fulfills when the key is deleted (or there |
| * is no such key), or that is rejected with ProducerDb.Error for a database |
| * error. |
| */ |
| IndexedDbProducerDb.prototype.deleteContentKeyPromise = function(timeSlot, useSync) |
| { |
| if (useSync) |
| return Promise.reject(new ProducerDb.Error(new Error |
| ("IndexedDbProducerDb.deleteContentKeyPromise is only supported for async"))); |
| |
| var fixedTimeSlot = ProducerDb.getFixedTimeSlot(timeSlot); |
| |
| return this.database.contentKeys.delete(fixedTimeSlot) |
| .catch(function(ex) { |
| return Promise.reject(new ProducerDb.Error(new Error |
| ("IndexedDbProducerDb.deleteContentKeyPromise: Error: " + ex))); |
| }); |
| }; |
| /** |
| * This class represents the digest tree for chrono-sync2013. |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Zhehao Wang, based on Jeff T.'s implementation in ndn-cpp |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var DigestTree = require('./digest-tree.js').DigestTree; /** @ignore */ |
| var Interest = require('../interest.js').Interest; /** @ignore */ |
| var Data = require('../data.js').Data; /** @ignore */ |
| var Name = require('../name.js').Name; /** @ignore */ |
| var Blob = require('../util/blob.js').Blob; /** @ignore */ |
| var MemoryContentCache = require('../util/memory-content-cache.js').MemoryContentCache; /** @ignore */ |
| var SyncStateProto = require('./sync-state.js').SyncStateProto; /** @ignore */ |
| var NdnCommon = require('../util/ndn-common.js').NdnCommon; |
| |
| /** |
| * ChronoSync2013 implements the NDN ChronoSync protocol as described in the |
| * 2013 paper "Let's ChronoSync: Decentralized Dataset State Synchronization in |
| * Named Data Networking". http://named-data.net/publications/chronosync . |
| * @note The support for ChronoSync is experimental and the API is not finalized. |
| * See the API docs for more detail at |
| * http://named-data.net/doc/ndn-ccl-api/chrono-sync2013.html . |
| * |
| * Create a new ChronoSync2013 to communicate using the given face. Initialize |
| * the digest log with a digest of "00" and and empty content. Register the |
| * applicationBroadcastPrefix to receive interests for sync state messages and |
| * express an interest for the initial root digest "00". |
| * @param {function} onReceivedSyncState When ChronoSync receives a sync state message, |
| * this calls onReceivedSyncState(syncStates, isRecovery) where syncStates is the |
| * list of SyncState messages and isRecovery is true if this is the initial |
| * list of SyncState messages or from a recovery interest. (For example, if |
| * isRecovery is true, a chat application would not want to re-display all |
| * the associated chat messages.) The callback should send interests to fetch |
| * the application data for the sequence numbers in the sync state. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onInitialized This calls onInitialized() when the first sync data |
| * is received (or the interest times out because there are no other |
| * publishers yet). |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {Name} applicationDataPrefix The prefix used by this application instance |
| * for application data. For example, "/my/local/prefix/ndnchat4/0K4wChff2v". |
| * This is used when sending a sync message for a new sequence number. |
| * In the sync message, this uses applicationDataPrefix.toUri(). |
| * @param {Name} applicationBroadcastPrefix The broadcast name prefix including the |
| * application name. For example, "/ndn/broadcast/ChronoChat-0.3/ndnchat1". |
| * This makes a copy of the name. |
| * @param {int} sessionNo The session number used with the applicationDataPrefix in |
| * sync state messages. |
| * @param {Face} face The Face for calling registerPrefix and expressInterest. The |
| * Face object must remain valid for the life of this ChronoSync2013 object. |
| * @param {KeyChain} keyChain To sign a data packet containing a sync state message, this |
| * calls keyChain.sign(data, certificateName). |
| * @param {Name} certificateName The certificate name of the key to use for signing a |
| * data packet containing a sync state message. |
| * @param {Milliseconds} syncLifetime The interest lifetime in milliseconds for sending |
| * sync interests. |
| * @param {function} onRegisterFailed If failed to register the prefix to receive |
| * interests for the applicationBroadcastPrefix, this calls |
| * onRegisterFailed(applicationBroadcastPrefix). |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @constructor |
| */ |
| var ChronoSync2013 = function ChronoSync2013 |
| (onReceivedSyncState, onInitialized, applicationDataPrefix, |
| applicationBroadcastPrefix, sessionNo, face, keyChain, certificateName, |
| syncLifetime, onRegisterFailed) |
| { |
| // assigning function pointers |
| this.onReceivedSyncState = onReceivedSyncState; |
| this.onInitialized = onInitialized; |
| this.applicationDataPrefixUri = applicationDataPrefix.toUri(); |
| this.applicationBroadcastPrefix = applicationBroadcastPrefix; |
| this.session = sessionNo; |
| this.face = face; |
| this.keyChain = keyChain; |
| this.certificateName = certificateName; |
| this.sync_lifetime = syncLifetime; |
| this.usrseq = -1; |
| |
| this.digest_tree = new DigestTree(); |
| this.contentCache = new MemoryContentCache(face); |
| |
| this.digest_log = new Array(); |
| this.digest_log.push(new ChronoSync2013.DigestLogEntry("00",[])); |
| |
| this.contentCache.registerPrefix |
| (this.applicationBroadcastPrefix, onRegisterFailed, |
| this.onInterest.bind(this)); |
| this.enabled = true; |
| |
| var interest = new Interest(this.applicationBroadcastPrefix); |
| interest.getName().append("00"); |
| |
| interest.setInterestLifetimeMilliseconds(1000); |
| |
| var Sync; |
| try { |
| // Using protobuf.min.js in the browser. |
| Sync = dcodeIO.ProtoBuf.newBuilder().import(SyncStateProto).build("Sync"); |
| } |
| catch (ex) { |
| // Using protobufjs in node. |
| Sync = require("protobufjs").newBuilder().import(SyncStateProto).build("Sync"); |
| } |
| this.SyncStateMsg = Sync.SyncStateMsg; |
| this.SyncState = Sync.SyncState; |
| |
| this.face.expressInterest(interest, this.onData.bind(this), this.initialTimeOut.bind(this)); |
| }; |
| |
| exports.ChronoSync2013 = ChronoSync2013; |
| |
| ChronoSync2013.prototype.getProducerSequenceNo = function(dataPrefix, sessionNo) |
| { |
| var index = this.digest_tree.find(dataPrefix, sessionNo); |
| if (index < 0) |
| return -1; |
| else |
| return this.digest_tree.get(index).getSequenceNo(); |
| }; |
| |
| /** |
| * Increment the sequence number, create a sync message with the new sequence number, |
| * and publish a data packet where the name is applicationBroadcastPrefix + root |
| * digest of current digest tree. Then add the sync message to digest tree and digest |
| * log which creates a new root digest. Finally, express an interest for the next sync |
| * update with the name applicationBroadcastPrefix + the new root digest. |
| * After this, application should publish the content for the new sequence number. |
| * Get the new sequence number with getSequenceNo(). |
| */ |
| ChronoSync2013.prototype.publishNextSequenceNo = function() |
| { |
| this.usrseq ++; |
| var content = [new this.SyncState({ name:this.applicationDataPrefixUri, |
| type:'UPDATE', |
| seqno:{ |
| seq:this.usrseq, |
| session:this.session |
| } |
| })]; |
| var content_t = new this.SyncStateMsg({ss:content}); |
| this.broadcastSyncState(this.digest_tree.getRoot(), content_t); |
| |
| if (!this.update(content)) |
| console.log("Warning: ChronoSync: update did not create a new digest log entry"); |
| |
| var interest = new Interest(this.applicationBroadcastPrefix); |
| interest.getName().append(this.digest_tree.getRoot()); |
| interest.setInterestLifetimeMilliseconds(this.sync_lifetime); |
| |
| this.face.expressInterest(interest, this.onData.bind(this), this.syncTimeout.bind(this)); |
| }; |
| |
| /** |
| * Get the sequence number of the latest data published by this application instance. |
| * @return {int} the sequence number |
| */ |
| ChronoSync2013.prototype.getSequenceNo = function() |
| { |
| return this.usrseq; |
| }; |
| |
| // DigestLogEntry class |
| |
| ChronoSync2013.DigestLogEntry = function ChronoSync2013DisgestLogEntry(digest, data) |
| { |
| this.digest = digest; |
| this.data = data; |
| }; |
| |
| ChronoSync2013.DigestLogEntry.prototype.getDigest = function() |
| { |
| return this.digest; |
| }; |
| |
| ChronoSync2013.DigestLogEntry.prototype.getData = function() |
| { |
| return this.data; |
| }; |
| |
| /** |
| * Unregister callbacks so that this does not respond to interests anymore. |
| * If you will dispose this ChronoSync2013 object while your application is |
| * still running, you should call shutdown() first. After calling this, you |
| * should not call publishNextSequenceNo() again since the behavior will be |
| * undefined. |
| */ |
| ChronoSync2013.prototype.shutdown = function() |
| { |
| this.enabled = false; |
| this.contentCache.unregisterAll(); |
| }; |
| |
| // SyncState class |
| /** |
| * A SyncState holds the values of a sync state message which is passed to the |
| * onReceivedSyncState callback which was given to the ChronoSyn2013 |
| * constructor. Note: this has the same info as the Protobuf class |
| * Sync::SyncState, but we make a separate class so that we don't need the |
| * Protobuf definition in the ChronoSync API. |
| */ |
| ChronoSync2013.SyncState = function ChronoSync2013SyncState(dataPrefixUri, sessionNo, sequenceNo) |
| { |
| this.dataPrefixUri_ = dataPrefixUri; |
| this.sessionNo_ = sessionNo; |
| this.sequenceNo_ = sequenceNo; |
| }; |
| |
| /** |
| * Get the application data prefix for this sync state message. |
| * @return The application data prefix as a Name URI string. |
| */ |
| ChronoSync2013.SyncState.prototype.getDataPrefix = function() |
| { |
| return this.dataPrefixUri_; |
| } |
| |
| /** |
| * Get the session number associated with the application data prefix for |
| * this sync state message. |
| * @return The session number. |
| */ |
| ChronoSync2013.SyncState.prototype.getSessionNo = function() |
| { |
| return this.sessionNo_; |
| } |
| |
| /** |
| * Get the sequence number for this sync state message. |
| * @return The sequence number. |
| */ |
| ChronoSync2013.SyncState.prototype.getSequenceNo = function() |
| { |
| return this.sequenceNo_; |
| } |
| |
| // Private methods for ChronoSync2013 class, |
| /** |
| * Make a data packet with the syncMessage and with name applicationBroadcastPrefix_ + digest. |
| * Sign and send. |
| * @param {string} The root digest as a hex string for the data packet name. |
| * @param {SyncStateMsg} The syncMessage updates the digest tree state with the given digest. |
| */ |
| ChronoSync2013.prototype.broadcastSyncState = function(digest, syncMessage) |
| { |
| var array = new Uint8Array(syncMessage.toArrayBuffer()); |
| var data = new Data(this.applicationBroadcastPrefix); |
| data.getName().append(digest); |
| data.setContent(new Blob(array, false)); |
| var thisChronoSync = this; |
| this.keyChain.sign(data, this.certificateName, function() { |
| thisChronoSync.contentCache.add(data); |
| }); |
| }; |
| |
| /** |
| * Update the digest tree with the messages in content. If the digest tree root is not in |
| * the digest log, also add a log entry with the content. |
| * @param {SyncStates[]} The sync state messages |
| * @return {bool} True if added a digest log entry (because the updated digest tree root |
| * was not in the log), false if didn't add a log entry. |
| */ |
| // Whatever's received by ondata, is pushed into digest log as its data directly |
| ChronoSync2013.prototype.update = function(content) |
| { |
| for (var i = 0; i < content.length; i++) { |
| if (content[i].type == 0) { |
| if (this.digest_tree.update(content[i].name, content[i].seqno.session, content[i].seqno.seq)) { |
| if (this.applicationDataPrefixUri == content[i].name) |
| this.usrseq = content[i].seqno.seq; |
| } |
| } |
| } |
| |
| if (this.logfind(this.digest_tree.getRoot()) == -1) { |
| var newlog = new ChronoSync2013.DigestLogEntry(this.digest_tree.getRoot(), content); |
| this.digest_log.push(newlog); |
| return true; |
| } |
| else |
| return false; |
| }; |
| |
| ChronoSync2013.prototype.logfind = function(digest) |
| { |
| for (var i = 0; i < this.digest_log.length; i++) { |
| if(digest == this.digest_log[i].digest) |
| return i; |
| } |
| return -1; |
| }; |
| |
| /** |
| * Process the sync interest from the applicationBroadcastPrefix. If we can't |
| * satisfy the interest, add it to the pending interest table in |
| * this.contentCache so that a future call to contentCacheAdd may satisfy it. |
| */ |
| ChronoSync2013.prototype.onInterest = function |
| (prefix, interest, face, interestFilterId, filter) |
| { |
| if (!this.enabled) |
| // Ignore callbacks after the application calls shutdown(). |
| return; |
| |
| //search if the digest is already exist in the digest log |
| |
| var syncdigest = interest.getName().get(this.applicationBroadcastPrefix.size()).toEscapedString(); |
| if (interest.getName().size() == this.applicationBroadcastPrefix.size() + 2) { |
| syncdigest = interest.getName().get(this.applicationBroadcastPrefix.size() + 1).toEscapedString(); |
| } |
| if (interest.getName().size() == this.applicationBroadcastPrefix.size() + 2 || syncdigest == "00") { |
| this.processRecoveryInst(interest, syncdigest, face); |
| } |
| else { |
| this.contentCache.storePendingInterest(interest, face); |
| |
| if (syncdigest != this.digest_tree.getRoot()) { |
| var index = this.logfind(syncdigest); |
| var content = []; |
| if(index == -1) { |
| var self = this; |
| // Are we sure that using a "/local/timeout" interest is the best future call approach? |
| var timeout = new Interest(new Name("/local/timeout")); |
| timeout.setInterestLifetimeMilliseconds(2000); |
| this.face.expressInterest |
| (timeout, this.dummyOnData, |
| this.judgeRecovery.bind(this, timeout, syncdigest, face)); |
| } |
| else { |
| //common interest processing |
| this.processSyncInst(index, syncdigest, face); |
| } |
| } |
| } |
| }; |
| |
| /** |
| * Process sync/recovery data. |
| * @param {Interest} |
| * @param {Data} |
| */ |
| ChronoSync2013.prototype.onData = function(interest, co) |
| { |
| if (!this.enabled) |
| // Ignore callbacks after the application calls shutdown(). |
| return; |
| |
| var arr = new Uint8Array(co.getContent().size()); |
| arr.set(co.getContent().buf()); |
| var content_t = this.SyncStateMsg.decode(arr.buffer); |
| var content = content_t.ss; |
| |
| var isRecovery = false; |
| |
| if (this.digest_tree.getRoot() == "00") { |
| isRecovery = true; |
| this.initialOndata(content); |
| } |
| else { |
| this.update(content); |
| if (interest.getName().size() == this.applicationBroadcastPrefix.size() + 2) |
| // Assume this is a recovery interest. |
| isRecovery = true; |
| else |
| isRecovery = false; |
| } |
| |
| var syncStates = []; |
| |
| for (var i = 0; i < content.length; i++) { |
| if (content[i].type == 0) { |
| syncStates.push(new ChronoSync2013.SyncState |
| (content[i].name, content[i].seqno.session, content[i].seqno.seq)); |
| } |
| } |
| |
| // Instead of using Protobuf, use our own definition of SyncStates to pass to onReceivedSyncState. |
| try { |
| this.onReceivedSyncState(syncStates, isRecovery); |
| } catch (ex) { |
| console.log("Error in onReceivedSyncState: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| |
| var n = new Name(this.applicationBroadcastPrefix); |
| n.append(this.digest_tree.getRoot()); |
| |
| var interest = new Interest(n); |
| interest.setInterestLifetimeMilliseconds(this.sync_lifetime); |
| |
| this.face.expressInterest(interest, this.onData.bind(this), this.syncTimeout.bind(this)); |
| }; |
| |
| /** |
| * Interest variable not actually in use here |
| */ |
| ChronoSync2013.prototype.initialTimeOut = function(interest) |
| { |
| if (!this.enabled) |
| // Ignore callbacks after the application calls shutdown(). |
| return; |
| |
| console.log("no other people"); |
| |
| this.usrseq++; |
| try { |
| this.onInitialized(); |
| } catch (ex) { |
| console.log("Error in onInitialized: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| var content = [new this.SyncState({ name:this.applicationDataPrefixUri, |
| type:'UPDATE', |
| seqno: { |
| seq:this.usrseq, |
| session:this.session |
| } |
| })]; |
| this.update(content); |
| var n = new Name(this.applicationBroadcastPrefix); |
| n.append(this.digest_tree.getRoot()); |
| var retryInterest = new Interest(n); |
| retryInterest.setInterestLifetimeMilliseconds(this.sync_lifetime); |
| |
| this.face.expressInterest(retryInterest, this.onData.bind(this), this.syncTimeout.bind(this)); |
| }; |
| |
| ChronoSync2013.prototype.processRecoveryInst = function(interest, syncdigest, face) |
| { |
| if (this.logfind(syncdigest) != -1) { |
| var content = []; |
| |
| for(var i = 0; i < this.digest_tree.digestnode.length; i++) { |
| content[i] = new this.SyncState({ name:this.digest_tree.digestnode[i].getDataPrefix(), |
| type:'UPDATE', |
| seqno:{ |
| seq:this.digest_tree.digestnode[i].getSequenceNo(), |
| session:this.digest_tree.digestnode[i].getSessionNo() |
| } |
| }); |
| } |
| |
| if (content.length != 0) { |
| var content_t = new this.SyncStateMsg({ss:content}); |
| var str = new Uint8Array(content_t.toArrayBuffer()); |
| var co = new Data(interest.getName()); |
| co.setContent(new Blob(str, false)); |
| if (interest.getName().get(-1).toEscapedString() == "00") |
| // Limit the lifetime of replies to interest for "00" since they can be different. |
| co.getMetaInfo().setFreshnessPeriod(1000); |
| |
| this.keyChain.sign(co, this.certificateName, function() { |
| try { |
| face.putData(co); |
| } catch (e) { |
| console.log(e.toString()); |
| } |
| }); |
| } |
| } |
| }; |
| |
| /** |
| * Common interest processing, using digest log to find the difference after syncdigest_t |
| * @return True if sent a data packet to satisfy the interest. |
| */ |
| ChronoSync2013.prototype.processSyncInst = function(index, syncdigest_t, face) |
| { |
| var content = []; |
| var data_name = []; |
| var data_seq = []; |
| var data_ses = []; |
| |
| for (var j = index + 1; j < this.digest_log.length; j++) { |
| var temp = this.digest_log[j].getData(); |
| for (var i = 0 ; i < temp.length ; i++) { |
| if (temp[i].type != 0) { |
| continue; |
| } |
| if (this.digest_tree.find(temp[i].name, temp[i].seqno.session) != -1) { |
| var n = data_name.indexOf(temp[i].name); |
| if (n == -1) { |
| data_name.push(temp[i].name); |
| data_seq.push(temp[i].seqno.seq); |
| data_ses.push(temp[i].seqno.session); |
| } |
| else { |
| data_seq[n] = temp[i].seqno.seq; |
| data_ses[n] = temp[i].seqno.session; |
| } |
| } |
| } |
| } |
| |
| for(var i = 0; i < data_name.length; i++) { |
| content[i] = new this.SyncState({ name:data_name[i], |
| type:'UPDATE', |
| seqno: { |
| seq:data_seq[i], |
| session:data_ses[i] |
| } |
| }); |
| } |
| if (content.length != 0) { |
| var content_t = new this.SyncStateMsg({ss:content}); |
| var str = new Uint8Array(content_t.toArrayBuffer()); |
| var n = new Name(this.prefix) |
| n.append(this.chatroom).append(syncdigest_t); |
| |
| var co = new Data(n); |
| co.setContent(new Blob(str, false)); |
| this.keyChain.sign(co, this.certificateName, function() { |
| try { |
| face.putData(co); |
| } |
| catch (e) { |
| console.log(e.toString()); |
| } |
| }); |
| } |
| }; |
| |
| /** |
| * Send recovery interset. |
| * @param {string} syncdigest_t |
| */ |
| ChronoSync2013.prototype.sendRecovery = function(syncdigest_t) |
| { |
| var n = new Name(this.applicationBroadcastPrefix); |
| n.append("recovery").append(syncdigest_t); |
| |
| var interest = new Interest(n); |
| |
| interest.setInterestLifetimeMilliseconds(this.sync_lifetime); |
| |
| this.face.expressInterest(interest, this.onData.bind(this), this.syncTimeout.bind(this)); |
| }; |
| |
| /** |
| * This is called by onInterest after a timeout to check if a recovery is needed. |
| * This method has an interest argument because we use it as the onTimeout for |
| * Face.expressInterest. |
| * @param {Interest} |
| * @param {string} |
| * @param {Face} |
| */ |
| ChronoSync2013.prototype.judgeRecovery = function(interest, syncdigest_t, face) |
| { |
| //console.log("*** judgeRecovery interest " + interest.getName().toUri() + " times out. Digest: " + syncdigest_t + " ***"); |
| var index = this.logfind(syncdigest_t); |
| if (index != -1) { |
| if (syncdigest_t != this.digest_tree.root) |
| this.processSyncInst(index, syncdigest_t, face); |
| } |
| else |
| this.sendRecovery(syncdigest_t); |
| }; |
| |
| ChronoSync2013.prototype.syncTimeout = function(interest) |
| { |
| if (!this.enabled) |
| // Ignore callbacks after the application calls shutdown(). |
| return; |
| |
| var component = interest.getName().get(4).toEscapedString(); |
| if (component == this.digest_tree.root) { |
| var n = new Name(interest.getName()); |
| var newInterest = new Interest(n); |
| |
| interest.setInterestLifetimeMilliseconds(this.sync_lifetime); |
| this.face.expressInterest(newInterest, this.onData.bind(this), this.syncTimeout.bind(this)); |
| } |
| }; |
| |
| ChronoSync2013.prototype.initialOndata = function(content) |
| { |
| this.update(content); |
| |
| var digest_t = this.digest_tree.getRoot(); |
| for (var i = 0; i < content.length; i++) { |
| if (content[i].name == this.applicationDataPrefixUri && content[i].seqno.session == this.session) { |
| //if the user was an old comer, after add the static log he need to increase his seqno by 1 |
| var content_t = [new this.SyncState({ name:this.applicationDataPrefixUri, |
| type:'UPDATE', |
| seqno: { |
| seq:content[i].seqno.seq + 1, |
| session:this.session |
| } |
| })]; |
| if (this.update(content_t)) { |
| var newlog = new ChronoSync2013.DigestLogEntry(this.digest_tree.getRoot(), content_t); |
| this.digest_log.push(newlog); |
| try { |
| this.onInitialized(); |
| } catch (ex) { |
| console.log("Error in onInitialized: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| } |
| } |
| |
| var content_t; |
| if (this.usrseq >= 0) { |
| //send the data packet with new seqno back |
| content_t = new this.SyncState({ name:this.applicationDataPrefixUri, |
| type:'UPDATE', |
| seqno: { |
| seq:this.usrseq, |
| session:this.session |
| } |
| }); |
| } |
| else |
| content_t = new this.SyncState({ name:this.applicationDataPrefixUri, |
| type:'UPDATE', |
| seqno: { |
| seq:0, |
| session:this.session |
| } |
| }); |
| var content_tt = new this.SyncStateMsg({ss:content_t}); |
| this.broadcastSyncState(digest_t, content_tt); |
| |
| if (this.digest_tree.find(this.applicationDataPrefixUri, this.session) == -1) { |
| //the user haven't put himself in the digest tree |
| this.usrseq++; |
| var content = [new this.SyncState({ name:this.applicationDataPrefixUri, |
| type:'UPDATE', |
| seqno: { |
| seq:this.usrseq, |
| session:this.session |
| } |
| })]; |
| if (this.update(content)) { |
| try { |
| this.onInitialized(); |
| } catch (ex) { |
| console.log("Error in onInitialized: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| } |
| }; |
| |
| ChronoSync2013.prototype.dummyOnData = function(interest, data) |
| { |
| console.log("*** dummyOnData called. ***"); |
| };/** |
| * This class represents the digest tree for chrono-sync2013. |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Zhehao Wang, based on Jeff T.'s implementation in ndn-cpp |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| // Use capitalized Crypto to not clash with the browser's crypto.subtle. |
| /** @ignore */ |
| var Crypto = require('../crypto.js'); |
| |
| /** |
| * @constructor |
| */ |
| var DigestTree = function DigestTree() |
| { |
| this.root = "00"; |
| this.digestnode = []; |
| }; |
| |
| exports.DigestTree = DigestTree; |
| |
| // The meaning of a session is explained here: |
| // http://named-data.net/doc/ndn-ccl-api/chrono-sync2013.html |
| // DigestTree.Node works with seqno_seq and seqno_session, without protobuf definition, |
| DigestTree.Node = function DigestTreeNode(dataPrefix, seqno_session, seqno_seq) |
| { |
| // In this context, this should mean DigestTree.Node instead |
| this.dataPrefix = dataPrefix; |
| this.seqno_session = seqno_session; |
| this.seqno_seq = seqno_seq; |
| |
| this.recomputeDigest(); |
| }; |
| |
| DigestTree.Node.prototype.getDataPrefix = function() |
| { |
| return this.dataPrefix; |
| }; |
| |
| DigestTree.Node.prototype.getSessionNo = function() |
| { |
| return this.seqno_session; |
| }; |
| |
| DigestTree.Node.prototype.getSequenceNo = function() |
| { |
| return this.seqno_seq; |
| }; |
| |
| DigestTree.Node.prototype.getDigest = function() |
| { |
| return this.digest; |
| }; |
| |
| DigestTree.Node.prototype.setSequenceNo = function(sequenceNo) |
| { |
| this.seqno_seq = sequenceNo; |
| this.recomputeDigest(); |
| }; |
| |
| // Using Node.JS buffer, as documented here http://nodejs.org/api/buffer.html. |
| DigestTree.Node.prototype.Int32ToBuffer = function(value) { |
| var result = new Buffer(4); |
| for (var i = 0; i < 4; i++) { |
| result[i] = value % 256; |
| value = Math.floor(value / 256); |
| } |
| return result; |
| } |
| |
| DigestTree.Node.prototype.recomputeDigest = function() |
| { |
| var seqHash = Crypto.createHash('sha256'); |
| |
| seqHash.update(this.Int32ToBuffer(this.seqno_session)); |
| seqHash.update(this.Int32ToBuffer(this.seqno_seq)); |
| |
| var digest_seq = seqHash.digest(); |
| |
| var nameHash = Crypto.createHash('sha256'); |
| nameHash.update(this.dataPrefix); |
| var digest_name = nameHash.digest(); |
| |
| var hash = Crypto.createHash('sha256'); |
| hash.update(digest_name); |
| hash.update(digest_seq); |
| |
| this.digest = hash.digest('hex'); |
| }; |
| |
| // Do the work of string and then sequence number compare |
| DigestTree.Node.Compare = function(node1, node2) |
| { |
| if (node1.dataPrefix != node2.dataPrefix) |
| return node1.dataPrefix < node2.dataPrefix; |
| return node1.seqno_session < node2.seqno_session; |
| }; |
| |
| /** |
| * Update the digest tree and recompute the root digest. If the combination of dataPrefix |
| * and sessionNo already exists in the tree then update its sequenceNo (only if the given |
| * sequenceNo is newer), otherwise add a new node. |
| * @param {string} The name prefix. |
| * @param {int} sessionNo The session number. |
| * @param {int} sequenceNo The sequence number. |
| * @return True if the digest tree is updated, false if not |
| */ |
| DigestTree.prototype.update = function(dataPrefix, sessionNo, sequenceNo) |
| { |
| var n_index = this.find(dataPrefix, sessionNo); |
| if (n_index >= 0) { |
| if (this.digestnode[n_index].getSequenceNo() < sequenceNo) |
| this.digestnode[n_index].setSequenceNo(sequenceNo); |
| else |
| return false; |
| } |
| else { |
| var temp = new DigestTree.Node(dataPrefix, sessionNo, sequenceNo); |
| this.digestnode.push(temp); |
| this.digestnode.sort(this.sortNodes); |
| } |
| this.recomputeRoot(); |
| return true; |
| }; |
| |
| // Need to confirm this sort works with the insertion in ndn-cpp. |
| DigestTree.prototype.sortNodes = function() |
| { |
| var temp; |
| for (var i = this.digestnode.length; i > 0; i--) { |
| for (var j = 0; j < i - 1; j++) { |
| if (this.digestnode[j].getDataPrefix() > this.digestnode[j + 1].getDataPrefix()) { |
| temp = this.digestnode[j]; |
| this.digestnode[j] = this.digestnode[j + 1]; |
| this.digestnode[j + 1] = temp; |
| } |
| } |
| } |
| }; |
| |
| DigestTree.prototype.sortNodes = function (node1, node2) |
| { |
| if (node1.getDataPrefix() == node2.getDataPrefix() && |
| node1.getSessionNo() == node2.getSessionNo()) |
| return 0; |
| |
| if ((node1.getDataPrefix() > node2.getDataPrefix()) || |
| ((node1.getDataPrefix() == node2.getDataPrefix()) && |
| (node1.getSessionNo() >node2.getSessionNo()))) |
| return 1; |
| else |
| return -1; |
| } |
| |
| DigestTree.prototype.find = function(dataPrefix, sessionNo) |
| { |
| for (var i = 0; i < this.digestnode.length; ++i) { |
| if (this.digestnode[i].getDataPrefix() == dataPrefix && |
| this.digestnode[i].getSessionNo() == sessionNo) |
| return i; |
| } |
| return -1; |
| }; |
| |
| DigestTree.prototype.size = function() |
| { |
| return this.digestnode.size(); |
| }; |
| |
| // Not really used |
| DigestTree.prototype.get = function(i) |
| { |
| return this.digestnode[i]; |
| }; |
| |
| DigestTree.prototype.getRoot = function() |
| { |
| return this.root; |
| }; |
| |
| DigestTree.prototype.recomputeRoot = function() |
| { |
| var md = Crypto.createHash('sha256'); |
| // The result of updateHex is related with the sequence of participants, |
| // I don't think that should be the case. |
| for (var i = 0; i < this.digestnode.length; i++) { |
| md.update(new Buffer(this.digestnode[i].digest, 'hex')); |
| } |
| this.root = md.digest('hex'); |
| }; |
| // Just define the SyncStateProto object. We do a Protobuf import dynamically |
| // when we need it so that protobufjs is optional. |
| var SyncStateProto = { |
| "package": "Sync", |
| "messages": [ |
| { |
| "name": "SyncState", |
| "fields": [ |
| { |
| "rule": "required", |
| "type": "string", |
| "name": "name", |
| "id": 1, |
| "options": {} |
| }, |
| { |
| "rule": "required", |
| "type": "ActionType", |
| "name": "type", |
| "id": 2, |
| "options": {} |
| }, |
| { |
| "rule": "optional", |
| "type": "SeqNo", |
| "name": "seqno", |
| "id": 3, |
| "options": {} |
| } |
| ], |
| "enums": [ |
| { |
| "name": "ActionType", |
| "values": [ |
| { |
| "name": "UPDATE", |
| "id": 0 |
| }, |
| { |
| "name": "DELETE", |
| "id": 1 |
| }, |
| { |
| "name": "OTHER", |
| "id": 2 |
| } |
| ], |
| "options": {} |
| } |
| ], |
| "messages": [ |
| { |
| "name": "SeqNo", |
| "fields": [ |
| { |
| "rule": "required", |
| "type": "uint32", |
| "name": "seq", |
| "id": 1, |
| "options": {} |
| }, |
| { |
| "rule": "required", |
| "type": "uint32", |
| "name": "session", |
| "id": 2, |
| "options": {} |
| } |
| ], |
| "enums": [], |
| "messages": [], |
| "options": {} |
| } |
| ], |
| "options": {} |
| }, |
| { |
| "name": "SyncStateMsg", |
| "fields": [ |
| { |
| "rule": "repeated", |
| "type": "SyncState", |
| "name": "ss", |
| "id": 1, |
| "options": {} |
| } |
| ], |
| "enums": [], |
| "messages": [], |
| "options": {} |
| } |
| ], |
| "enums": [], |
| "imports": [], |
| "options": {} |
| }; |
| |
| exports.SyncStateProto = SyncStateProto; |
| /** |
| * Copyright (C) 2014-2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| // Use capitalized Crypto to not clash with the browser's crypto.subtle. |
| /** @ignore */ |
| var Crypto = require('../crypto.js'); /** @ignore */ |
| var WireFormat = require('../encoding/wire-format.js').WireFormat; /** @ignore */ |
| var TlvEncoder = require('../encoding/tlv/tlv-encoder.js').TlvEncoder; /** @ignore */ |
| var Blob = require('./blob.js').Blob; |
| |
| /** |
| * A CommandInterestGenerator keeps track of a timestamp and generates command |
| * interests according to the NFD Signed Command Interests protocol: |
| * http://redmine.named-data.net/projects/nfd/wiki/Command_Interests |
| * |
| * Create a new CommandInterestGenerator and initialize the timestamp to now. |
| * @constructor |
| */ |
| var CommandInterestGenerator = function CommandInterestGenerator() |
| { |
| this.lastTimestamp = Math.round(new Date().getTime()); |
| }; |
| |
| exports.CommandInterestGenerator = CommandInterestGenerator; |
| |
| /** |
| * Append a timestamp component and a random value component to interest's name. |
| * This ensures that the timestamp is greater than the timestamp used in the |
| * previous call. Then use keyChain to sign the interest which appends a |
| * SignatureInfo component and a component with the signature bits. If the |
| * interest lifetime is not set, this sets it. |
| * @param {Interest} interest The interest whose name is append with components. |
| * @param {KeyChain} keyChain The KeyChain for calling sign. |
| * @param {Name} certificateName The certificate name of the key to use for |
| * signing. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * the SignatureInfo and to encode interest name for signing. If omitted, use |
| * WireFormat.getDefaultWireFormat(). |
| * @param {function} onComplete (optional) This calls onComplete() when complete. |
| * (Some crypto/database libraries only use a callback, so onComplete is |
| * required to use these.) |
| */ |
| CommandInterestGenerator.prototype.generate = function |
| (interest, keyChain, certificateName, wireFormat, onComplete) |
| { |
| onComplete = (typeof wireFormat === "function") ? wireFormat : onComplete; |
| wireFormat = (typeof wireFormat === "function" || !wireFormat) ? |
| WireFormat.getDefaultWireFormat() : wireFormat; |
| |
| var timestamp = Math.round(new Date().getTime()); |
| while (timestamp <= this.lastTimestamp) |
| timestamp += 1.0; |
| |
| // The timestamp is encoded as a TLV nonNegativeInteger. |
| var encoder = new TlvEncoder(8); |
| encoder.writeNonNegativeInteger(timestamp); |
| interest.getName().append(new Blob(encoder.getOutput(), false)); |
| |
| // The random value is a TLV nonNegativeInteger too, but we know it is 8 |
| // bytes, so we don't need to call the nonNegativeInteger encoder. |
| interest.getName().append(new Blob(Crypto.randomBytes(8), false)); |
| |
| // Update the timestamp before calling async sign. |
| this.lastTimestamp = timestamp; |
| |
| keyChain.sign(interest, certificateName, wireFormat, function() { |
| if (interest.getInterestLifetimeMilliseconds() == null || |
| interest.getInterestLifetimeMilliseconds() < 0) |
| // The caller has not set the interest lifetime, so set it here. |
| interest.setInterestLifetimeMilliseconds(1000.0); |
| |
| if (onComplete) |
| onComplete(); |
| }); |
| }; |
| /** |
| * Copyright (C) 2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var LOG = require('../log.js').Log.LOG; |
| |
| /** |
| * An InterestFilterTable is an internal class to hold a list of entries with |
| * an interest Filter and its OnInterestCallback. |
| * @constructor |
| */ |
| var InterestFilterTable = function InterestFilterTable() |
| { |
| this.table_ = []; // of Entry |
| }; |
| |
| exports.InterestFilterTable = InterestFilterTable; |
| |
| /** |
| * InterestFilterTable.Entry holds an interestFilterId, an InterestFilter and |
| * the OnInterestCallback with its related Face. |
| * Create a new Entry with the given values. |
| * @param {number} interestFilterId The ID from getNextEntryId(). |
| * @param {InterestFilter} filter The InterestFilter for this entry. |
| * @param {function} onInterest The callback to call. |
| * @param {Face} face The face on which was called registerPrefix or |
| * setInterestFilter which is passed to the onInterest callback. |
| * @constructor |
| */ |
| InterestFilterTable.Entry = function InterestFilterTableEntry |
| (interestFilterId, filter, onInterest, face) |
| { |
| this.interestFilterId_ = interestFilterId; |
| this.filter_ = filter; |
| this.onInterest_ = onInterest; |
| this.face_ = face; |
| }; |
| |
| /** |
| * Get the interestFilterId given to the constructor. |
| * @return {number} The interestFilterId. |
| */ |
| InterestFilterTable.Entry.prototype.getInterestFilterId = function() |
| { |
| return this.interestFilterId_; |
| }; |
| |
| /** |
| * Get the InterestFilter given to the constructor. |
| * @return {InterestFilter} The InterestFilter. |
| */ |
| InterestFilterTable.Entry.prototype.getFilter = function() |
| { |
| return this.filter_; |
| }; |
| |
| /** |
| * Get the onInterest callback given to the constructor. |
| * @return {function} The onInterest callback. |
| */ |
| InterestFilterTable.Entry.prototype.getOnInterest = function() |
| { |
| return this.onInterest_; |
| }; |
| |
| /** |
| * Get the Face given to the constructor. |
| * @return {Face} The Face. |
| */ |
| InterestFilterTable.Entry.prototype.getFace = function() |
| { |
| return this.face_; |
| }; |
| |
| /** |
| * Add a new entry to the table. |
| * @param {number} interestFilterId The ID from Node.getNextEntryId(). |
| * @param {InterestFilter} filter The InterestFilter for this entry. |
| * @param {function} onInterest The callback to call. |
| * @param {Face} face The face on which was called registerPrefix or |
| * setInterestFilter which is passed to the onInterest callback. |
| */ |
| InterestFilterTable.prototype.setInterestFilter = function |
| (interestFilterId, filter, onInterest, face) |
| { |
| this.table_.push(new InterestFilterTable.Entry |
| (interestFilterId, filter, onInterest, face)); |
| }; |
| |
| /** |
| * Find all entries from the interest filter table where the interest conforms |
| * to the entry's filter, and add to the matchedFilters list. |
| * @param {Interest} interest The interest which may match the filter in |
| * multiple entries. |
| * @param {Array<InterestFilterTable.Entry>} matchedFilters Add each matching |
| * InterestFilterTable.Entry from the interest filter table. The caller should |
| * pass in an empty array. |
| */ |
| InterestFilterTable.prototype.getMatchedFilters = function |
| (interest, matchedFilters) |
| { |
| for (var i = 0; i < this.table_.length; ++i) { |
| var entry = this.table_[i]; |
| if (entry.getFilter().doesMatch(interest.getName())) |
| matchedFilters.push(entry); |
| } |
| }; |
| |
| /** |
| * Remove the interest filter entry which has the interestFilterId from the |
| * interest filter table. This does not affect another interest filter with a |
| * different interestFilterId, even if it has the same prefix name. If there is |
| * no entry with the interestFilterId, do nothing. |
| * @param {number} interestFilterId The ID returned from setInterestFilter. |
| */ |
| InterestFilterTable.prototype.unsetInterestFilter = function(interestFilterId) |
| { |
| // Go backwards through the list so we can erase entries. |
| // Remove all entries even though interestFilterId should be unique. |
| var count = 0; |
| for (var i = this.table_.length - 1; i >= 0; --i) { |
| if (this.table_[i].getInterestFilterId() == interestFilterId) { |
| ++count; |
| this.table_.splice(i, 1); |
| } |
| } |
| |
| if (count === 0) |
| if (LOG > 0) console.log |
| ("unsetInterestFilter: Didn't find interestFilterId " + interestFilterId); |
| }; |
| /** |
| * Copyright (C) 2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var NdnCommon = require('../util/ndn-common.js').NdnCommon; /** @ignore */ |
| var LOG = require('../log.js').Log.LOG; |
| |
| /** |
| * A PendingInterestTable is an internal class to hold a list of pending |
| * interests with their callbacks. |
| * @constructor |
| */ |
| var PendingInterestTable = function PendingInterestTable() |
| { |
| this.table_ = []; // of Entry |
| this.removeRequests_ = []; // of number |
| }; |
| |
| exports.PendingInterestTable = PendingInterestTable; |
| |
| /** |
| * PendingInterestTable.Entry holds the callbacks and other fields for an entry |
| * in the pending interest table. |
| * Create a new Entry with the given fields. Note: You should not call this |
| * directly but call PendingInterestTable.add. |
| * @constructor |
| */ |
| PendingInterestTable.Entry = function PendingInterestTableEntry |
| (pendingInterestId, interest, onData, onTimeout, onNetworkNack) |
| { |
| this.pendingInterestId_ = pendingInterestId; |
| this.interest_ = interest; |
| this.onData_ = onData; |
| this.onTimeout_ = onTimeout; |
| this.onNetworkNack_ = onNetworkNack; |
| this.timerId_ = -1; |
| }; |
| |
| /** |
| * Get the pendingInterestId given to the constructor. |
| * @return {number} The pendingInterestId. |
| */ |
| PendingInterestTable.Entry.prototype.getPendingInterestId = function() |
| { |
| return this.pendingInterestId_; |
| }; |
| |
| /** |
| * Get the interest given to the constructor (from Face.expressInterest). |
| * @return {Interest} The interest. NOTE: You must not change the interest |
| * object - if you need to change it then make a copy. |
| */ |
| PendingInterestTable.Entry.prototype.getInterest = function() |
| { |
| return this.interest_; |
| }; |
| |
| /** |
| * Get the OnData callback given to the constructor. |
| * @return {function} The OnData callback. |
| */ |
| PendingInterestTable.Entry.prototype.getOnData = function() |
| { |
| return this.onData_; |
| }; |
| |
| /** |
| * Get the OnNetworkNack callback given to the constructor. |
| * @return {function} The OnNetworkNack callback. |
| */ |
| PendingInterestTable.Entry.prototype.getOnNetworkNack = function() |
| { |
| return this.onNetworkNack_; |
| }; |
| |
| /** |
| * Call onTimeout_ (if defined). This ignores exceptions from the call to |
| * onTimeout_. |
| */ |
| PendingInterestTable.Entry.prototype.callTimeout = function() |
| { |
| if (this.onTimeout_) { |
| try { |
| this.onTimeout_(this.interest_); |
| } catch (ex) { |
| console.log("Error in onTimeout: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| }; |
| |
| /** |
| * Call setTimeout(callback, milliseconds) and remember the timer ID. If the |
| * timer ID has already been set on a prevous call, do nothing. |
| */ |
| PendingInterestTable.Entry.prototype.setTimeout = function(callback, milliseconds) |
| { |
| if (this.timerId_ !== -1) |
| // Already set a timeout. |
| return; |
| this.timerId_ = setTimeout(callback, milliseconds); |
| }; |
| |
| /** |
| * Clear the timeout timer and reset the timer ID. |
| */ |
| PendingInterestTable.Entry.prototype.clearTimeout = function() |
| { |
| if (this.timerId_ !== -1) { |
| clearTimeout(this.timerId_); |
| this.timerId_ = -1; |
| } |
| }; |
| |
| /** |
| * Add a new entry to the pending interest table. Also set a timer to call the |
| * timeout. However, if removePendingInterest was already called with the |
| * pendingInterestId, don't add an entry and return null. |
| * @param {number} pendingInterestId |
| * @param {Interest} interestCopy |
| * @param {function} onData |
| * @param {function} onTimeout |
| * @param {function} onNetworkNack |
| * @return {PendingInterestTable.Entry} The new PendingInterestTable.Entry, or |
| * null if removePendingInterest was already called with the pendingInterestId. |
| */ |
| PendingInterestTable.prototype.add = function |
| (pendingInterestId, interestCopy, onData, onTimeout, onNetworkNack) |
| { |
| var removeRequestIndex = this.removeRequests_.indexOf(pendingInterestId); |
| if (removeRequestIndex >= 0) { |
| // removePendingInterest was called with the pendingInterestId returned by |
| // expressInterest before we got here, so don't add a PIT entry. |
| this.removeRequests_.splice(removeRequestIndex, 1); |
| return null; |
| } |
| |
| var entry = new PendingInterestTable.Entry |
| (pendingInterestId, interestCopy, onData, onTimeout, onNetworkNack); |
| this.table_.push(entry); |
| |
| // Set interest timer. |
| var timeoutMilliseconds = (interestCopy.getInterestLifetimeMilliseconds() || 4000); |
| var thisTable = this; |
| var timeoutCallback = function() { |
| if (LOG > 1) console.log("Interest time out: " + interestCopy.getName().toUri()); |
| |
| // Remove the entry from the table. |
| var index = thisTable.table_.indexOf(entry); |
| if (index >= 0) |
| thisTable.table_.splice(index, 1); |
| |
| entry.callTimeout(); |
| }; |
| |
| entry.setTimeout(timeoutCallback, timeoutMilliseconds); |
| return entry; |
| }; |
| |
| /** |
| * Find all entries from the pending interest table where data conforms to |
| * the entry's interest selectors, remove the entries from the table, and add to |
| * the entries list. |
| * @param {Data} data The incoming Data packet to find the interest for. |
| * @param {Array<PendingInterestTable.Entry>} entries Add matching |
| * PendingInterestTable.Entry from the pending interest table. The caller should |
| * pass in an empty array. |
| */ |
| PendingInterestTable.prototype.extractEntriesForExpressedInterest = function |
| (data, entries) |
| { |
| // Go backwards through the list so we can erase entries. |
| for (var i = this.table_.length - 1; i >= 0; --i) { |
| var pendingInterest = this.table_[i]; |
| if (pendingInterest.getInterest().matchesData(data)) { |
| pendingInterest.clearTimeout(); |
| entries.push(pendingInterest); |
| this.table_.splice(i, 1); |
| } |
| } |
| }; |
| |
| /** |
| * Find all entries from the pending interest table where the OnNetworkNack |
| * callback is not null and the entry's interest is the same as the given |
| * interest, remove the entries from the table, and add to the entries list. |
| * (We don't remove the entry if the OnNetworkNack callback is null so that |
| * OnTimeout will be called later.) The interests are the same if their default |
| * wire encoding is the same (which has everything including the name, nonce, |
| * link object and selectors). |
| * @param {Interest} interest The Interest to search for (typically from a Nack |
| * packet). |
| * @param {Array<PendingInterestTable.Entry>} entries Add matching |
| * PendingInterestTable.Entry from the pending interest table. The caller should |
| * pass in an empty array. |
| */ |
| PendingInterestTable.prototype.extractEntriesForNackInterest = function |
| (interest, entries) |
| { |
| var encoding = interest.wireEncode(); |
| |
| // Go backwards through the list so we can erase entries. |
| for (var i = this.table_.length - 1; i >= 0; --i) { |
| var pendingInterest = this.table_[i]; |
| if (pendingInterest.getOnNetworkNack() == null) |
| continue; |
| |
| // wireEncode returns the encoding cached when the interest was sent (if |
| // it was the default wire encoding). |
| if (pendingInterest.getInterest().wireEncode().equals(encoding)) { |
| pendingInterest.clearTimeout(); |
| entries.push(pendingInterest); |
| this.table_.splice(i, 1); |
| } |
| } |
| }; |
| |
| /** |
| * Remove the pending interest entry with the pendingInterestId from the pending |
| * interest table. This does not affect another pending interest with a |
| * different pendingInterestId, even if it has the same interest name. |
| * If there is no entry with the pendingInterestId, do nothing. |
| * @param {number} pendingInterestId The ID returned from expressInterest. |
| */ |
| PendingInterestTable.prototype.removePendingInterest = function |
| (pendingInterestId) |
| { |
| if (pendingInterestId == null) |
| return; |
| |
| // Go backwards through the list so we can erase entries. |
| // Remove all entries even though pendingInterestId should be unique. |
| var count = 0; |
| for (var i = this.table_.length - 1; i >= 0; --i) { |
| var entry = this.table_[i]; |
| if (entry.getPendingInterestId() == pendingInterestId) { |
| entry.clearTimeout(); |
| this.table_.splice(i, 1); |
| ++count; |
| } |
| } |
| |
| if (count === 0) |
| if (LOG > 0) console.log |
| ("removePendingInterest: Didn't find pendingInterestId " + pendingInterestId); |
| |
| if (count === 0) { |
| // The pendingInterestId was not found. Perhaps this has been called before |
| // the callback in expressInterest can add to the PIT. Add this |
| // removal request which will be checked before adding to the PIT. |
| if (this.removeRequests_.indexOf(pendingInterestId) < 0) |
| // Not already requested, so add the request. |
| this.removeRequests_.push(pendingInterestId); |
| } |
| }; |
| /** |
| * Copyright (C) 2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var LOG = require('../log.js').Log.LOG; |
| |
| /** |
| * A RegisteredPrefixTable is an internal class to hold a list of registered |
| * prefixes with information necessary to remove the registration later. |
| * @param {InterestFilterTable} interestFilterTable See removeRegisteredPrefix(), |
| * which may call interestFilterTable.unsetInterestFilter(). |
| * @constructor |
| */ |
| var RegisteredPrefixTable = function RegisteredPrefixTable(interestFilterTable) |
| { |
| this.interestFilterTable_ = interestFilterTable; |
| this.table_ = []; // of Entry |
| this.removeRequests_ = []; // of number |
| }; |
| |
| exports.RegisteredPrefixTable = RegisteredPrefixTable; |
| |
| /** |
| * Add a new entry to the table. However, if removeRegisteredPrefix was already |
| * called with the registeredPrefixId, don't add an entry and return false. |
| * @param {number} registeredPrefixId The ID from Node.getNextEntryId(). |
| * @param {Name} prefix The name prefix. |
| * @param {number} relatedInterestFilterId (optional) The related |
| * interestFilterId for the filter set in the same registerPrefix operation. If |
| * omitted, set to 0. |
| * return {boolean} True if added an entry, false if removeRegisteredPrefix was |
| * already called with the registeredPrefixId. |
| */ |
| RegisteredPrefixTable.prototype.add = function |
| (registeredPrefixId, prefix, relatedInterestFilterId) |
| { |
| var removeRequestIndex = this.removeRequests_.indexOf(registeredPrefixId); |
| if (removeRequestIndex >= 0) { |
| // removeRegisteredPrefix was called with the registeredPrefixId returned by |
| // registerPrefix before we got here, so don't add a registered prefix |
| // table entry. |
| this.removeRequests_.splice(removeRequestIndex, 1); |
| return false; |
| } |
| |
| this.table_.push(new RegisteredPrefixTable._Entry |
| (registeredPrefixId, prefix, relatedInterestFilterId)); |
| return true; |
| }; |
| |
| /** |
| * Remove the registered prefix entry with the registeredPrefixId from the |
| * registered prefix table. This does not affect another registered prefix with |
| * a different registeredPrefixId, even if it has the same prefix name. If an |
| * interest filter was automatically created by registerPrefix, also call |
| * interestFilterTable_.unsetInterestFilter to remove it. |
| * If there is no entry with the registeredPrefixId, do nothing. |
| * @param {number} registeredPrefixId The ID returned from registerPrefix. |
| */ |
| RegisteredPrefixTable.prototype.removeRegisteredPrefix = function |
| (registeredPrefixId) |
| { |
| // Go backwards through the list so we can erase entries. |
| // Remove all entries even though registeredPrefixId should be unique. |
| var count = 0; |
| for (var i = this.table_.length - 1; i >= 0; --i) { |
| var entry = this.table_[i]; |
| if (entry.getRegisteredPrefixId() == registeredPrefixId) { |
| ++count; |
| |
| if (entry.getRelatedInterestFilterId() > 0) |
| // Remove the related interest filter. |
| this.interestFilterTable_.unsetInterestFilter |
| (entry.getRelatedInterestFilterId()); |
| |
| this.table_.splice(i, 1); |
| } |
| } |
| |
| if (count === 0) |
| if (LOG > 0) console.log |
| ("removeRegisteredPrefix: Didn't find registeredPrefixId " + registeredPrefixId); |
| |
| if (count === 0) { |
| // The registeredPrefixId was not found. Perhaps this has been called before |
| // the callback in registerPrefix can add to the registered prefix table. |
| // Add this removal request which will be checked before adding to the |
| // registered prefix table. |
| if (this.removeRequests_.indexOf(registeredPrefixId) < 0) |
| // Not already requested, so add the request. |
| this.removeRequests_.push(registeredPrefixId); |
| } |
| }; |
| |
| /** |
| * RegisteredPrefixTable._Entry holds a registeredPrefixId and information |
| * necessary to remove the registration later. It optionally holds a related |
| * interestFilterId if the InterestFilter was set in the same registerPrefix |
| * operation. |
| * Create a RegisteredPrefixTable.Entry with the given values. |
| * @param {number} registeredPrefixId The ID from Node.getNextEntryId(). |
| * @param {Name} prefix The name prefix. |
| * @param {number} relatedInterestFilterId (optional) The related |
| * interestFilterId for the filter set in the same registerPrefix operation. If |
| * omitted, set to 0. |
| * @constructor |
| */ |
| RegisteredPrefixTable._Entry = function RegisteredPrefixTableEntry |
| (registeredPrefixId, prefix, relatedInterestFilterId) |
| { |
| this.registeredPrefixId_ = registeredPrefixId; |
| this.prefix_ = prefix; |
| this.relatedInterestFilterId_ = relatedInterestFilterId; |
| }; |
| |
| /** |
| * Get the registeredPrefixId given to the constructor. |
| * @return {number} The registeredPrefixId. |
| */ |
| RegisteredPrefixTable._Entry.prototype.getRegisteredPrefixId = function() |
| { |
| return this.registeredPrefixId; |
| }; |
| |
| /** |
| * Get the name prefix given to the constructor. |
| * @return {Name} The name prefix. |
| */ |
| RegisteredPrefixTable._Entry.prototype.getPrefix = function() |
| { |
| return this.prefix; |
| }; |
| |
| /** |
| * Get the related interestFilterId given to the constructor. |
| * @return {number} The related interestFilterId. |
| */ |
| RegisteredPrefixTable._Entry.prototype.getRelatedInterestFilterId = function() |
| { |
| return this.relatedInterestFilterId; |
| }; |
| /** |
| * Copyright (C) 2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From ndn-cxx fields.hpp https://github.com/named-data/ndn-cxx/blob/master/src/lp/fields.hpp |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** |
| * IncomingFaceId represents the incoming face ID header field in an NDNLPv2 packet. |
| * http://redmine.named-data.net/projects/nfd/wiki/NDNLPv2 |
| * @constructor |
| */ |
| var IncomingFaceId = function IncomingFaceId() |
| { |
| this.faceId_ = null; |
| }; |
| |
| exports.IncomingFaceId = IncomingFaceId; |
| |
| /** |
| * Get the incoming face ID value. |
| * @return {number} The face ID value. |
| */ |
| IncomingFaceId.prototype.getFaceId = function() { return this.faceId_; }; |
| |
| /** |
| * Set the face ID value. |
| * @param {number} faceId The incoming face ID value. |
| */ |
| IncomingFaceId.prototype.setFaceId = function(faceId) |
| { |
| this.faceId_ = faceId; |
| }; |
| |
| /** |
| * Get the first header field in lpPacket which is an IncomingFaceId. This is |
| * an internal method which the application normally would not use. |
| * @param {LpPacket} lpPacket The LpPacket with the header fields to search. |
| * @return {IncomingFaceId} The first IncomingFaceId header field, or null if |
| * not found. |
| */ |
| IncomingFaceId.getFirstHeader = function(lpPacket) |
| { |
| for (var i = 0; i < lpPacket.countHeaderFields(); ++i) { |
| var field = lpPacket.getHeaderField(i); |
| if (field instanceof IncomingFaceId) |
| return field; |
| } |
| |
| return null; |
| }; |
| /** |
| * Copyright (C) 2016 Regents of the University of California. |
| * @author: Jeff Thompson <jefft0@remap.ucla.edu> |
| * @author: From ndn-cxx packet.hpp https://github.com/named-data/ndn-cxx/blob/master/src/lp/packet.hpp |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var Blob = require('../util/blob.js').Blob; |
| |
| /** |
| * An LpPacket represents an NDNLPv2 packet including header fields an an |
| * optional fragment. This is an internal class which the application normally |
| * would not use. |
| * http://redmine.named-data.net/projects/nfd/wiki/NDNLPv2 |
| * @constructor |
| */ |
| var LpPacket = function LpPacket() |
| { |
| this.headerFields_ = []; |
| this.fragmentWireEncoding_ = new Blob(); |
| }; |
| |
| exports.LpPacket = LpPacket; |
| |
| /** |
| * Get the fragment wire encoding. |
| * @return {Blob} The wire encoding, or an isNull Blob if not specified. |
| */ |
| LpPacket.prototype.getFragmentWireEncoding = function() |
| { |
| return this.fragmentWireEncoding_; |
| }; |
| |
| /** |
| * Get the number of header fields. This does not include the fragment. |
| * @return {number} The number of header fields. |
| */ |
| LpPacket.prototype.countHeaderFields = function() |
| { |
| return this.headerFields_.length; |
| }; |
| |
| /** |
| * Get the header field at the given index. |
| * @param {number} index The index, starting from 0. It is an error if index is |
| * greater to or equal to countHeaderFields(). |
| * @return {object} The header field at the index. |
| */ |
| LpPacket.prototype.getHeaderField = function(index) |
| { |
| return this.headerFields_[index]; |
| }; |
| |
| /** |
| * Remove all header fields and set the fragment to an isNull Blob. |
| */ |
| LpPacket.prototype.clear = function() |
| { |
| this.headerFields_ = []; |
| this.fragmentWireEncoding_ = new Blob(); |
| }; |
| |
| /** |
| * Set the fragment wire encoding. |
| * @param {Blob} fragmentWireEncoding The fragment wire encoding or an isNull |
| * Blob if not specified. |
| */ |
| LpPacket.prototype.setFragmentWireEncoding = function(fragmentWireEncoding) |
| { |
| this.fragmentWireEncoding_ = |
| typeof fragmentWireEncoding === 'object' && fragmentWireEncoding instanceof Blob ? |
| fragmentWireEncoding : new Blob(fragmentWireEncoding); |
| }; |
| |
| /** |
| * Add a header field. To add the fragment, use setFragmentWireEncoding(). |
| * @param {object} headerField The header field to add. |
| */ |
| LpPacket.prototype.addHeaderField = function(headerField) |
| { |
| this.headerFields_.push(headerField); |
| }; |
| /** |
| * This class represents the top-level object for communicating with an NDN host. |
| * Copyright (C) 2013-2016 Regents of the University of California. |
| * @author: Meki Cherkaoui, Jeff Thompson <jefft0@remap.ucla.edu>, Wentao Shang |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * A copy of the GNU Lesser General Public License is in the file COPYING. |
| */ |
| |
| /** @ignore */ |
| var DataUtils = require('./encoding/data-utils.js').DataUtils; /** @ignore */ |
| var Name = require('./name.js').Name; /** @ignore */ |
| var Interest = require('./interest.js').Interest; /** @ignore */ |
| var Data = require('./data.js').Data; /** @ignore */ |
| var ControlParameters = require('./control-parameters.js').ControlParameters; /** @ignore */ |
| var ControlResponse = require('./control-response.js').ControlResponse; /** @ignore */ |
| var InterestFilter = require('./interest-filter.js').InterestFilter; /** @ignore */ |
| var WireFormat = require('./encoding/wire-format.js').WireFormat; /** @ignore */ |
| var TlvWireFormat = require('./encoding/tlv-wire-format.js').TlvWireFormat; /** @ignore */ |
| var Tlv = require('./encoding/tlv/tlv.js').Tlv; /** @ignore */ |
| var TlvDecoder = require('./encoding/tlv/tlv-decoder.js').TlvDecoder; /** @ignore */ |
| var ForwardingFlags = require('./forwarding-flags.js').ForwardingFlags; /** @ignore */ |
| var Transport = require('./transport/transport.js').Transport; /** @ignore */ |
| var TcpTransport = require('./transport/tcp-transport.js').TcpTransport; /** @ignore */ |
| var UnixTransport = require('./transport/unix-transport.js').UnixTransport; /** @ignore */ |
| var CommandInterestGenerator = require('./util/command-interest-generator.js').CommandInterestGenerator; /** @ignore */ |
| var Blob = require('./util/blob.js').Blob; /** @ignore */ |
| var NdnCommon = require('./util/ndn-common.js').NdnCommon; /** @ignore */ |
| var NetworkNack = require('./network-nack.js').NetworkNack; /** @ignore */ |
| var LpPacket = require('./lp/lp-packet.js').LpPacket; /** @ignore */ |
| var InterestFilterTable = require('./impl/interest-filter-table.js').InterestFilterTable; /** @ignore */ |
| var PendingInterestTable = require('./impl/pending-interest-table.js').PendingInterestTable; /** @ignore */ |
| var RegisteredPrefixTable = require('./impl/registered-prefix-table.js').RegisteredPrefixTable; /** @ignore */ |
| var fs = require('fs'); /** @ignore */ |
| var LOG = require('./log.js').Log.LOG; |
| |
| /** |
| * Create a new Face with the given settings. |
| * This throws an exception if Face.supported is false. |
| * There are two forms of the constructor. The first form takes the transport and connectionInfo: |
| * Face(transport, connectionInfo). The second form takes an optional settings object: |
| * Face([settings]). |
| * @constructor |
| * @param {Transport} transport An object of a subclass of Transport to use for |
| * communication. |
| * @param {Transport.ConnectionInfo} connectionInfo This must be a ConnectionInfo |
| * from the same subclass of Transport as transport. If omitted and transport is |
| * a new UnixTransport() then attempt to create to the Unix socket for the local |
| * forwarder. |
| * @param {Object} settings (optional) An associative array with the following defaults: |
| * { |
| * getTransport: function() { return new WebSocketTransport(); }, // If in the browser. |
| * OR function() { return new TcpTransport(); }, // If in Node.js. |
| * // If getTransport creates a UnixTransport and connectionInfo is null, |
| * // then connect to the local forwarder's Unix socket. |
| * getConnectionInfo: transport.defaultGetConnectionInfo, // a function, on each call it returns a new Transport.ConnectionInfo or null if there are no more hosts. |
| * // If connectionInfo or host is not null, getConnectionInfo is ignored. |
| * connectionInfo: null, |
| * host: null, // If null and connectionInfo is null, use getConnectionInfo when connecting. |
| * // However, if connectionInfo is not null, use it instead. |
| * port: 9696, // If in the browser. |
| * OR 6363, // If in Node.js. |
| * // However, if connectionInfo is not null, use it instead. |
| * onopen: function() { if (LOG > 3) console.log("NDN connection established."); }, |
| * onclose: function() { if (LOG > 3) console.log("NDN connection closed."); }, |
| * } |
| */ |
| var Face = function Face(transportOrSettings, connectionInfo) |
| { |
| if (!Face.supported) |
| throw new Error("The necessary JavaScript support is not available on this platform."); |
| |
| var settings; |
| if (typeof transportOrSettings == 'object' && transportOrSettings instanceof Transport) { |
| this.getConnectionInfo = null; |
| this.transport = transportOrSettings; |
| this.connectionInfo = (connectionInfo || null); |
| // Use defaults for other settings. |
| settings = {}; |
| |
| if (this.connectionInfo == null) { |
| if (this.transport && this.transport.__proto__ && |
| this.transport.__proto__.name == "UnixTransport") { |
| // Try to create the default connectionInfo for UnixTransport. |
| var filePath = Face.getUnixSocketFilePathForLocalhost(); |
| if (filePath != null) |
| this.connectionInfo = new UnixTransport.ConnectionInfo(filePath); |
| else |
| console.log |
| ("Face constructor: Cannot determine the default Unix socket file path for UnixTransport"); |
| if (LOG > 0) |
| console.log("Using " + this.connectionInfo.toString()); |
| } |
| } |
| } |
| else { |
| settings = (transportOrSettings || {}); |
| // For the browser, browserify-tcp-transport.js replaces TcpTransport with WebSocketTransport. |
| var getTransport = (settings.getTransport || function() { return new TcpTransport(); }); |
| this.transport = getTransport(); |
| this.getConnectionInfo = (settings.getConnectionInfo || this.transport.defaultGetConnectionInfo); |
| |
| this.connectionInfo = (settings.connectionInfo || null); |
| if (this.connectionInfo == null) { |
| var host = (settings.host !== undefined ? settings.host : null); |
| |
| if (this.transport && this.transport.__proto__ && |
| this.transport.__proto__.name == "UnixTransport") { |
| // We are using UnixTransport on Node.js. There is no IP-style host and port. |
| if (host != null) |
| // Assume the host is the local Unix socket path. |
| this.connectionInfo = new UnixTransport.ConnectionInfo(host); |
| else { |
| // If getConnectionInfo is not null, it will be used instead so no |
| // need to set this.connectionInfo. |
| if (this.getConnectionInfo == null) { |
| var filePath = Face.getUnixSocketFilePathForLocalhost(); |
| if (filePath != null) |
| this.connectionInfo = new UnixTransport.ConnectionInfo(filePath); |
| else |
| console.log |
| ("Face constructor: Cannot determine the default Unix socket file path for UnixTransport"); |
| } |
| } |
| } |
| else { |
| if (host != null) { |
| if (typeof WebSocketTransport != 'undefined') |
| this.connectionInfo = new WebSocketTransport.ConnectionInfo |
| (host, settings.port || 9696); |
| else |
| this.connectionInfo = new TcpTransport.ConnectionInfo |
| (host, settings.port || 6363); |
| } |
| } |
| } |
| } |
| |
| // Deprecated: Set this.host and this.port for backwards compatibility. |
| if (this.connectionInfo == null) { |
| this.host = null; |
| this.host = null; |
| } |
| else { |
| this.host = this.connectionInfo.host; |
| this.host = this.connectionInfo.port; |
| } |
| |
| this.readyStatus = Face.UNOPEN; |
| |
| // Event handler |
| this.onopen = (settings.onopen || function() { if (LOG > 3) console.log("Face connection established."); }); |
| this.onclose = (settings.onclose || function() { if (LOG > 3) console.log("Face connection closed."); }); |
| // This is used by reconnectAndExpressInterest. |
| this.onConnectedCallbacks = []; |
| this.commandKeyChain = null; |
| this.commandCertificateName = new Name(); |
| this.commandInterestGenerator = new CommandInterestGenerator(); |
| this.timeoutPrefix = new Name("/local/timeout"); |
| |
| this.pendingInterestTable_ = new PendingInterestTable(); |
| this.interestFilterTable_ = new InterestFilterTable(); |
| this.registeredPrefixTable_ = new RegisteredPrefixTable(this.interestFilterTable_); |
| this.lastEntryId = 0; |
| }; |
| |
| exports.Face = Face; |
| |
| Face.UNOPEN = 0; // the Face is created but not opened yet |
| Face.OPEN_REQUESTED = 1; // requested to connect but onopen is not called. |
| Face.OPENED = 2; // connection to the forwarder opened |
| Face.CLOSED = 3; // connection to the forwarder closed |
| |
| TcpTransport.importFace(Face); |
| |
| /** |
| * If the forwarder's Unix socket file path exists, then return the file path. |
| * Otherwise return an empty string. This uses Node.js blocking file system |
| * utilities. |
| * @return The Unix socket file path to use, or an empty string. |
| */ |
| Face.getUnixSocketFilePathForLocalhost = function() |
| { |
| var filePath = "/var/run/nfd.sock"; |
| if (fs.existsSync(filePath)) |
| return filePath; |
| else { |
| filePath = "/tmp/.ndnd.sock"; |
| if (fs.existsSync(filePath)) |
| return filePath; |
| else |
| return ""; |
| } |
| } |
| |
| /** |
| * Return true if necessary JavaScript support is available, else log an error and return false. |
| */ |
| Face.getSupported = function() |
| { |
| try { |
| var dummy = new Buffer(1).slice(0, 1); |
| } |
| catch (ex) { |
| console.log("NDN not available: Buffer not supported. " + ex); |
| return false; |
| } |
| |
| return true; |
| }; |
| |
| Face.supported = Face.getSupported(); |
| |
| Face.prototype.createRoute = function(hostOrConnectionInfo, port) |
| { |
| if (hostOrConnectionInfo instanceof Transport.ConnectionInfo) |
| this.connectionInfo = hostOrConnectionInfo; |
| else |
| this.connectionInfo = new TcpTransport.ConnectionInfo(hostOrConnectionInfo, port); |
| |
| // Deprecated: Set this.host and this.port for backwards compatibility. |
| this.host = this.connectionInfo.host; |
| this.host = this.connectionInfo.port; |
| }; |
| |
| Face.prototype.close = function() |
| { |
| if (this.readyStatus != Face.OPENED) |
| return; |
| |
| this.readyStatus = Face.CLOSED; |
| this.transport.close(); |
| }; |
| |
| /** |
| * An internal method to get the next unique entry ID for the pending interest |
| * table, interest filter table, etc. Most entry IDs are for the pending |
| * interest table (there usually are not many interest filter table entries) so |
| * we use a common pool to only have to have one method which is called by Face. |
| * |
| * @return {number} The next entry ID. |
| */ |
| Face.prototype.getNextEntryId = function() |
| { |
| return ++this.lastEntryId; |
| }; |
| |
| /** |
| * Return a function that selects a host at random from hostList and returns |
| * makeConnectionInfo(host, port), and if no more hosts remain, return null. |
| * @param {Array<string>} hostList An array of host names. |
| * @param {number} port The port for the connection. |
| * @param {function} makeConnectionInfo This calls makeConnectionInfo(host, port) |
| * to make the Transport.ConnectionInfo. For example: |
| * function(host, port) { return new TcpTransport.ConnectionInfo(host, port); } |
| * @return {function} A function which returns a Transport.ConnectionInfo. |
| */ |
| Face.makeShuffledHostGetConnectionInfo = function(hostList, port, makeConnectionInfo) |
| { |
| // Make a copy. |
| hostList = hostList.slice(0, hostList.length); |
| DataUtils.shuffle(hostList); |
| |
| return function() { |
| if (hostList.length == 0) |
| return null; |
| |
| return makeConnectionInfo(hostList.splice(0, 1)[0], port); |
| }; |
| }; |
| |
| /** |
| * Send the interest through the transport, read the entire response and call |
| * onData, onTimeout or onNetworkNack as described below. |
| * There are two forms of expressInterest. The first form takes the exact |
| * interest (including lifetime): |
| * expressInterest(interest, onData [, onTimeout] [, onNetworkNack] [, wireFormat]). |
| * The second form creates the interest from a name and optional interest template: |
| * expressInterest(name [, template], onData [, onTimeout] [, onNetworkNack] [, wireFormat]). |
| * @param {Interest} interest The Interest to send which includes the interest lifetime for the timeout. |
| * @param {function} onData When a matching data packet is received, this calls onData(interest, data) where |
| * interest is the interest given to expressInterest and data is the received |
| * Data object. NOTE: You must not change the interest object - if you need to |
| * change it then make a copy. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onTimeout (optional) If the interest times out according to the interest lifetime, |
| * this calls onTimeout(interest) where: |
| * interest is the interest given to expressInterest. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onNetworkNack (optional) When a network Nack packet for the |
| * interest is received and onNetworkNack is not null, this calls |
| * onNetworkNack(interest, networkNack) and does not call onTimeout. interest is |
| * the sent Interest and networkNack is the received NetworkNack. If |
| * onNetworkNack is supplied, then onTimeout must be supplied too. However, if a |
| * network Nack is received and onNetworkNack is null, do nothing and wait for |
| * the interest to time out. (Therefore, an application which does not yet |
| * process a network Nack reason treats a Nack the same as a timeout.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {Name} name The Name for the interest. (only used for the second form of expressInterest). |
| * @param {Interest} template (optional) If not omitted, copy the interest selectors from this Interest. |
| * If omitted, use a default interest lifetime. (only used for the second form of expressInterest). |
| * @param {WireFormat} (optional) A WireFormat object used to encode the message. |
| * If omitted, use WireFormat.getDefaultWireFormat(). |
| * @return {number} The pending interest ID which can be used with removePendingInterest. |
| * @throws Error If the encoded interest size exceeds Face.getMaxNdnPacketSize(). |
| */ |
| Face.prototype.expressInterest = function |
| (interestOrName, arg2, arg3, arg4, arg5, arg6) |
| { |
| var interest; |
| if (typeof interestOrName === 'object' && interestOrName instanceof Interest) |
| // Just use a copy of the interest. |
| interest = new Interest(interestOrName); |
| else { |
| // The first argument is a name. Make the interest from the name and possible template. |
| if (arg2 && typeof arg2 === 'object' && arg2 instanceof Interest) { |
| var template = arg2; |
| // Copy the template. |
| interest = new Interest(template); |
| interest.setName(interestOrName); |
| |
| // Shift the remaining args to be processed below. |
| arg2 = arg3; |
| arg3 = arg4; |
| arg4 = arg5; |
| arg5 = arg6; |
| } |
| else { |
| // No template. |
| interest = new Interest(interestOrName); |
| interest.setInterestLifetimeMilliseconds(4000); // default interest timeout |
| } |
| } |
| |
| var onData = arg2; |
| var onTimeout; |
| var onNetworkNack; |
| var wireFormat; |
| // arg3, arg4, arg5 may be: |
| // OnTimeout, OnNetworkNack, WireFormat |
| // OnTimeout, OnNetworkNack, null |
| // OnTimeout, WireFormat, null |
| // OnTimeout, null, null |
| // WireFormat, null, null |
| // null, null, null |
| if (typeof arg3 === "function") |
| onTimeout = arg3; |
| else |
| onTimeout = function() {}; |
| |
| if (typeof arg4 === "function") |
| onNetworkNack = arg4; |
| else |
| onNetworkNack = null; |
| |
| if (arg3 instanceof WireFormat) |
| wireFormat = arg3; |
| else if (arg4 instanceof WireFormat) |
| wireFormat = arg4; |
| else if (arg5 instanceof WireFormat) |
| wireFormat = arg5; |
| else |
| wireFormat = WireFormat.getDefaultWireFormat(); |
| |
| var pendingInterestId = this.getNextEntryId(); |
| |
| // Set the nonce in our copy of the Interest so it is saved in the PIT. |
| interest.setNonce(Face.nonceTemplate_); |
| interest.refreshNonce(); |
| |
| if (this.connectionInfo == null) { |
| if (this.getConnectionInfo == null) |
| console.log('ERROR: connectionInfo is NOT SET'); |
| else { |
| var thisFace = this; |
| this.connectAndExecute(function() { |
| thisFace.reconnectAndExpressInterest |
| (pendingInterestId, interest, onData, onTimeout, onNetworkNack, |
| wireFormat); |
| }); |
| } |
| } |
| else |
| this.reconnectAndExpressInterest |
| (pendingInterestId, interest, onData, onTimeout, onNetworkNack, wireFormat); |
| |
| return pendingInterestId; |
| }; |
| |
| /** |
| * If the host and port are different than the ones in this.transport, then call |
| * this.transport.connect to change the connection (or connect for the first time). |
| * Then call expressInterestHelper. |
| */ |
| Face.prototype.reconnectAndExpressInterest = function |
| (pendingInterestId, interest, onData, onTimeout, onNetworkNack, wireFormat) |
| { |
| var thisFace = this; |
| if (!this.connectionInfo.equals(this.transport.connectionInfo) || this.readyStatus === Face.UNOPEN) { |
| this.readyStatus = Face.OPEN_REQUESTED; |
| this.onConnectedCallbacks.push |
| (function() { |
| thisFace.expressInterestHelper |
| (pendingInterestId, interest, onData, onTimeout, onNetworkNack, |
| wireFormat); |
| }); |
| |
| this.transport.connect |
| (this.connectionInfo, this, |
| function() { |
| thisFace.readyStatus = Face.OPENED; |
| |
| // Execute each action requested while the connection was opening. |
| while (thisFace.onConnectedCallbacks.length > 0) { |
| try { |
| thisFace.onConnectedCallbacks.shift()(); |
| } catch (ex) { |
| console.log("Face.reconnectAndExpressInterest: ignoring exception from onConnectedCallbacks: " + ex); |
| } |
| } |
| |
| if (thisFace.onopen) |
| // Call Face.onopen after success |
| thisFace.onopen(); |
| }, |
| function() { thisFace.closeByTransport(); }); |
| } |
| else { |
| if (this.readyStatus === Face.OPEN_REQUESTED) |
| // The connection is still opening, so add to the interests to express. |
| this.onConnectedCallbacks.push |
| (function() { |
| thisFace.expressInterestHelper |
| (pendingInterestId, interest, onData, onTimeout, onNetworkNack, |
| wireFormat); |
| }); |
| else if (this.readyStatus === Face.OPENED) |
| this.expressInterestHelper |
| (pendingInterestId, interest, onData, onTimeout, onNetworkNack, |
| wireFormat); |
| else |
| throw new Error |
| ("reconnectAndExpressInterest: unexpected connection is not opened"); |
| } |
| }; |
| |
| /** |
| * Do the work of reconnectAndExpressInterest once we know we are connected. |
| * Add the PendingInterest and call this.transport.send to send the interest. |
| */ |
| Face.prototype.expressInterestHelper = function |
| (pendingInterestId, interest, onData, onTimeout, onNetworkNack, wireFormat) |
| { |
| if (this.pendingInterestTable_.add |
| (pendingInterestId, interest, onData, onTimeout, onNetworkNack) == null) |
| // removePendingInterest was already called with the pendingInterestId. |
| return; |
| |
| // Special case: For timeoutPrefix we don't actually send the interest. |
| if (!this.timeoutPrefix.match(interest.getName())) { |
| var binaryInterest = interest.wireEncode(wireFormat); |
| if (binaryInterest.size() > Face.getMaxNdnPacketSize()) |
| throw new Error |
| ("The encoded interest size exceeds the maximum limit getMaxNdnPacketSize()"); |
| |
| this.transport.send(binaryInterest.buf()); |
| } |
| }; |
| |
| /** |
| * Remove the pending interest entry with the pendingInterestId from the pending |
| * interest table. This does not affect another pending interest with a |
| * different pendingInterestId, even if it has the same interest name. |
| * If there is no entry with the pendingInterestId, do nothing. |
| * @param {number} pendingInterestId The ID returned from expressInterest. |
| */ |
| Face.prototype.removePendingInterest = function(pendingInterestId) |
| { |
| this.pendingInterestTable_.removePendingInterest(pendingInterestId); |
| }; |
| |
| /** |
| * Set the KeyChain and certificate name used to sign command interests (e.g. |
| * for registerPrefix). |
| * @param {KeyChain} keyChain The KeyChain object for signing interests, which |
| * must remain valid for the life of this Face. You must create the KeyChain |
| * object and pass it in. You can create a default KeyChain for your system with |
| * the default KeyChain constructor. |
| * @param {Name} certificateName The certificate name for signing interests. |
| * This makes a copy of the Name. You can get the default certificate name with |
| * keyChain.getDefaultCertificateName() . |
| */ |
| Face.prototype.setCommandSigningInfo = function(keyChain, certificateName) |
| { |
| this.commandKeyChain = keyChain; |
| this.commandCertificateName = new Name(certificateName); |
| }; |
| |
| /** |
| * Set the certificate name used to sign command interest (e.g. for |
| * registerPrefix), using the KeyChain that was set with setCommandSigningInfo. |
| * @param {Name} certificateName The certificate name for signing interest. This |
| * makes a copy of the Name. |
| */ |
| Face.prototype.setCommandCertificateName = function(certificateName) |
| { |
| this.commandCertificateName = new Name(certificateName); |
| }; |
| |
| /** |
| * Append a timestamp component and a random value component to interest's name. |
| * Then use the keyChain and certificateName from setCommandSigningInfo to sign |
| * the interest. If the interest lifetime is not set, this sets it. |
| * @note This method is an experimental feature. See the API docs for more |
| * detail at |
| * http://named-data.net/doc/ndn-ccl-api/face.html#face-makecommandinterest-method . |
| * @param {Interest} interest The interest whose name is appended with |
| * components. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * the SignatureInfo and to encode the interest name for signing. If omitted, |
| * use WireFormat.getDefaultWireFormat(). |
| * @param {function} onComplete (optional) This calls onComplete() when complete. |
| * If omitted, block until complete. (Some crypto/database libraries only use a |
| * callback, so onComplete is required to use these.) |
| */ |
| Face.prototype.makeCommandInterest = function(interest, wireFormat, onComplete) |
| { |
| onComplete = (typeof wireFormat === "function") ? wireFormat : onComplete; |
| wireFormat = (typeof wireFormat === "function" || !wireFormat) ? |
| WireFormat.getDefaultWireFormat() : wireFormat; |
| this.nodeMakeCommandInterest |
| (interest, this.commandKeyChain, this.commandCertificateName, wireFormat, |
| onComplete); |
| }; |
| |
| /** |
| * Append a timestamp component and a random value component to interest's name. |
| * Then use the keyChain and certificateName from setCommandSigningInfo to sign |
| * the interest. If the interest lifetime is not set, this sets it. |
| * @param {Interest} interest The interest whose name is appended with |
| * components. |
| * @param {KeyChain} keyChain The KeyChain for calling sign. |
| * @param {Name} certificateName The certificate name of the key to use for |
| * signing. |
| * @param {WireFormat} wireFormat A WireFormat object used to encode |
| * the SignatureInfo and to encode the interest name for signing. |
| * @param {function} onComplete (optional) This calls onComplete() when complete. |
| * (Some crypto/database libraries only use a callback, so onComplete is |
| * required to use these.) |
| */ |
| Face.prototype.nodeMakeCommandInterest = function |
| (interest, keyChain, certificateName, wireFormat, onComplete) |
| { |
| this.commandInterestGenerator.generate |
| (interest, keyChain, certificateName, wireFormat, onComplete); |
| }; |
| |
| /** |
| * Register prefix with the connected NDN hub and call onInterest when a |
| * matching interest is received. To register a prefix with NFD, you must |
| * first call setCommandSigningInfo. |
| * This uses the form: |
| * @param {Name} prefix The Name prefix. |
| * @param {function} onInterest (optional) If not null, this creates an interest |
| * filter from prefix so that when an Interest is received which matches the |
| * filter, this calls |
| * onInterest(prefix, interest, face, interestFilterId, filter). |
| * NOTE: You must not change the prefix object - if you need to change it then |
| * make a copy. If onInterest is null, it is ignored and you must call |
| * setInterestFilter. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onRegisterFailed If register prefix fails for any reason, |
| * this calls onRegisterFailed(prefix) where: |
| * prefix is the prefix given to registerPrefix. |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {function} onRegisterSuccess (optional) When this receives a success |
| * message, this calls onRegisterSuccess(prefix, registeredPrefixId) where |
| * prefix is the prefix given to registerPrefix and registeredPrefixId is |
| * the value retured by registerPrefix. If onRegisterSuccess is null or omitted, |
| * this does not use it. (The onRegisterSuccess parameter comes after |
| * onRegisterFailed because it can be null or omitted, unlike onRegisterFailed.) |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| * @param {ForwardingFlags} flags (optional) The ForwardingFlags object for |
| * finer control of which interests are forward to the application. If omitted, |
| * use the default flags defined by the default ForwardingFlags constructor. |
| * @return {number} The registered prefix ID which can be used with |
| * removeRegisteredPrefix. |
| */ |
| Face.prototype.registerPrefix = function |
| (prefix, onInterest, onRegisterFailed, onRegisterSuccess, flags, wireFormat) |
| { |
| // Temporarlity reassign to resolve the different overloaded forms. |
| var arg4 = onRegisterSuccess; |
| var arg5 = flags; |
| var arg6 = wireFormat; |
| // arg4, arg5, arg6 may be: |
| // OnRegisterSuccess, ForwardingFlags, WireFormat |
| // OnRegisterSuccess, ForwardingFlags, null |
| // OnRegisterSuccess, WireFormat, null |
| // OnRegisterSuccess, null, null |
| // ForwardingFlags, WireFormat, null |
| // ForwardingFlags, null, null |
| // WireFormat, null, null |
| // null, null, null |
| if (typeof arg4 === "function") |
| onRegisterSuccess = arg4; |
| else |
| onRegisterSuccess = null; |
| |
| if (arg4 instanceof ForwardingFlags) |
| flags = arg4; |
| else if (arg5 instanceof ForwardingFlags) |
| flags = arg5; |
| else |
| flags = new ForwardingFlags(); |
| |
| if (arg4 instanceof WireFormat) |
| wireFormat = arg4; |
| else if (arg5 instanceof WireFormat) |
| wireFormat = arg5; |
| else if (arg6 instanceof WireFormat) |
| wireFormat = arg6; |
| else |
| wireFormat = WireFormat.getDefaultWireFormat(); |
| |
| if (!onRegisterFailed) |
| onRegisterFailed = function() {}; |
| |
| var registeredPrefixId = this.getNextEntryId(); |
| var thisFace = this; |
| var onConnected = function() { |
| thisFace.nfdRegisterPrefix |
| (registeredPrefixId, prefix, onInterest, flags, onRegisterFailed, |
| onRegisterSuccess, thisFace.commandKeyChain, |
| thisFace.commandCertificateName, wireFormat); |
| }; |
| |
| if (this.connectionInfo == null) { |
| if (this.getConnectionInfo == null) |
| console.log('ERROR: connectionInfo is NOT SET'); |
| else |
| this.connectAndExecute(onConnected); |
| } |
| else |
| onConnected(); |
| |
| return registeredPrefixId; |
| }; |
| |
| /** |
| * Get the practical limit of the size of a network-layer packet. If a packet |
| * is larger than this, the library or application MAY drop it. |
| * @return {number} The maximum NDN packet size. |
| */ |
| Face.getMaxNdnPacketSize = function() { return NdnCommon.MAX_NDN_PACKET_SIZE; }; |
| |
| /** |
| * A RegisterResponse has onData to receive the response Data packet from the |
| * register prefix interest sent to the connected NDN hub. If this gets a bad |
| * response or onTimeout is called, then call onRegisterFailed. |
| */ |
| Face.RegisterResponse = function RegisterResponse |
| (prefix, onRegisterFailed, onRegisterSuccess, registeredPrefixId, parent, |
| onInterest) |
| { |
| this.prefix = prefix; |
| this.onRegisterFailed = onRegisterFailed; |
| this.onRegisterSuccess= onRegisterSuccess; |
| this.registeredPrefixId = registeredPrefixId; |
| this.parent = parent; |
| this.onInterest = onInterest; |
| }; |
| |
| Face.RegisterResponse.prototype.onData = function(interest, responseData) |
| { |
| // Decode responseData.getContent() and check for a success code. |
| var controlResponse = new ControlResponse(); |
| try { |
| controlResponse.wireDecode(responseData.getContent(), TlvWireFormat.get()); |
| } |
| catch (e) { |
| // Error decoding the ControlResponse. |
| if (LOG > 0) |
| console.log("Register prefix failed: Error decoding the NFD response: " + e); |
| if (this.onRegisterFailed) { |
| try { |
| this.onRegisterFailed(this.prefix); |
| } catch (ex) { |
| console.log("Error in onRegisterFailed: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| return; |
| } |
| |
| // Status code 200 is "OK". |
| if (controlResponse.getStatusCode() != 200) { |
| if (LOG > 0) |
| console.log("Register prefix failed: Expected NFD status code 200, got: " + |
| controlResponse.getStatusCode()); |
| if (this.onRegisterFailed) { |
| try { |
| this.onRegisterFailed(this.prefix); |
| } catch (ex) { |
| console.log("Error in onRegisterFailed: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| return; |
| } |
| |
| // Success, so we can add to the registered prefix table. |
| if (this.registeredPrefixId != 0) { |
| var interestFilterId = 0; |
| if (this.onInterest != null) |
| // registerPrefix was called with the "combined" form that includes the |
| // callback, so add an InterestFilterEntry. |
| interestFilterId = this.parent.setInterestFilter |
| (new InterestFilter(this.prefix), this.onInterest); |
| |
| if (!this.parent.registeredPrefixTable_.add |
| (this.registeredPrefixId, this.prefix, interestFilterId)) { |
| // removeRegisteredPrefix was already called with the registeredPrefixId. |
| if (interestFilterId > 0) |
| // Remove the related interest filter we just added. |
| this.parent.unsetInterestFilter(interestFilterId); |
| |
| return; |
| } |
| } |
| |
| if (LOG > 2) |
| console.log("Register prefix succeeded with the NFD forwarder for prefix " + |
| this.prefix.toUri()); |
| if (this.onRegisterSuccess != null) { |
| try { |
| this.onRegisterSuccess(this.prefix, this.registeredPrefixId); |
| } catch (ex) { |
| console.log("Error in onRegisterSuccess: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| }; |
| |
| /** |
| * We timed out waiting for the response. |
| */ |
| Face.RegisterResponse.prototype.onTimeout = function(interest) |
| { |
| if (LOG > 2) |
| console.log("Timeout for NFD register prefix command."); |
| if (this.onRegisterFailed) { |
| try { |
| this.onRegisterFailed(this.prefix); |
| } catch (ex) { |
| console.log("Error in onRegisterFailed: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| }; |
| |
| /** |
| * Do the work of registerPrefix to register with NFD. |
| * @param {number} registeredPrefixId The Face.getNextEntryId() which |
| * registerPrefix got so it could return it to the caller. If this is 0, then |
| * don't add to registeredPrefixTable (assuming it has already been done). |
| * @param {Name} prefix |
| * @param {function} onInterest |
| * @param {ForwardingFlags} flags |
| * @param {function} onRegisterFailed |
| * @param {function} onRegisterSuccess |
| * @param {KeyChain} commandKeyChain |
| * @param {Name} commandCertificateName |
| * @param {WireFormat} wireFormat |
| */ |
| Face.prototype.nfdRegisterPrefix = function |
| (registeredPrefixId, prefix, onInterest, flags, onRegisterFailed, |
| onRegisterSuccess, commandKeyChain, commandCertificateName, wireFormat) |
| { |
| if (commandKeyChain == null) |
| throw new Error |
| ("registerPrefix: The command KeyChain has not been set. You must call setCommandSigningInfo."); |
| if (commandCertificateName.size() == 0) |
| throw new Error |
| ("registerPrefix: The command certificate name has not been set. You must call setCommandSigningInfo."); |
| |
| var controlParameters = new ControlParameters(); |
| controlParameters.setName(prefix); |
| controlParameters.setForwardingFlags(flags); |
| |
| // Make the callback for this.isLocal(). |
| var thisFace = this; |
| var onIsLocalResult = function(isLocal) { |
| var commandInterest = new Interest(); |
| if (isLocal) { |
| commandInterest.setName(new Name("/localhost/nfd/rib/register")); |
| // The interest is answered by the local host, so set a short timeout. |
| commandInterest.setInterestLifetimeMilliseconds(2000.0); |
| } |
| else { |
| commandInterest.setName(new Name("/localhop/nfd/rib/register")); |
| // The host is remote, so set a longer timeout. |
| commandInterest.setInterestLifetimeMilliseconds(4000.0); |
| } |
| // NFD only accepts TlvWireFormat packets. |
| commandInterest.getName().append |
| (controlParameters.wireEncode(TlvWireFormat.get())); |
| thisFace.nodeMakeCommandInterest |
| (commandInterest, commandKeyChain, commandCertificateName, |
| TlvWireFormat.get(), function() { |
| // Send the registration interest. |
| var response = new Face.RegisterResponse |
| (prefix, onRegisterFailed, onRegisterSuccess, registeredPrefixId, |
| thisFace, onInterest); |
| thisFace.reconnectAndExpressInterest |
| (null, commandInterest, response.onData.bind(response), |
| response.onTimeout.bind(response), null, wireFormat); |
| }); |
| }; |
| |
| this.isLocal |
| (onIsLocalResult, |
| function(message) { |
| if (LOG > 0) |
| console.log("Error in Transport.isLocal: " + message); |
| if (onRegisterFailed) { |
| try { |
| onRegisterFailed(prefix); |
| } catch (ex) { |
| console.log("Error in onRegisterFailed: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| }); |
| }; |
| |
| /** |
| * Remove the registered prefix entry with the registeredPrefixId from the |
| * registered prefix table. This does not affect another registered prefix with |
| * a different registeredPrefixId, even if it has the same prefix name. If an |
| * interest filter was automatically created by registerPrefix, also remove it. |
| * If there is no entry with the registeredPrefixId, do nothing. |
| * |
| * @param {number} registeredPrefixId The ID returned from registerPrefix. |
| */ |
| Face.prototype.removeRegisteredPrefix = function(registeredPrefixId) |
| { |
| this.registeredPrefixTable_.removeRegisteredPrefix(registeredPrefixId); |
| }; |
| |
| /** |
| * Add an entry to the local interest filter table to call the onInterest |
| * callback for a matching incoming Interest. This method only modifies the |
| * library's local callback table and does not register the prefix with the |
| * forwarder. It will always succeed. To register a prefix with the forwarder, |
| * use registerPrefix. There are two forms of setInterestFilter. |
| * The first form uses the exact given InterestFilter: |
| * setInterestFilter(filter, onInterest). |
| * The second form creates an InterestFilter from the given prefix Name: |
| * setInterestFilter(prefix, onInterest). |
| * @param {InterestFilter} filter The InterestFilter with a prefix and optional |
| * regex filter used to match the name of an incoming Interest. This makes a |
| * copy of filter. |
| * @param {Name} prefix The Name prefix used to match the name of an incoming |
| * Interest. |
| * @param {function} onInterest When an Interest is received which matches the |
| * filter, this calls onInterest(prefix, interest, face, interestFilterId, filter). |
| * NOTE: The library will log any exceptions thrown by this callback, but for |
| * better error handling the callback should catch and properly handle any |
| * exceptions. |
| */ |
| Face.prototype.setInterestFilter = function(filterOrPrefix, onInterest) |
| { |
| var interestFilterId = this.getNextEntryId(); |
| this.interestFilterTable_.setInterestFilter |
| (interestFilterId, new InterestFilter(filterOrPrefix), onInterest, this); |
| return interestFilterId; |
| }; |
| |
| /** |
| * Remove the interest filter entry which has the interestFilterId from the |
| * interest filter table. This does not affect another interest filter with a |
| * different interestFilterId, even if it has the same prefix name. If there is |
| * no entry with the interestFilterId, do nothing. |
| * @param {number} interestFilterId The ID returned from setInterestFilter. |
| */ |
| Face.prototype.unsetInterestFilter = function(interestFilterId) |
| { |
| this.interestFilterTable_.unsetInterestFilter(interestFilterId); |
| }; |
| |
| /** |
| * The OnInterest callback calls this to put a Data packet which satisfies an |
| * Interest. |
| * @param {Data} data The Data packet which satisfies the interest. |
| * @param {WireFormat} wireFormat (optional) A WireFormat object used to encode |
| * the Data packet. If omitted, use WireFormat.getDefaultWireFormat(). |
| * @throws Error If the encoded Data packet size exceeds getMaxNdnPacketSize(). |
| */ |
| Face.prototype.putData = function(data, wireFormat) |
| { |
| wireFormat = (wireFormat || WireFormat.getDefaultWireFormat()); |
| |
| var encoding = data.wireEncode(wireFormat); |
| if (encoding.size() > Face.getMaxNdnPacketSize()) |
| throw new Error |
| ("The encoded Data packet size exceeds the maximum limit getMaxNdnPacketSize()"); |
| |
| this.transport.send(encoding.buf()); |
| }; |
| |
| /** |
| * Send the encoded packet out through the transport. |
| * @param {Buffer} encoding The Buffer with the encoded packet to send. |
| * @throws Error If the encoded packet size exceeds getMaxNdnPacketSize(). |
| */ |
| Face.prototype.send = function(encoding) |
| { |
| if (encoding.length > Face.getMaxNdnPacketSize()) |
| throw new Error |
| ("The encoded packet size exceeds the maximum limit getMaxNdnPacketSize()"); |
| |
| this.transport.send(encoding); |
| }; |
| |
| /** |
| * Check if the face is local based on the current connection through the |
| * Transport; some Transport may cause network I/O (e.g. an IP host name lookup). |
| * @param {function} onResult On success, this calls onResult(isLocal) where |
| * isLocal is true if the host is local, false if not. We use callbacks because |
| * this may need to do network I/O (e.g. an IP host name lookup). |
| * @param {function} onError On failure for DNS lookup or other error, this |
| * calls onError(message) where message is an error string. |
| */ |
| Face.prototype.isLocal = function(onResult, onError) |
| { |
| // TODO: How to call transport.isLocal when this.connectionInfo is null? (This |
| // happens when the application does not supply a host but relies on the |
| // getConnectionInfo function to select a host.) For now return true to keep |
| // the same behavior from before we added Transport.isLocal. |
| if (this.connectionInfo == null) |
| onResult(false); |
| else |
| this.transport.isLocal(this.connectionInfo, onResult, onError); |
| }; |
| |
| /** |
| * This is called when an entire element is received, such as a Data or Interest. |
| */ |
| Face.prototype.onReceivedElement = function(element) |
| { |
| if (LOG > 3) console.log('Complete element received. Length ' + element.length + '. Start decoding.'); |
| |
| var lpPacket = null; |
| if (element[0] == Tlv.LpPacket_LpPacket) { |
| // Decode the LpPacket and replace element with the fragment. |
| lpPacket = new LpPacket(); |
| // Set copy false so that the fragment is a slice which will be copied below. |
| // The header fields are all integers and don't need to be copied. |
| TlvWireFormat.get().decodeLpPacket(lpPacket, element, false); |
| element = lpPacket.getFragmentWireEncoding().buf(); |
| } |
| |
| // First, decode as Interest or Data. |
| var interest = null; |
| var data = null; |
| if (element[0] == Tlv.Interest || element[0] == Tlv.Data) { |
| var decoder = new TlvDecoder (element); |
| if (decoder.peekType(Tlv.Interest, element.length)) { |
| interest = new Interest(); |
| interest.wireDecode(element, TlvWireFormat.get()); |
| |
| if (lpPacket != null) |
| interest.setLpPacket(lpPacket); |
| } |
| else if (decoder.peekType(Tlv.Data, element.length)) { |
| data = new Data(); |
| data.wireDecode(element, TlvWireFormat.get()); |
| |
| if (lpPacket != null) |
| data.setLpPacket(lpPacket); |
| } |
| } |
| |
| if (lpPacket !== null) { |
| // We have decoded the fragment, so remove the wire encoding to save memory. |
| lpPacket.setFragmentWireEncoding(new Blob()); |
| |
| var networkNack = NetworkNack.getFirstHeader(lpPacket); |
| if (networkNack != null) { |
| if (interest == null) |
| // We got a Nack but not for an Interest, so drop the packet. |
| return; |
| |
| var pitEntries = []; |
| this.pendingInterestTable_.extractEntriesForNackInterest(interest, pitEntries); |
| for (var i = 0; i < pitEntries.length; ++i) { |
| var pendingInterest = pitEntries[i]; |
| try { |
| pendingInterest.getOnNetworkNack()(pendingInterest.getInterest(), networkNack); |
| } catch (ex) { |
| console.log("Error in onNetworkNack: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| |
| // We have processed the network Nack packet. |
| return; |
| } |
| } |
| |
| // Now process as Interest or Data. |
| if (interest !== null) { |
| if (LOG > 3) console.log('Interest packet received.'); |
| |
| // Call all interest filter callbacks which match. |
| var matchedFilters = []; |
| this.interestFilterTable_.getMatchedFilters(interest, matchedFilters); |
| for (var i = 0; i < matchedFilters.length; ++i) { |
| var entry = matchedFilters[i]; |
| if (LOG > 3) |
| console.log("Found interest filter for " + interest.getName().toUri()); |
| try { |
| entry.getOnInterest() |
| (entry.getFilter().getPrefix(), interest, this, |
| entry.getInterestFilterId(), entry.getFilter()); |
| } catch (ex) { |
| console.log("Error in onInterest: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| } |
| else if (data !== null) { |
| if (LOG > 3) console.log('Data packet received.'); |
| |
| var pendingInterests = []; |
| this.pendingInterestTable_.extractEntriesForExpressedInterest |
| (data, pendingInterests); |
| // Process each matching PIT entry (if any). |
| for (var i = 0; i < pendingInterests.length; ++i) { |
| var pendingInterest = pendingInterests[i]; |
| try { |
| pendingInterest.getOnData()(pendingInterest.getInterest(), data); |
| } catch (ex) { |
| console.log("Error in onData: " + NdnCommon.getErrorWithStackTrace(ex)); |
| } |
| } |
| } |
| }; |
| |
| /** |
| * Assume this.getConnectionInfo is not null. This is called when |
| * this.connectionInfo is null or its host is not alive. |
| * Get a connectionInfo, connect, then execute onConnected(). |
| */ |
| Face.prototype.connectAndExecute = function(onConnected) |
| { |
| var connectionInfo = this.getConnectionInfo(); |
| if (connectionInfo == null) { |
| console.log('ERROR: No more connectionInfo from getConnectionInfo'); |
| this.connectionInfo = null; |
| // Deprecated: Set this.host and this.port for backwards compatibility. |
| this.host = null; |
| this.host = null; |
| |
| return; |
| } |
| |
| if (connectionInfo.equals(this.connectionInfo)) { |
| console.log |
| ('ERROR: The host returned by getConnectionInfo is not alive: ' + |
| this.connectionInfo.toString()); |
| return; |
| } |
| |
| this.connectionInfo = connectionInfo; |
| if (LOG>0) console.log("connectAndExecute: trying host from getConnectionInfo: " + |
| this.connectionInfo.toString()); |
| // Deprecated: Set this.host and this.port for backwards compatibility. |
| this.host = this.connectionInfo.host; |
| this.host = this.connectionInfo.port; |
| |
| // Fetch any content. |
| var interest = new Interest(new Name("/")); |
| interest.setInterestLifetimeMilliseconds(4000); |
| |
| var thisFace = this; |
| var timerID = setTimeout(function() { |
| if (LOG>0) console.log("connectAndExecute: timeout waiting for host " + thisFace.host); |
| // Try again. |
| thisFace.connectAndExecute(onConnected); |
| }, 3000); |
| |
| this.reconnectAndExpressInterest |
| (null, interest, |
| function(localInterest, localData) { |
| // The host is alive, so cancel the timeout and continue with onConnected(). |
| clearTimeout(timerID); |
| |
| if (LOG>0) |
| console.log("connectAndExecute: connected to host " + thisFace.host); |
| onConnected(); |
| }, |
| function(localInterest) { /* Ignore timeout */ }, |
| null, WireFormat.getDefaultWireFormat()); |
| }; |
| |
| /** |
| * This is called by the Transport when the connection is closed by the remote host. |
| */ |
| Face.prototype.closeByTransport = function() |
| { |
| this.readyStatus = Face.CLOSED; |
| this.onclose(); |
| }; |
| |
| Face.nonceTemplate_ = new Blob(new Buffer(4), false); |
| (function(n,t,i){"use strict";function s(n,t){return typeof t!="object"&&(t=t()),Object.keys(t).forEach(function(i){n[i]=t[i]}),n}function y(n){return{from:function(t){return n.prototype=Object.create(t.prototype),n.prototype.constructor=n,{extend:function(i){s(n.prototype,typeof i!="object"?i(t.prototype):i)}}}}}function p(n,t){return t(n)}function u(n,t){function cr(){w.on("versionchange",function(){w.close();w.on("error").fire(new g("Database version changed by other database connection."))})}function di(n){this._cfg={version:n,storesSource:null,dbschema:{},tables:{},contentUpgrade:null};this.stores({})}function lr(n,t,i,u){var e,f,s,h,l,c;if(n===0)Object.keys(st).forEach(function(n){gi(t,n,st[n].primKey,st[n].indexes)}),e=w._createTransaction(yt,ui,st),e.idbtrans=t,e.idbtrans.onerror=o(i,["populating database"]),e.on("error").subscribe(i),r.newPSD(function(){r.PSD.trans=e;try{w.on("populate").fire(e)}catch(n){u.onerror=t.onerror=function(n){n.preventDefault()};try{t.abort()}catch(f){}t.db.close();i(n)}});else{if(f=[],s=ri.filter(function(t){return t._cfg.version===n})[0],!s)throw new g("Dexie specification of currently installed DB version is missing");st=w._dbSchema=s._cfg.dbschema;h=!1;l=ri.filter(function(t){return t._cfg.version>n});l.forEach(function(n){var e=st,r=n._cfg.dbschema,u;fr(e,t);fr(r,t);st=w._dbSchema=r;u=ar(e,r);u.add.forEach(function(n){f.push(function(t,i){gi(t,n[0],n[1].primKey,n[1].indexes);i()})});u.change.forEach(function(n){if(n.recreate)throw new g("Not yet support for changing primary key");else f.push(function(t,i){var r=t.objectStore(n.name);n.add.forEach(function(n){nr(r,n)});n.change.forEach(function(n){r.deleteIndex(n.name);nr(r,n)});n.del.forEach(function(n){r.deleteIndex(n)});i()})});n._cfg.contentUpgrade&&f.push(function(t,u){var f,e;h=!0;f=w._createTransaction(yt,[].slice.call(t.db.objectStoreNames,0),r);f.idbtrans=t;e=0;f._promise=p(f._promise,function(n){return function(t,i,r){function f(n){return function(){n.apply(this,arguments);--e==0&&u()}}return++e,n.call(this,t,function(n,t){arguments[0]=f(n);arguments[1]=f(t);i.apply(this,arguments)},r)}});t.onerror=o(i,["running upgrader function for version",n._cfg.version]);f.on("error").subscribe(i);n._cfg.contentUpgrade(f);e===0&&u()});h&&dr()||f.push(function(n,t){yr(r,n);t()})});c=function(){try{f.length?f.shift()(t,c):vr(st,t)}catch(n){u.onerror=t.onerror=function(n){n.preventDefault()};try{t.abort()}catch(r){}t.db.close();i(n)}};c()}}function ar(n,t){var f={del:[],add:[],change:[]},r,e,o,i,c,s,u,l,h;for(r in n)t[r]||f.del.push(r);for(r in t)if(e=n[r],o=t[r],e)if(i={name:r,def:t[r],recreate:!1,del:[],add:[],change:[]},e.primKey.src!==o.primKey.src)i.recreate=!0,f.change.push(i);else{c=e.indexes.reduce(function(n,t){return n[t.name]=t,n},{});s=o.indexes.reduce(function(n,t){return n[t.name]=t,n},{});for(u in c)s[u]||i.del.push(u);for(u in s)l=c[u],h=s[u],l?l.src!==h.src&&i.change.push(h):i.add.push(h);(i.recreate||i.del.length>0||i.add.length>0||i.change.length>0)&&f.change.push(i)}else f.add.push([r,o]);return f}function gi(n,t,i,r){var u=n.db.createObjectStore(t,i.keyPath?{keyPath:i.keyPath,autoIncrement:i.auto}:{autoIncrement:i.auto});return r.forEach(function(n){nr(u,n)}),u}function vr(n,t){Object.keys(n).forEach(function(i){t.db.objectStoreNames.contains(i)||gi(t,i,n[i].primKey,n[i].indexes)})}function yr(n,t){for(var u,r=0;r<t.db.objectStoreNames.length;++r)u=t.db.objectStoreNames[r],(n[u]===null||n[u]===i)&&t.db.deleteObjectStore(u)}function nr(n,t){n.createIndex(t.name,t.keyPath,{unique:t.unique,multiEntry:t.multi})}function pr(n,t){throw new g("Table "+t[0]+" not part of transaction. Original Scope Function Source: "+u.Promise.PSD.trans.scopeFunc.toString());}function ei(n,t,i,r){this.name=n;this.schema=i;this.hook=ni[n]?ni[n].hook:v(null,{creating:[at,f],reading:[lt,nt],updating:[vt,f],deleting:[wt,f]});this._tpf=t;this._collClass=r||li}function tr(n,t,i,r){ei.call(this,n,t,i,r||rr)}function ir(n,t,i,r){function o(n,t,i,r){return s._promise(n,i,r)}var s=this,f,u,e;for(this.db=w,this.mode=n,this.storeNames=t,this.idbtrans=null,this.on=v(this,["complete","error"],"abort"),this._reculock=0,this._blockedFuncs=[],this._psd=null,this.active=!0,this._dbschema=i,r&&(this.parent=r),this._tpf=o,this.tables=Object.create(ki),f=t.length-1;f!==-1;--f)u=t[f],e=w._tableFactory(n,i[u],o),this.tables[u]=e,this[u]||(this[u]=e)}function ci(n,t,i){this._ctx={table:n,index:t===":id"?null:t,collClass:n._collClass,or:i}}function li(n,t){var r=null,u=null,i;if(t)try{r=t()}catch(f){u=f}i=n._ctx;this._ctx={table:i.table,index:i.index,isPrimKey:!i.index||i.table.schema.primKey.keyPath&&i.index===i.table.schema.primKey.name,range:r,op:"openCursor",dir:"next",unique:"",algorithm:null,filter:null,isMatch:null,offset:0,limit:Infinity,error:u,or:i.or}}function rr(){li.apply(this,arguments)}function wr(n,t){return n._cfg.version-t._cfg.version}function ur(n,t,i,u,f,e){i.forEach(function(i){var o=w._tableFactory(u,f[i],t);n.forEach(function(n){n[i]||(e?Object.defineProperty(n,i,{configurable:!0,enumerable:!0,get:function(){var n=r.PSD&&r.PSD.trans;return n&&n.db===w?n.tables[i]:o}}):n[i]=o)})})}function br(n){n.forEach(function(n){for(var t in n)n[t]instanceof ei&&delete n[t]})}function pi(n,t,i,u,f,e){var s=r.PSD;e=e||nt;n.onerror||(n.onerror=o(f));n.onsuccess=t?k(function(){var r=n.result,o;r?(o=function(){r.continue()},t(r,function(n){o=n},u,f)&&i(e(r.value),r,function(n){o=n}),o()):u()},f,s):k(function(){var t=n.result,r;t?(r=function(){t.continue()},i(e(t.value),t,function(n){r=n}),r()):u()},f,s)}function kr(n){var t=[];return n.split(",").forEach(function(n){n=n.trim();var i=n.replace("&","").replace("++","").replace("*",""),r=i.indexOf("[")!==0?i:n.substring(n.indexOf("[")+1,n.indexOf("]")).split("+");t.push(new a(i,r||null,n.indexOf("&")!==-1,n.indexOf("*")!==-1,n.indexOf("++")!==-1,Array.isArray(r),r.indexOf(".")!==-1))}),t}function wi(n,t){return n<t?-1:n>t?1:0}function or(n,t){return n<t?1:n>t?-1:0}function sr(n){return function(t,i){for(var r=0,u;;){if(u=n(t[r],i[r]),u!==0)return u;if(++r,r===t.length||r===i.length)return n(t.length,i.length)}}}function bi(n,t){return n?t?function(){return n.apply(this,arguments)&&t.apply(this,arguments)}:n:t}function dr(){return navigator.userAgent.indexOf("Trident")>=0||navigator.userAgent.indexOf("MSIE")>=0}function gr(){if(w.verno=et.version/10,w._dbSchema=st={},ui=[].slice.call(et.objectStoreNames,0),ui.length!==0){var n=et.transaction(ft(ui),"readonly");ui.forEach(function(t){for(var u,s,r=n.objectStore(t),i=r.keyPath,f=i&&typeof i=="string"&&i.indexOf(".")!==-1,h=new a(i,i||"",!1,!1,!!r.autoIncrement,i&&typeof i!="string",f),o=[],e=0;e<r.indexNames.length;++e)u=r.index(r.indexNames[e]),i=u.keyPath,f=i&&typeof i=="string"&&i.indexOf(".")!==-1,s=new a(u.name,i,!!u.unique,!!u.multiEntry,!1,i&&typeof i!="string",f),o.push(s);st[t]=new ut(t,h,o,{})});ur([ni],w._transPromiseFactory,Object.keys(st),yt,st)}}function fr(n,t){for(var i,r,u,o,s=t.db.objectStoreNames,f=0;f<s.length;++f)for(i=s[f],r=t.objectStore(i),u=0;u<r.indexNames.length;++u){var h=r.indexNames[u],e=r.index(h).keyPath,c=typeof e=="string"?e:"["+[].slice.call(e).join("+")+"]";n[i]&&(o=n[i].idxByName[c],o&&(o.name=h))}}var hr=t&&t.addons||u.addons,oi=u.dependencies,ai=oi.indexedDB,kt=oi.IDBKeyRange,nu=oi.IDBTransaction,tu=oi.DOMError,yi=oi.TypeError,g=oi.Error,st=this._dbSchema={},ri=[],ui=[],ni={},ki={},et=null,si=!0,fi=null,vi=!1,ti="readonly",yt="readwrite",w=this,ii=[],hi=!1,er=!!ct();this.version=function(n){if(et)throw new g("Cannot add version when database is open");this.verno=Math.max(this.verno,n);var t=ri.filter(function(t){return t._cfg.version===n})[0];return t?t:(t=new di(n),ri.push(t),ri.sort(wr),t)};s(di.prototype,{stores:function(n){var i,t;return this._cfg.storesSource=this._cfg.storesSource?s(this._cfg.storesSource,n):n,i={},ri.forEach(function(n){s(i,n._cfg.storesSource)}),t=this._cfg.dbschema={},this._parseStoresSpec(i,t),st=w._dbSchema=t,br([ni,w,ki]),ur([ki],pr,Object.keys(t),yt,t),ur([ni,w,this._cfg.tables],w._transPromiseFactory,Object.keys(t),yt,t,!0),ui=Object.keys(t),this},upgrade:function(n){var t=this;return e(function(){n(w._createTransaction(yt,Object.keys(t._cfg.dbschema),t._cfg.dbschema))}),this._cfg.contentUpgrade=n,this},_parseStoresSpec:function(n,t){Object.keys(n).forEach(function(i){if(n[i]!==null){var u={},f=kr(n[i]),r=f.shift();if(r.multi)throw new g("Primary key cannot be multi-valued");r.keyPath&&r.auto&&h(u,r.keyPath,0);f.forEach(function(n){if(n.auto)throw new g("Only primary key can be marked as autoIncrement (++)");if(!n.keyPath)throw new g("Index must have a name and cannot be an empty string");h(u,n.keyPath,n.compound?n.keyPath.map(function(){return""}):"")});t[i]=new ut(i,r,f,u)}})}});this._allTables=ni;this._tableFactory=function(n,t,i){return n===ti?new ei(t.name,i,t,li):new tr(t.name,i,t)};this._createTransaction=function(n,t,i,r){return new ir(n,t,i,r)};this._transPromiseFactory=function(n,t,i){var f,u;return!si||r.PSD&&r.PSD.letThrough?(u=w._createTransaction(n,t,st),u._promise(n,function(n,t){u.error(function(n){w.on("error").fire(n)});i(function(t){u.complete(function(){n(t)})},t,u)})):f=new r(function(r,u){ii.push({resume:function(){var e=w._transPromiseFactory(n,t,i);f.onuncatched=e.onuncatched;e.then(r,u)}})})};this._whenReady=function(n){return si&&(!r.PSD||!r.PSD.letThrough)?new r(function(t,i){e(function(){new r(function(){n(t,i)})});ii.push({resume:function(){n(t,i)}})}):new r(n)};this.verno=0;this.open=function(){return new r(function(t,i){function f(n){try{u.transaction.abort()}catch(t){}vi=!1;fi=n;si=!1;i(fi);ii.forEach(function(n){n.resume()});ii=[]}if(et||vi)throw new g("Database already opened or being opened");var u,e=!1;try{if(fi=null,vi=!0,ri.length===0&&(hi=!0),!ai)throw new g("indexedDB API not found. If using IE10+, make sure to run your code on a server URL (not locally). If using Safari, make sure to include indexedDB polyfill.");u=hi?ai.open(n):ai.open(n,Math.round(w.verno*10));u.onerror=o(f,["opening database",n]);u.onblocked=function(n){w.on("blocked").fire(n)};u.onupgradeneeded=k(function(t){var i,r;hi&&!w._allowEmptyDB?(u.onerror=function(n){n.preventDefault()},u.transaction.abort(),u.result.close(),i=ai.deleteDatabase(n),i.onsuccess=i.onerror=function(){f(new g("Database '"+n+"' doesnt exist"))}):(t.oldVersion===0&&(e=!0),u.transaction.onerror=o(f),r=t.oldVersion>Math.pow(2,62)?0:t.oldVersion,lr(r/10,u.transaction,f,u))},f);u.onsuccess=k(function(){vi=!1;et=u.result;hi?gr():et.objectStoreNames.length>0&&fr(st,et.transaction(ft(et.objectStoreNames),ti));et.onversionchange=w.on("versionchange").fire;er||rt(function(t){if(t.indexOf(n)===-1)return t.push(n)});r.newPSD(function(){function i(){si=!1;ii.forEach(function(n){n.resume()});ii=[];t()}r.PSD.letThrough=!0;try{var n=w.on.ready.fire();n&&typeof n.then=="function"?n.then(i,function(n){et.close();et=null;f(n)}):b(i)}catch(u){f(u)}})},f)}catch(s){f(s)}})};this.close=function(){et&&(et.close(),et=null,si=!0,fi=null)};this.delete=function(){var t=arguments;return new r(function(i,r){function u(){w.close();var t=ai.deleteDatabase(n);t.onsuccess=function(){er||rt(function(t){var i=t.indexOf(n);if(i>=0)return t.splice(i,1)});i()};t.onerror=o(r,["deleting",n]);t.onblocked=function(){w.on("blocked").fire()}}if(t.length>0)throw new g("Arguments not allowed in db.delete()");vi?ii.push({resume:u}):u()})};this.backendDB=function(){return et};this.isOpen=function(){return et!==null};this.hasFailed=function(){return fi!==null};this.dynamicallyOpened=function(){return hi};this.name=n;Object.defineProperty(this,"tables",{get:function(){return Object.keys(ni).map(function(n){return ni[n]})}});this.on=v(this,"error","populate","blocked",{ready:[bt,f],versionchange:[pt,f]});this.on.ready.subscribe=p(this.on.ready.subscribe,function(n){return function(t,i){function r(){return i||w.on.ready.unsubscribe(r),t.apply(this,arguments)}n.call(this,r);w.isOpen()&&(si?ii.push({resume:r}):r())}});e(function(){w.on("populate").fire(w._createTransaction(yt,ui,st));w.on("error").fire(new g)});this.transaction=function(n,t,i){function s(t,e){var s=null,c,a,h;try{if(f)throw f;s=w._createTransaction(n,o,st,u);c=o.map(function(n){return s.tables[n]});c.push(s);h=0;r.newPSD(function(){r.PSD.trans=s;s.scopeFunc=i;u&&(s.idbtrans=u.idbtrans,s._promise=p(s._promise,function(n){return function(t,i,u){function f(n){return function(t){var i;return r._rootExec(function(){i=n(t);r._tickFinalize(function(){--h==0&&s.active&&(s.active=!1,s.on.complete.fire())})}),i}}return++h,n.call(this,t,function(n,t,r){return i(f(n),f(t),r)},u)}}));s.complete(function(){t(a)});s.error(function(n){s.idbtrans&&(s.idbtrans.onerror=ht);try{s.abort()}catch(i){}u&&(u.active=!1,u.on.error.fire(n));var t=e(n);u||t||w.on.error.fire(n)});r._rootExec(function(){a=i.apply(s,c)})});(!s.idbtrans||u&&h===0)&&s._nop()}catch(l){s&&s.idbtrans&&(s.idbtrans.onerror=ht);s&&s.abort();u&&u.on.error.fire(l);b(function(){e(l)||w.on("error").fire(l)})}}var u,e;t=[].slice.call(arguments,1,arguments.length-1);i=arguments[arguments.length-1];u=r.PSD&&r.PSD.trans;u&&u.db===w&&n.indexOf("!")===-1||(u=null);e=n.indexOf("?")!==-1;n=n.replace("!","").replace("?","");var h=Array.isArray(t[0])?t.reduce(function(n,t){return n.concat(t)}):t,f=null,o=h.map(function(n){return typeof n=="string"?n:(n instanceof ei||(f=f||new yi("Invalid type. Arguments following mode must be instances of Table or String")),n.name)});return n=="r"||n==ti?n=ti:n=="rw"||n==yt?n=yt:f=new g("Invalid transaction mode: "+n),u&&(f||(u&&u.mode===ti&&n===yt&&(e?u=null:f=f||new g("Cannot enter a sub-transaction with READWRITE mode when parent transaction is READONLY")),u&&o.forEach(function(n){u.tables.hasOwnProperty(n)||(e?u=null:f=f||new g("Table "+n+" not included in parent transaction. Parent Transaction function: "+u.scopeFunc.toString()))}))),u?u._promise(n,s,"lock"):w._whenReady(s)};this.table=function(n){if(!hi&&!ni.hasOwnProperty(n))throw new g("Table does not exist");return ni[n]};s(ei.prototype,function(){function n(){throw new g("Current Transaction is READONLY");}return{_trans:function(n,t,i){return this._tpf(n,[this.name],t,i)},_idbstore:function(n,t,i){var r=this;return this._tpf(n,[this.name],function(n,i,u){t(n,i,u.idbtrans.objectStore(r.name),u)},i)},get:function(n,t){var i=this;return e(function(){t(i.schema.instanceTemplate)}),this._idbstore(ti,function(t,r,u){var f=u.get(n);f.onerror=o(r,["getting",n,"from",i.name]);f.onsuccess=function(){t(i.hook.reading.fire(f.result))}}).then(t)},where:function(n){return new ci(this,n)},count:function(n){return this.toCollection().count(n)},offset:function(n){return this.toCollection().offset(n)},limit:function(n){return this.toCollection().limit(n)},reverse:function(){return this.toCollection().reverse()},filter:function(n){return this.toCollection().and(n)},each:function(n){var t=this;return e(function(){n(t.schema.instanceTemplate)}),this._idbstore(ti,function(i,r,u){var f=u.openCursor();f.onerror=o(r,["calling","Table.each()","on",t.name]);pi(f,null,n,i,r,t.hook.reading.fire)})},toArray:function(n){var t=this;return e(function(){n([t.schema.instanceTemplate])}),this._idbstore(ti,function(n,i,r){var u=[],f=r.openCursor();f.onerror=o(i,["calling","Table.toArray()","on",t.name]);pi(f,null,function(n){u.push(n)},function(){n(u)},i,t.hook.reading.fire)}).then(n)},orderBy:function(n){return new this._collClass(new ci(this,n))},toCollection:function(){return new this._collClass(new ci(this))},mapToClass:function(n,t){var i,r;return this.schema.mappedClass=n,i=Object.create(n.prototype),this.schema.primKey.keyPath&&(h(i,this.schema.primKey.keyPath,this.schema.primKey.auto?0:""),ot(n.prototype,this.schema.primKey.keyPath)),t&&it(i,t),this.schema.instanceTemplate=i,r=Object.setPrototypeOf?function(t){return t?(Object.setPrototypeOf(t,n.prototype),t):t}:function(t){var r,i;if(!t)return t;r=Object.create(n.prototype);for(i in t)t.hasOwnProperty(i)&&(r[i]=t[i]);return r},this.schema.readHook&&this.hook.reading.unsubscribe(this.schema.readHook),this.schema.readHook=r,this.hook("reading",r),n},defineClass:function(n){return this.mapToClass(u.defineClass(n),n)},add:n,put:n,"delete":n,clear:n,update:n}});y(tr).from(ei).extend(function(){return{add:function(n,t){var u=this,r=this.hook.creating.fire;return this._idbstore(yt,function(e,s,l,a){var v={},w,y,p;r!==f&&(w=t||(l.keyPath?c(n,l.keyPath):i),y=r.call(v,w,n,a),w===i&&y!==i&&(l.keyPath?h(n,l.keyPath,y):t=y));p=t?l.add(n,t):l.add(n);p.onerror=o(function(n){if(v.onerror)v.onerror(n);return s(n)},["adding",n,"into",u.name]);p.onsuccess=function(t){var i=l.keyPath;if(i&&h(n,i,t.target.result),v.onsuccess)v.onsuccess(t.target.result);e(p.result)}})},put:function(n,t){var r=this,u=this.hook.creating.fire,e=this.hook.updating.fire;return u!==f||e!==f?this._trans(yt,function(u,f,e){var o=t||r.schema.primKey.keyPath&&c(n,r.schema.primKey.keyPath);o===i?e.tables[r.name].add(n).then(u,f):(e._lock(),n=l(n),e.tables[r.name].where(":id").equals(o).modify(function(){this.value=n}).then(function(i){return i===0?e.tables[r.name].add(n,t):o}).finally(function(){e._unlock()}).then(u,f))}):this._idbstore(yt,function(i,u,f){var e=t?f.put(n,t):f.put(n);e.onerror=o(u,["putting",n,"into",r.name]);e.onsuccess=function(t){var r=f.keyPath;r&&h(n,r,t.target.result);i(e.result)}})},"delete":function(n){return this.hook.deleting.subscribers.length?this.where(":id").equals(n).delete():this._idbstore(yt,function(t,i,r){var u=r.delete(n);u.onerror=o(i,["deleting",n,"from",r.name]);u.onsuccess=function(){t(u.result)}})},clear:function(){return this.hook.deleting.subscribers.length?this.toCollection().delete():this._idbstore(yt,function(n,t,i){var r=i.clear();r.onerror=o(t,["clearing",i.name]);r.onsuccess=function(){n(r.result)}})},update:function(n,t){if(typeof t!="object"||Array.isArray(t))throw new g("db.update(keyOrObject, modifications). modifications must be an object.");if(typeof n!="object"||Array.isArray(n))return this.where(":id").equals(n).modify(t);Object.keys(t).forEach(function(i){h(n,i,t[i])});var u=c(n,this.schema.primKey.keyPath);return u===i&&r.reject(new g("Object does not contain its primary key")),this.where(":id").equals(u).modify(t)}}});s(ir.prototype,{_lock:function(){return++this._reculock,this._reculock===1&&r.PSD&&(r.PSD.lockOwnerFor=this),this},_unlock:function(){if(--this._reculock==0)for(r.PSD&&(r.PSD.lockOwnerFor=null);this._blockedFuncs.length>0&&!this._locked();){var n=this._blockedFuncs.shift();try{n()}catch(t){}}return this},_locked:function(){return this._reculock&&(!r.PSD||r.PSD.lockOwnerFor!==this)},_nop:function(n){this.tables[this.storeNames[0]].get(0).then(n)},_promise:function(n,t,i){var f=this;return r.newPSD(function(){var e;return f._locked()?e=new r(function(r,u){f._blockedFuncs.push(function(){f._promise(n,t,i).then(r,u)})}):(e=f.active?new r(function(r,e){if(!f.idbtrans&&n){if(!et)throw fi?new g("Database not open. Following error in populate, ready or upgrade function made Dexie.open() fail: "+fi):new g("Database not open");var o=f.idbtrans=et.transaction(ft(f.storeNames),f.mode);o.onerror=function(n){f.on("error").fire(n&&n.target.error);n.preventDefault();f.abort()};o.onabort=function(n){f.active=!1;f.on("abort").fire(n)};o.oncomplete=function(n){f.active=!1;f.on("complete").fire(n)}}i&&f._lock();try{t(r,e,f)}catch(s){u.ignoreTransaction(function(){f.on("error").fire(s)});f.abort();e(s)}}):r.reject(gt(new g("Transaction is inactive. Original Scope Function Source: "+f.scopeFunc.toString()))),f.active&&i&&e.finally(function(){f._unlock()})),e.onuncatched=function(n){u.ignoreTransaction(function(){f.on("error").fire(n)});f.abort()},e})},complete:function(n){return this.on("complete",n)},error:function(n){return this.on("error",n)},abort:function(){if(this.idbtrans&&this.active)try{this.active=!1;this.idbtrans.abort();this.on.error.fire(new g("Transaction Aborted"))}catch(n){}},table:function(n){if(!this.tables.hasOwnProperty(n))throw new g("Table "+n+" not in transaction");return this.tables[n]}});s(ci.prototype,function(){function n(n,t){try{throw t;}catch(i){n._ctx.error=i}return n}function i(n){return Array.prototype.slice.call(n.length===1&&Array.isArray(n[0])?n[0]:n)}function r(n){return n==="next"?function(n){return n.toUpperCase()}:function(n){return n.toLowerCase()}}function u(n){return n==="next"?function(n){return n.toLowerCase()}:function(n){return n.toUpperCase()}}function f(n,t,i,r,u,f){for(var h,s=Math.min(n.length,r.length),o=-1,e=0;e<s;++e){if(h=t[e],h!==r[e])return u(n[e],i[e])<0?n.substr(0,e)+i[e]+i.substr(e+1):u(n[e],r[e])<0?n.substr(0,e)+r[e]+i.substr(e+1):o>=0?n.substr(0,o)+t[o]+i.substr(o+1):null;u(n[e],h)<0&&(o=e)}return s<r.length&&f==="next"?n+i.substr(n.length):s<n.length&&f==="prev"?n.substr(0,i.length):o<0?null:n.substr(0,o)+r[o]+i.substr(o+1)}function t(n,t,i){function a(n){s=r(n);e=u(n);h=n==="next"?wi:or;c=s(i);o=e(i);l=n}var s,e,h,c,o,l;a("next");n._ondirectionchange=function(n){a(n)};n._addAlgorithm(function(n,i,r){var u=n.key,s,a;return typeof u!="string"?!1:(s=e(u),t(s,o)?(i(function(){n.continue()}),!0):(a=f(u,s,c,o,h,l),a?i(function(){n.continue(a)}):i(r),!1))})}return{between:function(n,t,i,r){return(i=i!==!1,r=r===!0,n>t||n===t&&(i||r)&&!(i&&r))?new this._ctx.collClass(this,function(){return kt.only(n)}).limit(0):new this._ctx.collClass(this,function(){return kt.bound(n,t,!i,!r)})},equals:function(n){return new this._ctx.collClass(this,function(){return kt.only(n)})},above:function(n){return new this._ctx.collClass(this,function(){return kt.lowerBound(n,!0)})},aboveOrEqual:function(n){return new this._ctx.collClass(this,function(){return kt.lowerBound(n)})},below:function(n){return new this._ctx.collClass(this,function(){return kt.upperBound(n,!0)})},belowOrEqual:function(n){return new this._ctx.collClass(this,function(){return kt.upperBound(n)})},startsWith:function(t){return typeof t!="string"?n(new this._ctx.collClass(this),new yi("String expected")):this.between(t,t+String.fromCharCode(65535),!0,!0)},startsWithIgnoreCase:function(i){if(typeof i!="string")return n(new this._ctx.collClass(this),new yi("String expected"));if(i==="")return this.startsWith(i);var r=new this._ctx.collClass(this,function(){return kt.bound(i.toUpperCase(),i.toLowerCase()+String.fromCharCode(65535))});return t(r,function(n,t){return n.indexOf(t)===0},i),r._ondirectionchange=function(){n(r,new g("reverse() not supported with WhereClause.startsWithIgnoreCase()"))},r},equalsIgnoreCase:function(i){if(typeof i!="string")return n(new this._ctx.collClass(this),new yi("String expected"));var r=new this._ctx.collClass(this,function(){return kt.bound(i.toUpperCase(),i.toLowerCase())});return t(r,function(n,t){return n===t},i),r},anyOf:function(){var f=this._ctx,e=f.table.schema,o=f.index?e.idxByName[f.index]:e.primKey,s=o&&o.compound,n=i(arguments),t=s?sr(wi):wi,u,r;return(n.sort(t),n.length===0)?new this._ctx.collClass(this,function(){return kt.only("")}).limit(0):(u=new this._ctx.collClass(this,function(){return kt.bound(n[0],n[n.length-1])}),u._ondirectionchange=function(i){t=i==="next"?wi:or;s&&(t=sr(t));n.sort(t)},r=0,u._addAlgorithm(function(i,u,f){for(var e=i.key;t(e,n[r])>0;)if(++r,r===n.length)return u(f),!1;return t(e,n[r])===0?(u(function(){i.continue()}),!0):(u(function(){i.continue(n[r])}),!1)}),u)}}});s(li.prototype,function(){function t(n,t){n.filter=bi(n.filter,t)}function f(n,t){n.isMatch=bi(n.isMatch,t)}function r(n,t){if(n.isPrimKey)return t;var i=n.table.schema.idxByName[n.index];if(!i)throw new g("KeyPath "+n.index+" on object store "+t.name+" is not indexed");return n.isPrimKey?t:t.index(i.name)}function u(n,t){return r(n,t)[n.op](n.range||null,n.dir+n.unique)}function i(n,t,i,r,f){n.or?function(){function e(){++c==2&&i()}function h(n,i,u){if(!o||o(i,u,e,r)){var f=i.primaryKey.toString();s.hasOwnProperty(f)||(s[f]=!0,t(n,i,u))}}var o=n.filter,s={},l=n.table.schema.primKey.keyPath,c=0;n.or._iterate(h,e,r,f);pi(u(n,f),n.algorithm,h,e,r,n.table.hook.reading.fire)}():pi(u(n,f),bi(n.algorithm,n.filter),t,i,r,n.table.hook.reading.fire)}function n(n){return n.table.schema.instanceTemplate}return{_read:function(n,t){var i=this._ctx;return i.error?i.table._trans(null,function(n,t){t(i.error)}):i.table._idbstore(ti,n).then(t)},_write:function(n){var t=this._ctx;return t.error?t.table._trans(null,function(n,i){i(t.error)}):t.table._idbstore(yt,n,"locked")},_addAlgorithm:function(n){var t=this._ctx;t.algorithm=bi(t.algorithm,n)},_iterate:function(n,t,r,u){return i(this._ctx,n,t,r,u)},each:function(t){var r=this._ctx;return e(function(){t(n(r))}),this._read(function(n,u,f){i(r,t,n,u,f)})},count:function(n){var f,t,u;return e(function(){n(0)}),f=this,t=this._ctx,t.filter||t.algorithm||t.or?(u=0,this._read(function(n,r,f){i(t,function(){return++u,!1},function(){n(u)},r,f)},n)):this._read(function(n,i,u){var e=r(t,u),s=t.range?e.count(t.range):e.count();s.onerror=o(i,["calling","count()","on",f.name]);s.onsuccess=function(i){n(Math.min(i.target.result,Math.max(0,t.limit-t.offset)))}},n)},sortBy:function(t,i){function u(n,t){return t?u(n[r[t]],t-1):n[h]}function c(n,t){var i=u(n,o),r=u(t,o);return i<r?-f:i>r?f:0}var s=this._ctx,f;e(function(){i([n(s)])});var r=t.split(".").reverse(),h=r[0],o=r.length-1;return f=this._ctx.dir==="next"?1:-1,this.toArray(function(n){return n.sort(c)}).then(i)},toArray:function(t){var r=this._ctx;return e(function(){t([n(r)])}),this._read(function(n,t,u){var f=[];i(r,function(n){f.push(n)},function(){n(f)},t,u)},t)},offset:function(n){var i=this._ctx;return n<=0?this:(i.offset+=n,i.or||i.algorithm||i.filter?t(i,function(){return--n<0}):t(i,function(t,i){return n===0?!0:n===1?(--n,!1):(i(function(){t.advance(n);n=0}),!1)}),this)},limit:function(n){return this._ctx.limit=Math.min(this._ctx.limit,n),t(this._ctx,function(t,i,r){return--n<=0&&i(r),n>=0}),this},until:function(i,r){var u=this._ctx;return e(function(){i(n(u))}),t(this._ctx,function(n,t,u){return i(n.value)?(t(u),r):!0}),this},first:function(t){var i=this;return e(function(){t(n(i._ctx))}),this.limit(1).toArray(function(n){return n[0]}).then(t)},last:function(n){return this.reverse().first(n)},and:function(i){var r=this;return e(function(){i(n(r._ctx))}),t(this._ctx,function(n){return i(n.value)}),f(this._ctx,i),this},or:function(n){return new ci(this._ctx.table,n,this)},reverse:function(){return this._ctx.dir=this._ctx.dir==="prev"?"next":"prev",this._ondirectionchange&&this._ondirectionchange(this._ctx.dir),this},desc:function(){return this.reverse()},eachKey:function(t){var i=this,r=this._ctx;return e(function(){t(n(i._ctx)[i._ctx.index])}),r.isPrimKey||(r.op="openKeyCursor"),this.each(function(n,i){t(i.key,i)})},eachUniqueKey:function(n){return this._ctx.unique="unique",this.eachKey(n)},keys:function(t){var u,i,r;return e(function(){t([n(i)[u._ctx.index]])}),u=this,i=this._ctx,i.isPrimKey||(i.op="openKeyCursor"),r=[],this.each(function(n,t){r.push(t.key)}).then(function(){return r}).then(t)},uniqueKeys:function(n){return this._ctx.unique="unique",this.keys(n)},firstKey:function(n){var t=this;return this.limit(1).keys(function(n){return n[0]}).then(n)},lastKey:function(n){return this.reverse().firstKey(n)},distinct:function(){var n={};return t(this._ctx,function(t){var i=t.primaryKey.toString(),r=n.hasOwnProperty(i);return n[i]=!0,!r}),this}}});y(rr).from(li).extend({modify:function(n){var a=this,t=this._ctx,r=t.table.hook,i=r.updating.fire,u=r.deleting.fire;return e(function(){typeof n=="function"&&n.call({value:t.table.schema.instanceTemplate},t.table.schema.instanceTemplate)}),this._write(function(r,e,v,y){function st(n,i){var r,u,f;if(et=i.primaryKey,r={primKey:i.primaryKey,value:n},w.call(r,n)!==!1)u=!r.hasOwnProperty("value"),f=u?i.delete():i.update(r.value),++ut,f.onerror=o(function(n){if(p.push(n),nt.push(r.primKey),r.onerror)r.onerror(n);return it(),!0},u?["deleting",n,"from",t.table.name]:["modifying",n,"on",t.table.name]),f.onsuccess=function(){if(r.onsuccess)r.onsuccess(r.value);++b;it()};else if(r.onsuccess)r.onsuccess(r.value)}function ot(n){return n&&(p.push(n),nt.push(et)),e(new d("Error modifying one or more objects",p,b,nt))}function it(){ft&&b+p.length===ut&&(p.length>0?ot():r(b))}var w,k,rt,g;typeof n=="function"?w=i===f&&u===f?n:function(t){var f=l(t),e,r;if(n.call(this,t)===!1)return!1;this.hasOwnProperty("value")?(e=dt(f,this.value),r=i.call(this,e,this.primKey,f,y),r&&(t=this.value,Object.keys(r).forEach(function(n){h(t,n,r[n])}))):u.call(this,this.primKey,t,y)}:i===f?(k=Object.keys(n),rt=k.length,w=function(t){for(var i,u,f=!1,r=0;r<rt;++r)i=k[r],u=n[i],c(t,i)!==u&&(h(t,i,u),f=!0);return f}):(g=n,n=tt(g),w=function(t){var u=!1,r=i.call(this,n,this.primKey,l(t),y);return r&&s(n,r),Object.keys(n).forEach(function(i){var r=n[i];c(t,i)!==r&&(h(t,i,r),u=!0)}),r&&(n=tt(g)),u});var ut=0,b=0,ft=!1,p=[],nt=[],et=null;a._iterate(st,function(){ft=!0;it()},ot,v)})},"delete":function(){return this.modify(function(){delete this.value})}});s(this,{Collection:li,Table:ei,Transaction:ir,Version:di,WhereClause:ci,WriteableCollection:rr,WriteableTable:tr});cr();hr.forEach(function(n){n(w)})}function f(){}function nt(n){return n}function lt(n,t){return n===nt?t:function(i){return t(n(i))}}function w(n,t){return function(){n.apply(this,arguments);t.apply(this,arguments)}}function at(n,t){return n===f?t:function(){var f=n.apply(this,arguments),r,u,e;return f!==i&&(arguments[0]=f),r=this.onsuccess,u=this.onerror,delete this.onsuccess,delete this.onerror,e=t.apply(this,arguments),r&&(this.onsuccess=this.onsuccess?w(r,this.onsuccess):r),u&&(this.onerror=this.onerror?w(u,this.onerror):u),e!==i?e:f}}function vt(n,t){return n===f?t:function(){var r=n.apply(this,arguments),f,e,u;return r!==i&&s(arguments[0],r),f=this.onsuccess,e=this.onerror,delete this.onsuccess,delete this.onerror,u=t.apply(this,arguments),f&&(this.onsuccess=this.onsuccess?w(f,this.onsuccess):f),e&&(this.onerror=this.onerror?w(e,this.onerror):e),r===i?u===i?i:u:u===i?r:s(r,u)}}function yt(n,t){return n===f?t:function(){return n.apply(this,arguments)===!1?!1:t.apply(this,arguments)}}function pt(n,t){return n===f?t:function(){return t.apply(this,arguments)===!1?!1:n.apply(this,arguments)}}function wt(n,t){return n===f?t:function(){n.apply(this,arguments);t.apply(this,arguments)}}function bt(n,t){return n===f?t:function(){var i=n.apply(this,arguments),r,u;return i&&typeof i.then=="function"?(r=this,u=arguments,i.then(function(){return t.apply(r,u)})):t.apply(this,arguments)}}function v(t){function i(n,t,i){if(Array.isArray(n))return c(n);if(typeof n=="object")return h(n);t||(t=yt);i||(i=f);var r={subscribers:[],fire:i,subscribe:function(n){r.subscribers.push(n);r.fire=t(r.fire,n)},unsubscribe:function(n){r.subscribers=r.subscribers.filter(function(t){return t!==n});r.fire=r.subscribers.reduce(t,i)}};return u[n]=e[n]=r,r}function h(t){Object.keys(t).forEach(function(r){var f=t[r],u;if(Array.isArray(f))i(r,t[r][0],t[r][1]);else if(f==="asap")u=i(r,null,function(){var t=arguments;u.subscribers.forEach(function(i){b(function(){i.apply(n,t)})})}),u.subscribe=function(n){u.subscribers.indexOf(n)===-1&&u.subscribers.push(n)},u.unsubscribe=function(n){var t=u.subscribers.indexOf(n);t!==-1&&u.subscribers.splice(t,1)};else throw new Error("Invalid event config");})}function c(n){function r(){if(t)return!1;t=!0}var t=!1;n.forEach(function(n){i(n).subscribe(r)})}var o=arguments,u={},e=function(n,i){if(i){var f=[].slice.call(arguments,1),r=u[n];return r.subscribe.apply(r,f),t}if(typeof n=="string")return u[n]},r,s;for(e.addEventType=i,r=1,s=o.length;r<s;++r)i(o[r]);return e}function kt(n){if(!n)throw new Error("Assertion failed");}function b(t){n.setImmediate?setImmediate(t):setTimeout(t,0)}function et(n){var t=setTimeout(n,1e3);clearTimeout(t)}function k(n,t,i){return function(){var u=r.PSD;r.PSD=i;try{n.apply(this,arguments)}catch(f){t(f)}finally{r.PSD=u}}}function c(n,t){var f,r,o,s,u,e;if(n.hasOwnProperty(t))return n[t];if(!t)return n;if(typeof t!="string"){for(f=[],r=0,o=t.length;r<o;++r)s=c(n,t[r]),f.push(s);return f}return(u=t.indexOf("."),u!==-1)?(e=n[t.substr(0,u)],e===i?i:c(e,t.substr(u+1))):i}function h(n,t,r){var u,c,e,f,s,o;if(n&&t!==i)if(typeof t!="string"&&"length"in t)for(kt(typeof r!="string"&&"length"in r),u=0,c=t.length;u<c;++u)h(n,t[u],r[u]);else e=t.indexOf("."),e!==-1?(f=t.substr(0,e),s=t.substr(e+1),s===""?r===i?delete n[f]:n[f]=r:(o=n[f],o||(o=n[f]={}),h(o,s,r))):r===i?delete n[t]:n[t]=r}function ot(n,t){h(n,t,i)}function tt(n){var i={};for(var t in n)n.hasOwnProperty(t)&&(i[t]=n[t]);return i}function l(n){var t,i,u,r;if(!n||typeof n!="object")return n;if(Array.isArray(n))for(t=[],i=0,u=n.length;i<u;++i)t.push(l(n[i]));else if(n instanceof Date)t=new Date,t.setTime(n.getTime());else{t=n.constructor?Object.create(n.constructor.prototype):{};for(r in n)n.hasOwnProperty(r)&&(t[r]=l(n[r]))}return t}function dt(n,t){var u={};for(var r in n)n.hasOwnProperty(r)&&(t.hasOwnProperty(r)?n[r]!==t[r]&&JSON.stringify(n[r])!=JSON.stringify(t[r])&&(u[r]=t[r]):u[r]=i);for(r in t)t.hasOwnProperty(r)&&!n.hasOwnProperty(r)&&(u[r]=t[r]);return u}function st(n){if(typeof n=="function")return new n;if(Array.isArray(n))return[st(n[0])];if(n&&typeof n=="object"){var t={};return it(t,n),t}return n}function it(n,t){Object.keys(t).forEach(function(i){var r=st(t[i]);n[i]=r})}function o(n,t){return function(i){var r=i&&i.target.error||new Error,u;return t&&(u=" occurred when "+t.map(function(n){switch(typeof n){case"function":return n();case"string":return n;default:return JSON.stringify(n)}}).join(" "),r.name?r.toString=function(){return r.name+u+(r.message?". "+r.message:"")}:r=r+u),n(r),i&&(i.stopPropagation&&i.stopPropagation(),i.preventDefault&&i.preventDefault()),!1}}function gt(n){try{throw n;}catch(t){return t}}function ht(n){n.preventDefault()}function rt(n){var t,i=u.dependencies.localStorage;if(!i)return n([]);try{t=JSON.parse(i.getItem("Dexie.DatabaseNames")||"[]")}catch(r){t=[]}n(t)&&i.setItem("Dexie.DatabaseNames",JSON.stringify(t))}function a(n,t,i,r,u,f,e){this.name=n;this.keyPath=t;this.unique=i;this.multi=r;this.auto=u;this.compound=f;this.dotted=e;var o=typeof t=="string"?t:t&&"["+[].join.call(t,"+")+"]";this.src=(i?"&":"")+(r?"*":"")+(u?"++":"")+o}function ut(n,t,i,r){this.name=n;this.primKey=t||new a;this.indexes=i||[new a];this.instanceTemplate=r;this.mappedClass=null;this.idxByName=i.reduce(function(n,t){return n[t.name]=t,n},{})}function d(n,t,i,r){this.name="ModifyError";this.failures=t;this.failedKeys=r;this.successCount=i;this.message=t.join("\n")}function ft(n){return n.length===1?n[0]:n}function ct(){var n=u.dependencies.indexedDB,t=n&&(n.getDatabaseNames||n.webkitGetDatabaseNames);return t&&t.bind(n)}var r=function(){function y(n){u.push([n,a.call(arguments,1)])}function p(){var r=u,t,f,i;for(u=[],t=0,f=r.length;t<f;++t)i=r[t],i[0].apply(n,i[1])}function t(n){if(typeof this!="object")throw new TypeError("Promises must be constructed via new");if(typeof n!="function")throw new TypeError("not a function");this._state=null;this._value=null;this._deferreds=[];this._catched=!1;var r=this,u=!0;this._PSD=t.PSD;try{k(this,n,function(n){u?i(c,r,n):c(r,n)},function(n){return u?(i(s,r,n),!1):s(r,n)})}finally{u=!1}}function o(n,f){var s,o,l,a,b,c;if(n._state===null){n._deferreds.push(f);return}if(s=n._state?f.onFulfilled:f.onRejected,s===null)return(n._state?f.resolve:f.reject)(n._value);l=r;r=!1;i=y;try{a=t.PSD;t.PSD=n._PSD;o=s(n._value);n._state||o&&typeof o.then=="function"&&o._state===!1||w(n);f.resolve(o)}catch(v){if(b=f.reject(v),!b&&n.onuncatched)try{n.onuncatched(v)}catch(v){}}finally{if(t.PSD=a,l){do{while(u.length>0)p();if(c=e.pop(),c)try{c()}catch(v){}}while(e.length>0||u.length>0);i=h;r=!0}}}function d(n){var f=r,t;r=!1;i=y;try{n()}finally{if(f){do{while(u.length>0)p();if(t=e.pop(),t)try{t()}catch(o){}}while(e.length>0||u.length>0);i=h;r=!0}}}function w(n){n._catched=!0;n._parent&&w(n._parent)}function c(n,i){var r=t.PSD;t.PSD=n._PSD;try{if(i===n)throw new TypeError("A promise cannot be resolved with itself.");if(i&&(typeof i=="object"||typeof i=="function")&&typeof i.then=="function"){k(n,function(n,t){i.then(n,t)},function(t){c(n,t)},function(t){s(n,t)});return}n._state=!0;n._value=i;b.call(n)}catch(u){s(u)}finally{t.PSD=r}}function s(n,i){var r=t.PSD;if(t.PSD=n._PSD,n._state=!1,n._value=i,b.call(n),!n._catched)try{if(n.onuncatched)n.onuncatched(n._value);t.on.error.fire(n._value)}catch(u){}return t.PSD=r,n._catched}function b(){for(var n=0,t=this._deferreds.length;n<t;n++)o(this,this._deferreds[n]);this._deferreds=[]}function l(n,t,i,r){this.onFulfilled=typeof n=="function"?n:null;this.onRejected=typeof t=="function"?t:null;this.resolve=i;this.reject=r}function k(n,t,i,r){var u=!1;try{t(function(n){u||(u=!0,i(n))},function(t){return u?n._catched:(u=!0,r(t))})}catch(f){return u?void 0:r(f)}}var a=[].slice,h=typeof setImmediate=="undefined"?function(t){var i=arguments;setTimeout(function(){t.apply(n,a.call(i,1))},0)}:setImmediate,i=h,r=!0,u=[],e=[];return t.on=v(null,"error"),t.all=function(){var n=Array.prototype.slice.call(arguments.length===1&&Array.isArray(arguments[0])?arguments[0]:arguments);return new t(function(t,i){function f(r,e){try{if(e&&(typeof e=="object"||typeof e=="function")){var o=e.then;if(typeof o=="function"){o.call(e,function(n){f(r,n)},i);return}}n[r]=e;--u==0&&t(n)}catch(s){i(s)}}var u,r;if(n.length===0)return t([]);for(u=n.length,r=0;r<n.length;r++)f(r,n[r])})},t.prototype.then=function(n,r){var f=this,u=new t(function(t,u){f._state===null?o(f,new l(n,r,t,u)):i(o,f,new l(n,r,t,u))});return u._PSD=this._PSD,u.onuncatched=this.onuncatched,u._parent=this,u},t.prototype._then=function(n,t){o(this,new l(n,t,f,f))},t.prototype["catch"]=function(n){if(arguments.length===1)return this.then(null,n);var i=arguments[0],r=arguments[1];return typeof i=="function"?this.then(null,function(n){return n instanceof i?r(n):t.reject(n)}):this.then(null,function(n){return n&&n.name===i?r(n):t.reject(n)})},t.prototype["finally"]=function(n){return this.then(function(t){return n(),t},function(i){return n(),t.reject(i)})},t.prototype.onuncatched=null,t.resolve=function(n){var i=new t(function(){});return i._state=!0,i._value=n,i},t.reject=function(n){var i=new t(function(){});return i._state=!1,i._value=n,i},t.race=function(n){return new t(function(t,i){n.map(function(n){n.then(t,i)})})},t.PSD=null,t.newPSD=function(n){var i=t.PSD;t.PSD=i?Object.create(i):{};try{return n()}finally{t.PSD=i}},t._rootExec=d,t._tickFinalize=function(n){if(r)throw new Error("Not in a virtual tick");e.push(n)},t}(),e=function(){},g;y(d).from(Error);u.delete=function(n){var t=new u(n),i=t.delete();return i.onblocked=function(n){t.on("blocked",n);return this},i};u.getDatabaseNames=function(n){return new r(function(n,t){var r=ct(),i;r?(i=r(),i.onsuccess=function(t){n([].slice.call(t.target.result,0))},i.onerror=o(t)):rt(function(t){return n(t),!1})}).then(n)};u.defineClass=function(n){function t(n){n&&s(this,n)}return it(t.prototype,n),t};u.ignoreTransaction=function(n){return r.newPSD(function(){return r.PSD.trans=null,n()})};u.spawn=function(){return n.console&&console.warn("Dexie.spawn() is deprecated. Use Dexie.ignoreTransaction() instead."),u.ignoreTransaction.apply(this,arguments)};u.vip=function(n){return r.newPSD(function(){return r.PSD.letThrough=!0,n()})};Object.defineProperty(u,"currentTransaction",{get:function(){return r.PSD&&r.PSD.trans||null}});u.Promise=r;u.derive=y;u.extend=s;u.override=p;u.events=v;u.getByKeyPath=c;u.setByKeyPath=h;u.delByKeyPath=ot;u.shallowClone=tt;u.deepClone=l;u.addons=[];u.fakeAutoComplete=e;u.asap=b;u.ModifyError=d;u.MultiModifyError=d;u.IndexSpec=a;u.TableSchema=ut;g=n.idbModules&&n.idbModules.shimIndexedDB?n.idbModules:{};u.dependencies={indexedDB:g.shimIndexedDB||n.indexedDB||n.mozIndexedDB||n.webkitIndexedDB||n.msIndexedDB,IDBKeyRange:g.IDBKeyRange||n.IDBKeyRange||n.webkitIDBKeyRange,IDBTransaction:g.IDBTransaction||n.IDBTransaction||n.webkitIDBTransaction,Error:n.Error||String,SyntaxError:n.SyntaxError||String,TypeError:n.TypeError||String,DOMError:n.DOMError||String,localStorage:(typeof chrome!="undefined"&&chrome!==null?chrome.storage:void 0)!=null?null:n.localStorage};u.version=1.1;t("Dexie",u);et(function(){e=et})}).apply(null,typeof define=="function"&&define.amd?[self||window,function(n,t){define(n,function(){return t})}]:typeof global!="undefined"&&typeof module!="undefined"&&module.exports?[global,function(n,t){module.exports=t}]:[self||window,function(n,t){(self||window)[n]=t}]); |
| //# sourceMappingURL=Dexie.min.js.map |