[Pkg-javascript-commits] [node-asn1.js] 03/202: der: encoder

Bastien Roucariès rouca at moszumanska.debian.org
Thu Apr 20 19:18:49 UTC 2017


This is an automated email from the git hooks/post-receive script.

rouca pushed a commit to branch master
in repository node-asn1.js.

commit e7c2bb12da23ad45fae136a51b1592371a40d8ff
Author: Fedor Indutny <fedor.indutny at gmail.com>
Date:   Sun Dec 1 22:31:22 2013 +0400

    der: encoder
---
 lib/asn1.js                             |   1 +
 lib/asn1/base/buffer.js                 |  54 ++++-
 lib/asn1/base/index.js                  |   3 +-
 lib/asn1/base/node.js                   | 382 +++++++++++++++++++++-----------
 lib/asn1/constants/der.js               |  42 ++++
 lib/asn1/constants/index.js             |  19 ++
 lib/asn1/decoders/der.js                |  84 +++----
 lib/asn1/encoders/der.js                | 193 ++++++++++++++++
 lib/asn1/encoders/index.js              |   3 +
 test/ping-pong-test.js                  | 115 ++++++++++
 test/{basic-test.js => rfc2560-test.js} |   5 +-
 11 files changed, 711 insertions(+), 190 deletions(-)

diff --git a/lib/asn1.js b/lib/asn1.js
index d4a1ce5..7dc4b33 100644
--- a/lib/asn1.js
+++ b/lib/asn1.js
@@ -2,6 +2,7 @@ var asn1 = exports;
 
 asn1.define = require('./asn1/api').define;
 asn1.base = require('./asn1/base');
+asn1.constants = require('./asn1/constants');
 asn1.decoders = require('./asn1/decoders');
 asn1.encoders = require('./asn1/encoders');
 asn1.modules = require('./modules');
diff --git a/lib/asn1/base/buffer.js b/lib/asn1/base/buffer.js
index bab837f..30d9a8d 100644
--- a/lib/asn1/base/buffer.js
+++ b/lib/asn1/base/buffer.js
@@ -8,7 +8,7 @@ function DecoderBuffer(base) {
   this.offset = 0;
   this.length = base.length;
 }
