Initial commit
[yaffs-website] / node_modules / asn1 / lib / ber / writer.js
1 // Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
2
3 var assert = require('assert');
4 var ASN1 = require('./types');
5 var errors = require('./errors');
6
7
8 ///--- Globals
9
10 var newInvalidAsn1Error = errors.newInvalidAsn1Error;
11
12 var DEFAULT_OPTS = {
13   size: 1024,
14   growthFactor: 8
15 };
16
17
18 ///--- Helpers
19
20 function merge(from, to) {
21   assert.ok(from);
22   assert.equal(typeof(from), 'object');
23   assert.ok(to);
24   assert.equal(typeof(to), 'object');
25
26   var keys = Object.getOwnPropertyNames(from);
27   keys.forEach(function(key) {
28     if (to[key])
29       return;
30
31     var value = Object.getOwnPropertyDescriptor(from, key);
32     Object.defineProperty(to, key, value);
33   });
34
35   return to;
36 }
37
38
39
40 ///--- API
41
42 function Writer(options) {
43   options = merge(DEFAULT_OPTS, options || {});
44
45   this._buf = new Buffer(options.size || 1024);
46   this._size = this._buf.length;
47   this._offset = 0;
48   this._options = options;
49
50   // A list of offsets in the buffer where we need to insert
51   // sequence tag/len pairs.
52   this._seq = [];
53 }
54
55 Object.defineProperty(Writer.prototype, 'buffer', {
56   get: function () {
57     if (this._seq.length)
58       throw new InvalidAsn1Error(this._seq.length + ' unended sequence(s)');
59
60     return (this._buf.slice(0, this._offset));
61   }
62 });
63
64 Writer.prototype.writeByte = function(b) {
65   if (typeof(b) !== 'number')
66     throw new TypeError('argument must be a Number');
67
68   this._ensure(1);
69   this._buf[this._offset++] = b;
70 };
71
72
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')
77     tag = ASN1.Integer;
78
79   var sz = 4;
80
81   while ((((i & 0xff800000) === 0) || ((i & 0xff800000) === 0xff800000 >> 0)) &&
82          (sz > 1)) {
83     sz--;
84     i <<= 8;
85   }
86
87   if (sz > 4)
88     throw new InvalidAsn1Error('BER ints cannot be > 0xffffffff');
89
90   this._ensure(2 + sz);
91   this._buf[this._offset++] = tag;
92   this._buf[this._offset++] = sz;
93
94   while (sz-- > 0) {
95     this._buf[this._offset++] = ((i & 0xff000000) >>> 24);
96     i <<= 8;
97   }
98
99 };
100
101
102 Writer.prototype.writeNull = function() {
103   this.writeByte(ASN1.Null);
104   this.writeByte(0x00);
105 };
106
107
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;
113
114   return this.writeInt(i, tag);
115 };
116
117
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')
122     tag = ASN1.Boolean;
123
124   this._ensure(3);
125   this._buf[this._offset++] = tag;
126   this._buf[this._offset++] = 0x01;
127   this._buf[this._offset++] = b ? 0xff : 0x00;
128 };
129
130
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;
136
137   var len = Buffer.byteLength(s);
138   this.writeByte(tag);
139   this.writeLength(len);
140   if (len) {
141     this._ensure(len);
142     this._buf.write(s, this._offset);
143     this._offset += len;
144   }
145 };
146
147
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');
153
154   this.writeByte(tag);
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;
159 };
160
161
162 Writer.prototype.writeStringArray = function(strings) {
163   if ((!strings instanceof Array))
164     throw new TypeError('argument must be an Array[String]');
165
166   var self = this;
167   strings.forEach(function(s) {
168     self.writeString(s);
169   });
170 };
171
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')
177     tag = ASN1.OID;
178
179   if (!/^([0-9]+\.){3,}[0-9]+$/.test(s))
180     throw new Error('argument is not a valid OID string');
181
182   function encodeOctet(bytes, octet) {
183     if (octet < 128) {
184         bytes.push(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);
197     } else {
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);
203     }
204   }
205
206   var tmp = s.split('.');
207   var bytes = [];
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));
211   });
212
213   var self = this;
214   this._ensure(2 + bytes.length);
215   this.writeByte(tag);
216   this.writeLength(bytes.length);
217   bytes.forEach(function(b) {
218     self.writeByte(b);
219   });
220 };
221
222
223 Writer.prototype.writeLength = function(len) {
224   if (typeof(len) !== 'number')
225     throw new TypeError('argument must be a Number');
226
227   this._ensure(4);
228
229   if (len <= 0x7f) {
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;
243   } else {
244     throw new InvalidAsn1ERror('Length too long (> 4 bytes)');
245   }
246 };
247
248 Writer.prototype.startSequence = function(tag) {
249   if (typeof(tag) !== 'number')
250     tag = ASN1.Sequence | ASN1.Constructor;
251
252   this.writeByte(tag);
253   this._seq.push(this._offset);
254   this._ensure(3);
255   this._offset += 3;
256 };
257
258
259 Writer.prototype.endSequence = function() {
260   var seq = this._seq.pop();
261   var start = seq + 3;
262   var len = this._offset - start;
263
264   if (len <= 0x7f) {
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;
281   } else {
282     throw new InvalidAsn1Error('Sequence too long');
283   }
284 };
285
286
287 Writer.prototype._shift = function(start, len, shift) {
288   assert.ok(start !== undefined);
289   assert.ok(len !== undefined);
290   assert.ok(shift);
291
292   this._buf.copy(this._buf, start + shift, start, start + len);
293   this._offset += shift;
294 };
295
296 Writer.prototype._ensure = function(len) {
297   assert.ok(len);
298
299   if (this._size - this._offset < len) {
300     var sz = this._size * this._options.growthFactor;
301     if (sz - this._offset < len)
302       sz += len;
303
304     var buf = new Buffer(sz);
305
306     this._buf.copy(buf, 0, 0, this._offset);
307     this._buf = buf;
308     this._size = sz;
309   }
310 };
311
312
313
314 ///--- Exported API
315
316 module.exports = Writer;