1 // Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
3 var assert = require('assert');
4 var ASN1 = require('./types');
5 var errors = require('./errors');
10 var newInvalidAsn1Error = errors.newInvalidAsn1Error;
20 function merge(from, to) {
22 assert.equal(typeof(from), 'object');
24 assert.equal(typeof(to), 'object');
26 var keys = Object.getOwnPropertyNames(from);
27 keys.forEach(function(key) {
31 var value = Object.getOwnPropertyDescriptor(from, key);
32 Object.defineProperty(to, key, value);
42 function Writer(options) {
43 options = merge(DEFAULT_OPTS, options || {});
45 this._buf = new Buffer(options.size || 1024);
46 this._size = this._buf.length;
48 this._options = options;
50 // A list of offsets in the buffer where we need to insert
51 // sequence tag/len pairs.
55 Object.defineProperty(Writer.prototype, 'buffer', {
58 throw new InvalidAsn1Error(this._seq.length + ' unended sequence(s)');
60 return (this._buf.slice(0, this._offset));
64 Writer.prototype.writeByte = function(b) {
65 if (typeof(b) !== 'number')
66 throw new TypeError('argument must be a Number');
69 this._buf[this._offset++] = b;
73 Writer.prototype.writeInt = function(i, tag) {
74 if (typeof(i) !== 'number')
75 throw new TypeError('argument must be a Number');
76 if (typeof(tag) !== 'number')
81 while ((((i & 0xff800000) === 0) || ((i & 0xff800000) === 0xff800000 >> 0)) &&
88 throw new InvalidAsn1Error('BER ints cannot be > 0xffffffff');
91 this._buf[this._offset++] = tag;
92 this._buf[this._offset++] = sz;
95 this._buf[this._offset++] = ((i & 0xff000000) >>> 24);
102 Writer.prototype.writeNull = function() {
103 this.writeByte(ASN1.Null);
104 this.writeByte(0x00);
108 Writer.prototype.writeEnumeration = function(i, tag) {
109 if (typeof(i) !== 'number')
110 throw new TypeError('argument must be a Number');
111 if (typeof(tag) !== 'number')
112 tag = ASN1.Enumeration;
114 return this.writeInt(i, tag);
118 Writer.prototype.writeBoolean = function(b, tag) {
119 if (typeof(b) !== 'boolean')
120 throw new TypeError('argument must be a Boolean');
121 if (typeof(tag) !== 'number')
125 this._buf[this._offset++] = tag;
126 this._buf[this._offset++] = 0x01;
127 this._buf[this._offset++] = b ? 0xff : 0x00;
131 Writer.prototype.writeString = function(s, tag) {
132 if (typeof(s) !== 'string')
133 throw new TypeError('argument must be a string (was: ' + typeof(s) + ')');
134 if (typeof(tag) !== 'number')
135 tag = ASN1.OctetString;
137 var len = Buffer.byteLength(s);
139 this.writeLength(len);
142 this._buf.write(s, this._offset);
148 Writer.prototype.writeBuffer = function(buf, tag) {
149 if (typeof(tag) !== 'number')
150 throw new TypeError('tag must be a number');
151 if (!Buffer.isBuffer(buf))
152 throw new TypeError('argument must be a buffer');
155 this.writeLength(buf.length);
156 this._ensure(buf.length);
157 buf.copy(this._buf, this._offset, 0, buf.length);
158 this._offset += buf.length;
162 Writer.prototype.writeStringArray = function(strings) {
163 if ((!strings instanceof Array))
164 throw new TypeError('argument must be an Array[String]');
167 strings.forEach(function(s) {
172 // This is really to solve DER cases, but whatever for now
173 Writer.prototype.writeOID = function(s, tag) {
174 if (typeof(s) !== 'string')
175 throw new TypeError('argument must be a string');
176 if (typeof(tag) !== 'number')
179 if (!/^([0-9]+\.){3,}[0-9]+$/.test(s))
180 throw new Error('argument is not a valid OID string');
182 function encodeOctet(bytes, octet) {
185 } else if (octet < 16384) {
186 bytes.push((octet >>> 7) | 0x80);
187 bytes.push(octet & 0x7F);
188 } else if (octet < 2097152) {
189 bytes.push((octet >>> 14) | 0x80);
190 bytes.push(((octet >>> 7) | 0x80) & 0xFF);
191 bytes.push(octet & 0x7F);
192 } else if (octet < 268435456) {
193 bytes.push((octet >>> 21) | 0x80);
194 bytes.push(((octet >>> 14) | 0x80) & 0xFF);
195 bytes.push(((octet >>> 7) | 0x80) & 0xFF);
196 bytes.push(octet & 0x7F);
198 bytes.push(((octet >>> 28) | 0x80) & 0xFF);
199 bytes.push(((octet >>> 21) | 0x80) & 0xFF);
200 bytes.push(((octet >>> 14) | 0x80) & 0xFF);
201 bytes.push(((octet >>> 7) | 0x80) & 0xFF);
202 bytes.push(octet & 0x7F);
206 var tmp = s.split('.');
208 bytes.push(parseInt(tmp[0], 10) * 40 + parseInt(tmp[1], 10));
209 tmp.slice(2).forEach(function(b) {
210 encodeOctet(bytes, parseInt(b, 10));
214 this._ensure(2 + bytes.length);
216 this.writeLength(bytes.length);
217 bytes.forEach(function(b) {
223 Writer.prototype.writeLength = function(len) {
224 if (typeof(len) !== 'number')
225 throw new TypeError('argument must be a Number');
230 this._buf[this._offset++] = len;
231 } else if (len <= 0xff) {
232 this._buf[this._offset++] = 0x81;
233 this._buf[this._offset++] = len;
234 } else if (len <= 0xffff) {
235 this._buf[this._offset++] = 0x82;
236 this._buf[this._offset++] = len >> 8;
237 this._buf[this._offset++] = len;
238 } else if (len <= 0xffffff) {
239 this._buf[this._offset++] = 0x83;
240 this._buf[this._offset++] = len >> 16;
241 this._buf[this._offset++] = len >> 8;
242 this._buf[this._offset++] = len;
244 throw new InvalidAsn1ERror('Length too long (> 4 bytes)');
248 Writer.prototype.startSequence = function(tag) {
249 if (typeof(tag) !== 'number')
250 tag = ASN1.Sequence | ASN1.Constructor;
253 this._seq.push(this._offset);
259 Writer.prototype.endSequence = function() {
260 var seq = this._seq.pop();
262 var len = this._offset - start;
265 this._shift(start, len, -2);
266 this._buf[seq] = len;
267 } else if (len <= 0xff) {
268 this._shift(start, len, -1);
269 this._buf[seq] = 0x81;
270 this._buf[seq + 1] = len;
271 } else if (len <= 0xffff) {
272 this._buf[seq] = 0x82;
273 this._buf[seq + 1] = len >> 8;
274 this._buf[seq + 2] = len;
275 } else if (len <= 0xffffff) {
276 this._shift(start, len, 1);
277 this._buf[seq] = 0x83;
278 this._buf[seq + 1] = len >> 16;
279 this._buf[seq + 2] = len >> 8;
280 this._buf[seq + 3] = len;
282 throw new InvalidAsn1Error('Sequence too long');
287 Writer.prototype._shift = function(start, len, shift) {
288 assert.ok(start !== undefined);
289 assert.ok(len !== undefined);
292 this._buf.copy(this._buf, start + shift, start, start + len);
293 this._offset += shift;
296 Writer.prototype._ensure = function(len) {
299 if (this._size - this._offset < len) {
300 var sz = this._size * this._options.growthFactor;
301 if (sz - this._offset < len)
304 var buf = new Buffer(sz);
306 this._buf.copy(buf, 0, 0, this._offset);
316 module.exports = Writer;