-module.exports = DecoderBuffer;
+exports.DecoderBuffer = DecoderBuffer;
 
 DecoderBuffer.prototype.save = function save() {
   return { offset: this.offset };
@@ -46,3 +46,55 @@ DecoderBuffer.prototype.skip = function skip(bytes) {
 DecoderBuffer.prototype.raw = function raw() {
   return this.base.slice(this.offset, this.length);
 }
+
+function EncoderBuffer(value) {
+  if (Array.isArray(value)) {
+    this.length = 0;
+    this.value = value.map(function(item) {
+      if (!(item instanceof EncoderBuffer))
+        item = new EncoderBuffer(item);
+      this.length += item.length;
+      return item;
+    }, this);
+  } else if (typeof value === 'number') {
+    assert(0 <= value && value <= 0xff, 'non-byte EncoderBuffer value');
+    this.value = value;
+    this.length = 1;
+  } else if (typeof value === 'string') {
+    this.value = value;
+    this.length = Buffer.byteLength(value);
+  } else if (Buffer.isBuffer(value)) {
+    this.value = value;
+    this.length = value.length;
+  } else {
+    assert(0, 'Unsupported type: ' + typeof value);
+  }
+}
+exports.EncoderBuffer = EncoderBuffer;
+
+EncoderBuffer.prototype.join = function join(out, offset) {
+  if (!out)
+    out = new Buffer(this.length);
+  if (!offset)
+    offset = 0;
+
+  if (this.length === 0)
+    return out;
+
+  if (Array.isArray(this.value)) {
+    this.value.forEach(function(item) {
+      item.join(out, offset);
+      offset += item.length;
+    });
+  } else {
+    if (typeof this.value === 'number')
+      out[offset] = this.value;
+    else if (typeof this.value === 'string')
+      out.write(this.value, offset);
+    else if (Buffer.isBuffer(this.value))
+      this.value.copy(out, offset);
+    offset += this.length;
+  }
+
+  return out;
+};
diff --git a/lib/asn1/base/index.js b/lib/asn1/base/index.js
index 9a71ab1..a46492c 100644
--- a/lib/asn1/base/index.js
+++ b/lib/asn1/base/index.js
@@ -1,4 +1,5 @@
 var base = exports;
 
-base.DecoderBuffer = require('./buffer');
+base.DecoderBuffer = require('./buffer').DecoderBuffer;
+base.EncoderBuffer = require('./buffer').EncoderBuffer;
 base.Node = require('./node');
diff --git a/lib/asn1/base/node.js b/lib/asn1/base/node.js
index d7d8a59..7686fda 100644
--- a/lib/asn1/base/node.js
+++ b/lib/asn1/base/node.js
@@ -1,29 +1,33 @@
 var assert = require('assert');
+var EncoderBuffer = require('../base').EncoderBuffer;
 
 // Supported tags
 var tags = [
-  'seq', 'seqof', 'octstr', 'bitstr', 'objid',
+  'seq', 'seqof', 'set', 'setof', 'octstr', 'bitstr', 'objid', 'bool',
   'gentime', 'utctime', 'null_', 'enum', 'int'
 ];
 
 // Public methods list
 var methods = [
-  'key', 'obj', 'use', 'optional', 'explicit', 'implicit', 'def', 'choice'
+  'key', 'obj', 'use', 'optional', 'explicit', 'implicit', 'def', 'choice',
+  'any'
 ].concat(tags);
 
 // Overrided methods list
 var overrided = [
-  '_peekTag', '_execTag', '_execUse',
-  '_execStr', '_execObjid', '_execTime', '_execNull', '_execInt', '_execBool',
-  '_execOf'
+  '_peekTag', '_decodeTag', '_use',
+  '_decodeStr', '_decodeObjid', '_decodeTime',
+  '_decodeNull', '_decodeInt', '_decodeBool', '_decodeList',
+
+  '_encodeComposite', '_encodeStr', '_encodeObjid', '_encodeTime',
+  '_encodeNull', '_encodeInt', '_encodeBool'
 ];
 
-function Node(enc, type, parent) {
+function Node(enc, parent) {
   var state = {};
   this._baseState = state;
 
   state.enc = enc;
-  state.type = type;
 
   state.parent = parent || null;
   state.children = null;
@@ -31,6 +35,7 @@ function Node(enc, type, parent) {
   // State
   state.tag = null;
   state.args = null;
+  state.reverseArgs = null;
   state.choice = null;
   state.optional = false;
   state.any = false;
@@ -96,131 +101,26 @@ Node.prototype._useArgs = function useArgs(args) {
   if (args.length !== 0) {
     assert(state.args === null);
     state.args = args;
-  }
-};
-
-// Execute node
-Node.prototype._exec = function exec(input, obj) {
-  var state = this._baseState;
-
-  // Exec root node
-  if (state.parent === null)
-    return state.children[0]._exec(input);
-
-  var result = state['default'];
-  var present = true;
-
-  // Check if tag is there
-  if (state.optional) {
-    present = this._peekTag(
-      input,
-      state.explicit !== null ? state.explicit :
-          state.implicit !== null ? state.implicit :
-              state.tag || 0
-    );
-  }
-
-  // Push object on stack
-  if (state.obj && present) {
-    var prevObj = obj;
-    obj = {};
-  }
-
-  if (present) {
-    // Unwrap explicit values
-    if (state.explicit !== null)
-      input = this._execTag(input, state.explicit);
-
-    // Unwrap implicit and normal values
-    if (state.use === null && !state.any && state.choice === null) {
-      input = this._execTag(
-        input,
-        state.implicit !== null ? state.implicit : state.tag,
-        state.any
-      );
-    }
-
-    // Select proper method for tag
-    if (state.any)
-      result = input.raw();
-    if (state.choice !== null)
-      result = this._execChoice(input, obj);
-    else
-      result = this._execByTag(state.tag, input);
-
-    // Execute children
-    if (!state.any && state.choice === null && state.children !== null) {
-      state.children.forEach(function execChildren(child) {
-        child._exec(input, obj);
+    state.reverseArgs = args.map(function(arg) {
+      if (typeof arg !== 'object' || arg.constructor !== Object)
+        return arg;
+
+      var res = {};
+      Object.keys(arg).forEach(function(key) {
+        if (key == (key | 0))
+          key |= 0;
+        var value = arg[key];
+        res[value] = key;
       });
-    }
-  }
-
-  // Pop object
-  if (state.obj && present) {
-    result = obj;
-    obj = prevObj;
+      return res;
+    });
   }
-
-  // Set key
-  if (state.key !== null)
-    obj[state.key] = result;
-
-  return result;
-};
-
-Node.prototype._execChoice = function execChoice(input, obj) {
-  var state = this._baseState;
-  var result = null;
-  var match = false;
-
-  Object.keys(state.choice).some(function(key) {
-    var save = input.save();
-    var node = state.choice[key];
-    try {
-      result = { type: key, value: node._exec(input, obj) };
-      match = true;
-    } catch (e) {
-      input.restore(save);
-      return false;
-    }
-    return true;
-  }, this);
-
-  assert(match, 'Choice not matched');
-  return result;
-};
-
-Node.prototype._execByTag = function execByTag(tag, input) {
-  var state = this._baseState;
-
-  if (tag === 'seq' || tag === 'set')
-    return null;
-  if (tag === 'seqof' || tag === 'setof')
-    return this._execOf(input, tag, state.args[0]);
-  else if (tag === 'octstr' || tag === 'bitstr')
-    return this._execStr(input, tag);
-  else if (tag === 'objid' && state.args)
-    return this._execObjid(input, state.args[0], state.args[1]);
-  else if (tag === 'objid')
-    return this._execObjid(input, null, null);
-  else if (tag === 'gentime')
-    return this._execTime(input, tag);
-  else if (tag === 'null_')
-    return this._execNull(input);
-  else if (tag === 'bool')
-    return this._execBool(input);
-  else if (tag === 'int' || tag === 'enum')
-    return this._execInt(input, state.args && state.args[0]);
-  else if (state.use !== null)
-    return this._execUse(input, state.use);
-  else
-    assert(false, 'unknown tag: ' + tag);
-
-  return null;
 };
 
+//
 // Overrided methods
+//
+
 overrided.forEach(function(method) {
   Node.prototype[method] = function _overrided() {
     var state = this._baseState;
@@ -228,7 +128,9 @@ overrided.forEach(function(method) {
   };
 });
 
+//
 // Public methods
+//
 
 tags.forEach(function(tag) {
   Node.prototype[tag] = function _tagMethod() {
@@ -329,3 +231,229 @@ Node.prototype.choice = function choice(obj) {
 
   return this;
 };
+
+//
+// Decoding
+//
+
+Node.prototype._decode = function decode(input, obj) {
+  var state = this._baseState;
+
+  // Decode root node
+  if (state.parent === null)
+    return state.children[0]._decode(input);
+
+  var result = state['default'];
+  var present = true;
+
+  // Check if tag is there
+  if (state.optional) {
+    present = this._peekTag(
+      input,
+      state.explicit !== null ? state.explicit :
+          state.implicit !== null ? state.implicit :
+              state.tag || 0
+    );
+  }
+
+  // Push object on stack
+  if (state.obj && present) {
+    var prevObj = obj;
+    obj = {};
+  }
+
+  if (present) {
+    // Unwrap explicit values
+    if (state.explicit !== null)
+      input = this._decodeTag(input, state.explicit);
+
+    // Unwrap implicit and normal values
+    if (state.use === null && !state.any && state.choice === null) {
+      input = this._decodeTag(
+        input,
+        state.implicit !== null ? state.implicit : state.tag,
+        state.any
+      );
+    }
+
+    // Select proper method for tag
+    if (state.any)
+      result = input.raw();
+    else if (state.choice === null)
+      result = this._decodeGeneric(state.tag, input);
+    else
+      result = this._decodeChoice(input, obj);
+
+    // Decode children
+    if (!state.any && state.choice === null && state.children !== null) {
+      state.children.forEach(function decodeChildren(child) {
+        child._decode(input, obj);
+      });
+    }
+  }
+
+  // Pop object
+  if (state.obj && present) {
+    result = obj;
+    obj = prevObj;
+  }
+
+  // Set key
+  if (state.key !== null)
+    obj[state.key] = result;
+
+  return result;
+};
+
+Node.prototype._decodeGeneric = function decodeGeneric(tag, input) {
+  var state = this._baseState;
+
+  if (tag === 'seq' || tag === 'set')
+    return null;
+  if (tag === 'seqof' || tag === 'setof')
+    return this._decodeList(input, tag, state.args[0]);
+  else if (tag === 'octstr' || tag === 'bitstr')
+    return this._decodeStr(input, tag);
+  else if (tag === 'objid' && state.args)
+    return this._decodeObjid(input, state.args[0], state.args[1]);
+  else if (tag === 'objid')
+    return this._decodeObjid(input, null, null);
+  else if (tag === 'gentime')
+    return this._decodeTime(input, tag);
+  else if (tag === 'null_')
+    return this._decodeNull(input);
+  else if (tag === 'bool')
+    return this._decodeBool(input);
+  else if (tag === 'int' || tag === 'enum')
+    return this._decodeInt(input, state.args && state.args[0]);
+  else if (state.use !== null)
+    return this._use(input, state.use);
+  else
+    assert(false, 'unknown tag: ' + tag);
+
+  return null;
+};
+
+Node.prototype._decodeChoice = function decodeChoice(input, obj) {
+  var state = this._baseState;
+  var result = null;
+  var match = false;
+
+  Object.keys(state.choice).some(function(key) {
+    var save = input.save();
+    var node = state.choice[key];
+    try {
+      result = { type: key, value: node._decode(input, obj) };
+      match = true;
+    } catch (e) {
+      input.restore(save);
+      return false;
+    }
+    return true;
+  }, this);
+
+  assert(match, 'Choice not matched');
+  return result;
+};
+
+//
+// Encoding
+//
+
+Node.prototype._encode = function encode(data) {
+  var state = this._baseState;
+
+  // Decode root node
+  if (state.parent === null)
+    return state.children[0]._encode(data);
+
+  var result = null;
+  var present = true;
+
+  // Check if data is there
+  if (state.optional && data === undefined) {
+    if (state['default'] !== null)
+      data = state['default']
+    else
+      return;
+  }
+
+  // Encode children first
+  var content = null;
+  var primitive = false;
+  if (state.any) {
+    // Anything that was given is translated to buffer
+    result = new EncoderBuffer(data);
+  } else if (state.children) {
+    content = state.children.map(function(child) {
+      assert(child._baseState.key, 'Child should have a key');
+      return child._encode(data[child._baseState.key]);
+    }, this).filter(function(child) {
+      return child;
+    });
+
+    content = new EncoderBuffer(content);
+  } else {
+    if (state.choice === null) {
+      if (state.tag === 'seqof' || state.tag === 'setof') {
+        assert(state.args && state.args.length === 1);
+        assert(Array.isArray(data), 'seqof/setof, but data is not Array');
+
+        content = new EncoderBuffer(data.map(function(item) {
+          return this._use(state.args[0], item);
+        }, this));
+      } else if (state.use !== null) {
+        content = this._use(state.use, data);
+      } else {
+        content = this._encodePrimitive(state.tag, data);
+        primitive = true;
+      }
+    } else {
+      result = this._encodeChoice(data);
+    }
+  }
+
+  // Encode data itself
+  var result;
+  if (!state.any && state.choice === null) {
+    result = this._encodeComposite(
+      state.implicit !== null ? state.implicit : state.tag,
+      primitive,
+      content
+    );
+  }
+
+  // Wrap in explicit
+  if (state.explicit !== null)
+    result = this._encodeComposite(state.explicit, false, result);
+
+  return result;
+};
+
+Node.prototype._encodeChoice = function encodeChoice(data) {
+  var state = this._baseState;
+
+  var node = state.choice[data.type];
+  return node._encode(data.value);
+};
+
+Node.prototype._encodePrimitive = function encodePrimitive(tag, data) {
+  var state = this._baseState;
+
+  if (tag === 'octstr' || tag === 'bitstr')
+    return this._encodeStr(data, tag);
+  else if (tag === 'objid' && state.args)
+    return this._encodeObjid(data, state.reverseArgs[0], state.args[1]);
+  else if (tag === 'objid')
+    return this._encodeObjid(data, null, null);
+  else if (tag === 'gentime' || tag === 'utctime')
+    return this._encodeTime(data, tag);
+  else if (tag === 'null_')
+    return this._encodeNull();
+  else if (tag === 'int' || tag === 'enum')
+    return this._encodeInt(data, state.args && state.reverseArgs[0]);
+  else if (tag === 'bool')
+    return this._encodeBool(data);
+  else
+    throw new Error('Unsupported tag: ' + tag);
+};
diff --git a/lib/asn1/constants/der.js b/lib/asn1/constants/der.js
new file mode 100644
index 0000000..907dd39
--- /dev/null
+++ b/lib/asn1/constants/der.js
@@ -0,0 +1,42 @@
+var constants = require('../constants');
+
+exports.tagClass = {
+  0: 'universal',
+  1: 'application',
+  2: 'context',
+  3: 'private'
+};
+exports.tagClassByName = constants._reverse(exports.tagClass);
+
+exports.tag = {
+  0x00: 'end',
+  0x01: 'bool',
+  0x02: 'int',
+  0x03: 'bitstr',
+  0x04: 'octstr',
+  0x05: 'null_',
+  0x06: 'objid',
+  0x07: 'objDesc',
+  0x08: 'external',
+  0x09: 'real',
+  0x0a: 'enum',
+  0x0b: 'embed',
+  0x0c: 'utf8str',
+  0x0d: 'relativeOid',
+  0x10: 'seq',
+  0x11: 'set',
+  0x12: 'numstr',
+  0x13: 'printstr',
+  0x14: 't61str',
+  0x15: 'videostr',
+  0x16: 'ia5str',
+  0x17: 'utctime',
+  0x18: 'gentime',
+  0x19: 'graphstr',
+  0x1a: 'iso646str',
+  0x1b: 'genstr',
+  0x1c: 'unistr',
+  0x1d: 'charstr',
+  0x1e: 'bmpstr'
+};
+exports.tagByName = constants._reverse(exports.tag);
diff --git a/lib/asn1/constants/index.js b/lib/asn1/constants/index.js
new file mode 100644
index 0000000..c44e325
--- /dev/null
+++ b/lib/asn1/constants/index.js
@@ -0,0 +1,19 @@
+var constants = exports;
+
+// Helper
+constants._reverse = function reverse(map) {
+  var res = {};
+
+  Object.keys(map).forEach(function(key) {
+    // Convert key to integer if it is stringified
+    if ((key | 0) == key)
+      key = key | 0;
+
+    var value = map[key];
+    res[value] = key;
+  });
+
+  return res;
+};
+
+constants.der = require('./der');
diff --git a/lib/asn1/decoders/der.js b/lib/asn1/decoders/der.js
index 5ef28e8..f21359e 100644
--- a/lib/asn1/decoders/der.js
+++ b/lib/asn1/decoders/der.js
@@ -4,6 +4,9 @@ var util = require('util');
 var asn1 = require('../../asn1');
 var base = asn1.base;
 
+// Import DER constants
+var der = asn1.constants.der;
+
 function DERDecoder(entity) {
   this.enc = 'der';
   this.name = entity.name;
@@ -19,13 +22,14 @@ DERDecoder.prototype.decode = function decode(data) {
   if (!(data instanceof base.DecoderBuffer))
     data = new base.DecoderBuffer(data);
 
-  return this.tree._exec(data);
+  debugger;
+  return this.tree._decode(data);
 };
 
 // Tree methods
 
 function DERNode(parent) {
-  base.Node.call(this, 'der', 'decoder', parent);
+  base.Node.call(this, 'der', parent);
 }
 util.inherits(DERNode, base.Node);
 
@@ -34,15 +38,15 @@ DERNode.prototype._peekTag = function peekTag(buffer, tag) {
     return false;
 
   var state = buffer.save();
-  var decodedTag = decodeTag(buffer);
+  var decodedTag = derDecodeTag(buffer);
   buffer.restore(state);
 
   return decodedTag.tag === tag || decodedTag.tagStr === tag;
 };
 
-DERNode.prototype._execTag = function execTag(buffer, tag, any) {
-  var decodedTag = decodeTag(buffer);
-  var len = decodeLen(buffer, decodedTag.primitive);
+DERNode.prototype._decodeTag = function decodeTag(buffer, tag, any) {
+  var decodedTag = derDecodeTag(buffer);
+  var len = derDecodeLen(buffer, decodedTag.primitive);
 
   if (!any) {
     assert(decodedTag.tag === tag ||
@@ -62,8 +66,8 @@ DERNode.prototype._execTag = function execTag(buffer, tag, any) {
 
 DERNode.prototype._skipUntilEnd = function skipUntilEnd(buffer) {
   while (true) {
-    var tag = decodeTag(buffer);
-    var len = decodeLen(buffer, tag.primitive);
+    var tag = derDecodeTag(buffer);
+    var len = derDecodeLen(buffer, tag.primitive);
 
     if (tag.primitive || len !== null)
       buffer.skip(len)
@@ -75,7 +79,7 @@ DERNode.prototype._skipUntilEnd = function skipUntilEnd(buffer) {
   }
 };
 
-DERNode.prototype._execOf = function execOf(buffer, tag, decoder) {
+DERNode.prototype._decodeList = function decodeList(buffer, tag, decoder) {
   var result = [];
   while (!buffer.isEmpty()) {
     try {
@@ -89,11 +93,11 @@ DERNode.prototype._execOf = function execOf(buffer, tag, decoder) {
   return result;
 };
 
-DERNode.prototype._execStr = function execStr(buffer, tag) {
+DERNode.prototype._decodeStr = function decodeStr(buffer, tag) {
   return buffer.raw();
 };
 
-DERNode.prototype._execObjid = function execObjid(buffer, values, relative) {
+DERNode.prototype._decodeObjid = function decodeObjid(buffer, values, relative) {
   var identifiers = [];
   var ident = 0;
   while (!buffer.isEmpty()) {
@@ -122,9 +126,10 @@ DERNode.prototype._execObjid = function execObjid(buffer, values, relative) {
   return result;
 };
 
-DERNode.prototype._execTime = function execTime(buffer, tag) {
+DERNode.prototype._decodeTime = function decodeTime(buffer, tag) {
   assert.equal(tag, 'gentime');
 
+  // TODO(indutny): verify in spec
   var str = buffer.raw().toString();
   var year = str.slice(0, 4) | 0;
   var mon = str.slice(4, 6) | 0;
@@ -136,15 +141,15 @@ DERNode.prototype._execTime = function execTime(buffer, tag) {
   return Date.UTC(year, mon - 1, day, hour, min, sec, 0);
 };
 
-DERNode.prototype._execNull = function execNull(buffer) {
+DERNode.prototype._decodeNull = function decodeNull(buffer) {
   return null;
 };
 
-DERNode.prototype._execBool = function execBool(buffer) {
+DERNode.prototype._decodeBool = function decodeBool(buffer) {
   return buffer.readUInt8() !== 0;
 };
 
-DERNode.prototype._execInt = function execInt(buffer, values) {
+DERNode.prototype._decodeInt = function decodeInt(buffer, values) {
   var res = 0;
   while (!buffer.isEmpty()) {
     res <<= 8;
@@ -156,55 +161,16 @@ DERNode.prototype._execInt = function execInt(buffer, values) {
   return res;
 };
 
-DERNode.prototype._execUse = function execUse(buffer, decoder) {
+DERNode.prototype._use = function use(buffer, decoder) {
   return decoder.decode(buffer, 'der');
 };
 
 // Utility methods
 
-var tagClasses = {
-  0: 'universal',
-  1: 'application',
-  2: 'context',
-  3: 'private'
-};
-
-var tags = {
-  0x00: 'end',
-  0x01: 'bool',
-  0x02: 'int',
-  0x03: 'bitstr',
-  0x04: 'octstr',
-  0x05: 'null',
-  0x06: 'objid',
-  0x07: 'objDesc',
-  0x08: 'external',
-  0x09: 'real',
-  0x0a: 'enum',
-  0x0b: 'embed',
-  0x0c: 'utf8str',
-  0x0d: 'relativeOid',
-  0x10: 'seq',
-  0x11: 'set',
-  0x12: 'numstr',
-  0x13: 'printstr',
-  0x14: 't61str',
-  0x15: 'videostr',
-  0x16: 'ia5str',
-  0x17: 'utctime',
-  0x18: 'gentime',
-  0x19: 'graphstr',
-  0x1a: 'iso646str',
-  0x1b: 'genstr',
-  0x1c: 'unistr',
-  0x1d: 'charstr',
-  0x1e: 'bmpstr'
-};
-
-function decodeTag(buf) {
+function derDecodeTag(buf) {
   var tag = buf.readUInt8();
 
-  var cls = tagClasses[tag >> 6];
+  var cls = der.tagClass[tag >> 6];
   var primitive = (tag & 0x20) === 0;
 
   // Multi-octet tag - load
@@ -219,7 +185,7 @@ function decodeTag(buf) {
   } else {
     tag &= 0x1f;
   }
-  var tagStr = tags[tag];
+  var tagStr = der.tag[tag];
 
   return {
     cls: cls,
@@ -229,7 +195,7 @@ function decodeTag(buf) {
   };
 }
 
-function decodeLen(buf, primitive) {
+function derDecodeLen(buf, primitive) {
   var len = buf.readUInt8();
 
   // Indefinite form
diff --git a/lib/asn1/encoders/der.js b/lib/asn1/encoders/der.js
new file mode 100644
index 0000000..cc72d4e
--- /dev/null
+++ b/lib/asn1/encoders/der.js
@@ -0,0 +1,193 @@
+var assert = require('assert');
+var util = require('util');
+var Buffer = require('buffer').Buffer;
+
+var asn1 = require('../../asn1');
+var base = asn1.base;
+
+// Import DER constants
+var der = asn1.constants.der;
+
+function DEREncoder(entity) {
+  this.enc = 'der';
+  this.name = entity.name;
+  this.entity = entity;
+
+  // Construct base tree
+  this.tree = new DERNode();
+  this.tree._init(entity.body);
+};
+module.exports = DEREncoder;
+
+DEREncoder.prototype.encode = function encode(data) {
+  return this.tree._encode(data).join();
+};
+
+// Tree methods
+
+function DERNode(parent) {
+  base.Node.call(this, 'der', parent);
+}
+util.inherits(DERNode, base.Node);
+
+DERNode.prototype._encodeComposite = function encodeComposite(tag,
+                                                              primitive,
+                                                              content) {
+  var encodedTag = encodeTag(tag, primitive);
+
+  // Short form
+  if (content.length < 0x80) {
+    var header = new Buffer(2);
+    header[0] = encodedTag;
+    header[1] = content.length;
+    return new base.EncoderBuffer([ header, content ]);
+  }
+
+  // Long form
+  // Count octets required to store length
+  var lenOctets = 1;
+  for (var i = content.length; i >= 0x100; i >>= 8)
+    lenOctets++;
+
+  var header = new Buffer(1 + 1 + lenOctets);
+  header[0] = encodedTag;
+  header[1] = 0x80 | lenOctets;
+
+  for (var i = 1 + lenOctets, j = content.length; j > 0; i--, j >>= 8)
+    header[i] = j & 0xff;
+
+  return new base.EncoderBuffer([ header, content ]);
+};
+
+DERNode.prototype._encodeStr = function encodeStr(str, tag) {
+  if (tag === 'octstr')
+    return new base.EncoderBuffer(str);
+
+  // TODO(indunty): support first octet
+  else if (tag === 'bitstr')
+    return new base.EncoderBuffer([ 0, str ]);
+};
+
+DERNode.prototype._encodeObjid = function encodeObjid(id, values, relative) {
+  if (typeof id === 'string') {
+    assert(values, 'string objid given, but no values map found');
+    assert(values.hasOwnProperty(id), 'objid not found in values map');
+    id = values[id].split(/\s+/g);
+    for (var i = 0; i < id.length; i++)
+      id[i] |= 0;
+  }
+
+  assert(Array.isArray(id));
+  if (!relative) {
+    assert(id[1] < 40, 'Second objid identifier OOB');
+    id.splice(0, 2, id[0] * 40 + id[1]);
+  }
+
+  // Count number of octets
+  var size = 0;
+  for (var i = 0; i < id.length; i++) {
+    var ident = id[i];
+    for (size++; ident >= 0x80; ident >>= 7)
+      size++;
+  }
+
+  var objid = new Buffer(size);
+  var offset = objid.length - 1;
+  for (var i = id.length - 1; i >= 0; i--) {
+    var ident = id[i];
+    objid[offset--] = ident & 0x7f;
+    while ((ident >>= 7) > 0)
+      objid[offset--] = 0x80 | (ident & 0x7f);
+  }
+
+  return new base.EncoderBuffer(objid);
+};
+
+function two(num) {
+  if (num <= 10)
+    return '0' + num;
+  else
+    return num;
+}
+
+DERNode.prototype._encodeTime = function encodeTime(time, tag) {
+  var str;
+
+  // TODO(indutny): verify in spec
+  if (tag === 'gentime') {
+    var date = new Date(time);
+
+    str = [
+      date.getFullYear(),
+      two(date.getUTCMonth() + 1),
+      two(date.getUTCDate()),
+      two(date.getUTCHours()),
+      two(date.getUTCMinutes()),
+      two(date.getUTCSeconds()),
+      'Z'
+    ].join('');
+  } else {
+    assert(0, tag + ' time is not supported yet');
+  }
+
+  return this._encodeStr(str, 'octstr');
+};
+
+DERNode.prototype._encodeNull = function encodeNull() {
+  return new base.EncoderBuffer('');
+};
+
+DERNode.prototype._encodeInt = function encodeInt(num, values) {
+  if (typeof num === 'string') {
+    assert(values, 'String int or enum given, but no values map');
+    assert(values.hasOwnProperty(num), 'Values map doesn\'t contain number');
+    num = values[num];
+  }
+
+  var size = 1;
+  for (var i = num; i >= 0x100; i >>= 8)
+    size++;
+
+  var out = new Buffer(size);
+  for (var i = out.length - 1; i >= 0; i--) {
+    out[i] = num & 0xff;
+    num >>= 8;
+  }
+
+  return new base.EncoderBuffer(out);
+};
+
+DERNode.prototype._encodeBool = function encodeBool(value) {
+  return new base.EncoderBuffer(value ? 0xff : 0);
+};
+
+DERNode.prototype._use = function use(encoder, data) {
+  return encoder.encode(data, 'der');
+};
+
+// Utility methods
+
+function encodeTag(tag, primitive, cls) {
+  var res;
+
+  if (tag === 'seqof')
+    tag = 'seq';
+  else if (tag === 'setof')
+    tag = 'set';
+
+  if (der.tagByName.hasOwnProperty(tag))
+    res = der.tagByName[tag];
+  else if (typeof tag === 'number' && (tag | 0) === tag)
+    res = tag;
+  else
+    throw new Error('Unknown tag: ' + tag);
+
+  assert(res < 0x1f, 'Multi-octet tag encoding unsupported');
+
+  if (primitive)
+    res |= 0x20;
+
+  res |= der.tagClassByName[cls || 'universal'];
+
+  return res;
+}
diff --git a/lib/asn1/encoders/index.js b/lib/asn1/encoders/index.js
index e69de29..5b3cc38 100644
--- a/lib/asn1/encoders/index.js
+++ b/lib/asn1/encoders/index.js
@@ -0,0 +1,3 @@
+var encoders = exports;
+
+encoders.der = require('./der');
diff --git a/test/ping-pong-test.js b/test/ping-pong-test.js
new file mode 100644
index 0000000..fb652c7
--- /dev/null
+++ b/test/ping-pong-test.js
@@ -0,0 +1,115 @@
+var assert = require('assert');
+var asn1 = require('..');
+
+var Buffer = require('buffer').Buffer;
+
+describe('asn1.js ping/pong', function() {
+  function test(name, model, input, expected) {
+    it('should support ' + name, function() {
+      var M = asn1.define('TestModel', model);
+
+      var encoded = M.encode(input, 'der');
+      var decoded = M.decode(encoded, 'der');
+      assert.deepEqual(decoded, expected !== undefined ? expected : input);
+    });
+  }
+
+  describe('primitives', function() {
+    test('int', function() {
+      this.int();
+    }, 0);
+
+    test('enum', function() {
+      this.enum({ 0: 'hello', 1: 'world' });
+    }, 'world');
+
+    test('octstr', function() {
+      this.octstr();
+    }, new Buffer('hello'));
+
+    test('gentime', function() {
+      this.gentime();
+    }, 1385921175000);
+
+    test('null', function() {
+      this.null_();
+    }, null);
+
+    test('objid', function() {
+      this.objid({
+        '1 3 6 1 5 5 7 48 1 1': 'id-pkix-ocsp-basic'
+      });
+    }, 'id-pkix-ocsp-basic');
+
+    test('true', function() {
+      this.bool();
+    }, true);
+
+    test('false', function() {
+      this.bool();
+    }, false);
+
+    test('any', function() {
+      this.any();
+    }, new Buffer('ok any'));
+
+    test('default explicit', function() {
+      this.def('v1').explicit(0).int({
+        0: 'v1',
+        1: 'v2'
+      });
+    }, undefined, 'v1');
+
+    test('implicit', function() {
+      this.implicit(0).int({
+        0: 'v1',
+        1: 'v2'
+      });
+    }, 'v2', 'v2');
+  });
+
+  describe('composite', function() {
+    test('2x int', function() {
+      this.seq().obj(
+        this.key('hello').int(),
+        this.key('world').int()
+      );
+    }, { hello: 4, world: 2 });
+
+    test('enum', function() {
+      this.seq().obj(
+        this.key('hello').enum({ 0: 'world', 1: 'devs' })
+      );
+    }, { hello: 'devs' });
+
+    test('optionals', function() {
+      this.seq().obj(
+        this.key('hello').enum({ 0: 'world', 1: 'devs' }),
+        this.key('how').optional().def('are you').enum({
+          0: 'are you',
+          1: 'are we?!'
+        })
+      );
+    }, { hello: 'devs', how: 'are we?!' });
+
+    test('optionals #2', function() {
+      this.seq().obj(
+        this.key('hello').enum({ 0: 'world', 1: 'devs' }),
+        this.key('how').optional().def('are you').enum({
+          0: 'are you',
+          1: 'are we?!'
+        })
+      );
+    }, { hello: 'devs' }, { hello: 'devs', how: 'are you' });
+
+    test('setof', function() {
+      var S = asn1.define('S', function() {
+        this.seq().obj(
+          this.key('a').def('b').int({ 0: 'a', 1: 'b' }),
+          this.key('c').def('d').int({ 2: 'c', 3: 'd' })
+        );
+      });
+      this.seqof(S);
+    }, [{}, { a: 'a', c: 'c' }], [{ a: 'b', c: 'd' }, { a: 'a', c: 'c' }]);
+  });
+});
diff --git a/test/basic-test.js b/test/rfc2560-test.js
similarity index 90%
rename from test/basic-test.js
rename to test/rfc2560-test.js
index 5cf4029..9bea5c1 100644
--- a/test/basic-test.js
+++ b/test/rfc2560-test.js
@@ -3,7 +3,7 @@ var asn1 = require('..');
 
 var Buffer = require('buffer').Buffer;
 
-describe('asn1.js', function() {
+describe('asn1.js RFC2560', function() {
   it('should decode OCSP response', function() {
     var data = new Buffer(
       '308201d40a0100a08201cd308201c906092b0601050507300101048201ba308201b630' +
@@ -30,6 +30,7 @@ describe('asn1.js', function() {
       res.responseBytes.response,
       'der'
     );
-    console.log(require('util').inspect(basic, false, 300));
+    assert.equal(basic.tbsResponseData.version, 'v1');
+    assert.equal(basic.tbsResponseData.producedAt, 1385797510000);
   });
 });

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-asn1.js.git



More information about the Pkg-javascript-commits mailing list