text: var crypto = require('crypto')
    , qs = require('querystring')
    ;
  
  function sha1 (key, body) {
    return crypto.createHmac('sha1', key).update(body).digest('base64')
  }
  
  function rfc3986 (str) {
    return encodeURIComponent(str)
      .replace(/!/g,'%21')
      .replace(/\*/g,'%2A')
      .replace(/\(/g,'%28')
      .replace(/\)/g,'%29')
      .replace(/'/g,'%27')
      ;
  }
  
  // Maps object to bi-dimensional array
  // Converts { foo: 'A', bar: [ 'b', 'B' ]} to
  // [ ['foo', 'A'], ['bar', 'b'], ['bar', 'B'] ]
  function map (obj) {
    var key, val, arr = []
    for (key in obj) {
      val = obj[key]
      if (Array.isArray(val))
        for (var i = 0; i < val.length; i++)
          arr.push([key, val[i]])
      else
        arr.push([key, val])
    }
    return arr
  }
  
  // Compare function for sort
  function compare (a, b) {
    return a > b ? 1 : a < b ? -1 : 0
  }
  
  function hmacsign (httpMethod, base_uri, params, consumer_secret, token_secret) {
    // adapted from https://dev.twitter.com/docs/auth/oauth and 
    // https://dev.twitter.com/docs/auth/creating-signature
  
    // Parameter normalization
    // http://tools.ietf.org/html/rfc5849#section-3.4.1.3.2
    var normalized = map(params)
    // 1.  First, the name and value of each parameter are encoded
    .map(function (p) {
      return [ rfc3986(p[0]), rfc3986(p[1] || '') ]
    })
    // 2.  The parameters are sorted by name, using ascending byte value
    //     ordering.  If two or more parameters share the same name, they
    //     are sorted by their value.
    .sort(function (a, b) {
      return compare(a[0], b[0]) || compare(a[1], b[1])
    })
    // 3.  The name of each parameter is concatenated to its corresponding
    //     value using an "=" character (ASCII code 61) as a separator, even
    //     if the value is empty.
    .map(function (p) { return p.join('=') })
     // 4.  The sorted name/value pairs are concatenated together into a
     //     single string by using an "&" character (ASCII code 38) as
     //     separator.
    .join('&')
  
    var base = [
      rfc3986(httpMethod ? httpMethod.toUpperCase() : 'GET'),
      rfc3986(base_uri),
      rfc3986(normalized)
    ].join('&')
  
    var key = [
      consumer_secret || '',
      token_secret || ''
    ].map(rfc3986).join('&')
  
    return sha1(key, base)
  }
  
  exports.hmacsign = hmacsign
  exports.rfc3986 = rfc3986
  