twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

writer.js (7666B)


      1 // Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
      2 
      3 var assert = require('assert');
      4 var Buffer = require('safer-buffer').Buffer;
      5 var ASN1 = require('./types');
      6 var errors = require('./errors');
      7 
      8 
      9 // --- Globals
     10 
     11 var newInvalidAsn1Error = errors.newInvalidAsn1Error;
     12 
     13 var DEFAULT_OPTS = {
     14   size: 1024,
     15   growthFactor: 8
     16 };
     17 
     18 
     19 // --- Helpers
     20 
     21 function merge(from, to) {
     22   assert.ok(from);
     23   assert.equal(typeof (from), 'object');
     24   assert.ok(to);
     25   assert.equal(typeof (to), 'object');
     26 
     27   var keys = Object.getOwnPropertyNames(from);
     28   keys.forEach(function (key) {
     29     if (to[key])
     30       return;
     31 
     32     var value = Object.getOwnPropertyDescriptor(from, key);
     33     Object.defineProperty(to, key, value);
     34   });
     35 
     36   return to;
     37 }
     38 
     39 
     40 
     41 // --- API
     42 
     43 function Writer(options) {
     44   options = merge(DEFAULT_OPTS, options || {});
     45 
     46   this._buf = Buffer.alloc(options.size || 1024);
     47   this._size = this._buf.length;
     48   this._offset = 0;
     49   this._options = options;
     50 
     51   // A list of offsets in the buffer where we need to insert
     52   // sequence tag/len pairs.
     53   this._seq = [];
     54 }
     55 
     56 Object.defineProperty(Writer.prototype, 'buffer', {
     57   get: function () {
     58     if (this._seq.length)
     59       throw newInvalidAsn1Error(this._seq.length + ' unended sequence(s)');
     60 
     61     return (this._buf.slice(0, this._offset));
     62   }
     63 });
     64 
     65 Writer.prototype.writeByte = function (b) {
     66   if (typeof (b) !== 'number')
     67     throw new TypeError('argument must be a Number');
     68 
     69   this._ensure(1);
     70   this._buf[this._offset++] = b;
     71 };
     72 
     73 
     74 Writer.prototype.writeInt = function (i, tag) {
     75   if (typeof (i) !== 'number')
     76     throw new TypeError('argument must be a Number');
     77   if (typeof (tag) !== 'number')
     78     tag = ASN1.Integer;
     79 
     80   var sz = 4;
     81 
     82   while ((((i & 0xff800000) === 0) || ((i & 0xff800000) === 0xff800000 >> 0)) &&
     83         (sz > 1)) {
     84     sz--;
     85     i <<= 8;
     86   }
     87 
     88   if (sz > 4)
     89     throw newInvalidAsn1Error('BER ints cannot be > 0xffffffff');
     90 
     91   this._ensure(2 + sz);
     92   this._buf[this._offset++] = tag;
     93   this._buf[this._offset++] = sz;
     94 
     95   while (sz-- > 0) {
     96     this._buf[this._offset++] = ((i & 0xff000000) >>> 24);
     97     i <<= 8;
     98   }
     99 
    100 };
    101 
    102 
    103 Writer.prototype.writeNull = function () {
    104   this.writeByte(ASN1.Null);
    105   this.writeByte(0x00);
    106 };
    107 
    108 
    109 Writer.prototype.writeEnumeration = function (i, tag) {
    110   if (typeof (i) !== 'number')
    111     throw new TypeError('argument must be a Number');
    112   if (typeof (tag) !== 'number')
    113     tag = ASN1.Enumeration;
    114 
    115   return this.writeInt(i, tag);
    116 };
    117 
    118 
    119 Writer.prototype.writeBoolean = function (b, tag) {
    120   if (typeof (b) !== 'boolean')
    121     throw new TypeError('argument must be a Boolean');
    122   if (typeof (tag) !== 'number')
    123     tag = ASN1.Boolean;
    124 
    125   this._ensure(3);
    126   this._buf[this._offset++] = tag;
    127   this._buf[this._offset++] = 0x01;
    128   this._buf[this._offset++] = b ? 0xff : 0x00;
    129 };
    130 
    131 
    132 Writer.prototype.writeString = function (s, tag) {
    133   if (typeof (s) !== 'string')
    134     throw new TypeError('argument must be a string (was: ' + typeof (s) + ')');
    135   if (typeof (tag) !== 'number')
    136     tag = ASN1.OctetString;
    137 
    138   var len = Buffer.byteLength(s);
    139   this.writeByte(tag);
    140   this.writeLength(len);
    141   if (len) {
    142     this._ensure(len);
    143     this._buf.write(s, this._offset);
    144     this._offset += len;
    145   }
    146 };
    147 
    148 
    149 Writer.prototype.writeBuffer = function (buf, tag) {
    150   if (typeof (tag) !== 'number')
    151     throw new TypeError('tag must be a number');
    152   if (!Buffer.isBuffer(buf))
    153     throw new TypeError('argument must be a buffer');
    154 
    155   this.writeByte(tag);
    156   this.writeLength(buf.length);
    157   this._ensure(buf.length);
    158   buf.copy(this._buf, this._offset, 0, buf.length);
    159   this._offset += buf.length;
    160 };
    161 
    162 
    163 Writer.prototype.writeStringArray = function (strings) {
    164   if ((!strings instanceof Array))
    165     throw new TypeError('argument must be an Array[String]');
    166 
    167   var self = this;
    168   strings.forEach(function (s) {
    169     self.writeString(s);
    170   });
    171 };
    172 
    173 // This is really to solve DER cases, but whatever for now
    174 Writer.prototype.writeOID = function (s, tag) {
    175   if (typeof (s) !== 'string')
    176     throw new TypeError('argument must be a string');
    177   if (typeof (tag) !== 'number')
    178     tag = ASN1.OID;
    179 
    180   if (!/^([0-9]+\.){3,}[0-9]+$/.test(s))
    181     throw new Error('argument is not a valid OID string');
    182 
    183   function encodeOctet(bytes, octet) {
    184     if (octet < 128) {
    185         bytes.push(octet);
    186     } else if (octet < 16384) {
    187         bytes.push((octet >>> 7) | 0x80);
    188         bytes.push(octet & 0x7F);
    189     } else if (octet < 2097152) {
    190       bytes.push((octet >>> 14) | 0x80);
    191       bytes.push(((octet >>> 7) | 0x80) & 0xFF);
    192       bytes.push(octet & 0x7F);
    193     } else if (octet < 268435456) {
    194       bytes.push((octet >>> 21) | 0x80);
    195       bytes.push(((octet >>> 14) | 0x80) & 0xFF);
    196       bytes.push(((octet >>> 7) | 0x80) & 0xFF);
    197       bytes.push(octet & 0x7F);
    198     } else {
    199       bytes.push(((octet >>> 28) | 0x80) & 0xFF);
    200       bytes.push(((octet >>> 21) | 0x80) & 0xFF);
    201       bytes.push(((octet >>> 14) | 0x80) & 0xFF);
    202       bytes.push(((octet >>> 7) | 0x80) & 0xFF);
    203       bytes.push(octet & 0x7F);
    204     }
    205   }
    206 
    207   var tmp = s.split('.');
    208   var bytes = [];
    209   bytes.push(parseInt(tmp[0], 10) * 40 + parseInt(tmp[1], 10));
    210   tmp.slice(2).forEach(function (b) {
    211     encodeOctet(bytes, parseInt(b, 10));
    212   });
    213 
    214   var self = this;
    215   this._ensure(2 + bytes.length);
    216   this.writeByte(tag);
    217   this.writeLength(bytes.length);
    218   bytes.forEach(function (b) {
    219     self.writeByte(b);
    220   });
    221 };
    222 
    223 
    224 Writer.prototype.writeLength = function (len) {
    225   if (typeof (len) !== 'number')
    226     throw new TypeError('argument must be a Number');
    227 
    228   this._ensure(4);
    229 
    230   if (len <= 0x7f) {
    231     this._buf[this._offset++] = len;
    232   } else if (len <= 0xff) {
    233     this._buf[this._offset++] = 0x81;
    234     this._buf[this._offset++] = len;
    235   } else if (len <= 0xffff) {
    236     this._buf[this._offset++] = 0x82;
    237     this._buf[this._offset++] = len >> 8;
    238     this._buf[this._offset++] = len;
    239   } else if (len <= 0xffffff) {
    240     this._buf[this._offset++] = 0x83;
    241     this._buf[this._offset++] = len >> 16;
    242     this._buf[this._offset++] = len >> 8;
    243     this._buf[this._offset++] = len;
    244   } else {
    245     throw newInvalidAsn1Error('Length too long (> 4 bytes)');
    246   }
    247 };
    248 
    249 Writer.prototype.startSequence = function (tag) {
    250   if (typeof (tag) !== 'number')
    251     tag = ASN1.Sequence | ASN1.Constructor;
    252 
    253   this.writeByte(tag);
    254   this._seq.push(this._offset);
    255   this._ensure(3);
    256   this._offset += 3;
    257 };
    258 
    259 
    260 Writer.prototype.endSequence = function () {
    261   var seq = this._seq.pop();
    262   var start = seq + 3;
    263   var len = this._offset - start;
    264 
    265   if (len <= 0x7f) {
    266     this._shift(start, len, -2);
    267     this._buf[seq] = len;
    268   } else if (len <= 0xff) {
    269     this._shift(start, len, -1);
    270     this._buf[seq] = 0x81;
    271     this._buf[seq + 1] = len;
    272   } else if (len <= 0xffff) {
    273     this._buf[seq] = 0x82;
    274     this._buf[seq + 1] = len >> 8;
    275     this._buf[seq + 2] = len;
    276   } else if (len <= 0xffffff) {
    277     this._shift(start, len, 1);
    278     this._buf[seq] = 0x83;
    279     this._buf[seq + 1] = len >> 16;
    280     this._buf[seq + 2] = len >> 8;
    281     this._buf[seq + 3] = len;
    282   } else {
    283     throw newInvalidAsn1Error('Sequence too long');
    284   }
    285 };
    286 
    287 
    288 Writer.prototype._shift = function (start, len, shift) {
    289   assert.ok(start !== undefined);
    290   assert.ok(len !== undefined);
    291   assert.ok(shift);
    292 
    293   this._buf.copy(this._buf, start + shift, start, start + len);
    294   this._offset += shift;
    295 };
    296 
    297 Writer.prototype._ensure = function (len) {
    298   assert.ok(len);
    299 
    300   if (this._size - this._offset < len) {
    301     var sz = this._size * this._options.growthFactor;
    302     if (sz - this._offset < len)
    303       sz += len;
    304 
    305     var buf = Buffer.alloc(sz);
    306 
    307     this._buf.copy(buf, 0, 0, this._offset);
    308     this._buf = buf;
    309     this._size = sz;
    310   }
    311 };
    312 
    313 
    314 
    315 // --- Exported API
    316 
    317 module.exports = Writer;