From c5ccafd436ac8d9b51e9be0b9baf2a8e997c6df0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonathan=20S=C3=BCssemilch=20Poulain?=
 <jonathan@sofiero.net>
Date: Fri, 16 May 2014 17:10:24 +0200
Subject: [PATCH 1/6] Server Side Sorting control

---
 lib/controls/index.js                       |  10 +-
 lib/controls/server_side_sorting_control.js | 117 ++++++++++++++++++++
 2 files changed, 126 insertions(+), 1 deletion(-)
 create mode 100644 lib/controls/server_side_sorting_control.js

diff --git a/lib/controls/index.js b/lib/controls/index.js
index d2d7f23..4157d5f 100644
--- a/lib/controls/index.js
+++ b/lib/controls/index.js
@@ -7,6 +7,7 @@ var EntryChangeNotificationControl =
   require('./entry_change_notification_control');
 var PersistentSearchControl = require('./persistent_search_control');
 var PagedResultsControl = require('./paged_results_control');
+var ServerSideSortingControl = require('./server_side_sorting_control.js');
 
 
 
@@ -57,6 +58,12 @@ module.exports = {
         value: value
       });
       break;
+    case ServerSideSortingControl.OID:
+      control = new ServerSideSortingControl({
+        critical: critical,
+        value: value
+      });
+      break;
     default:
       control = new Control({
         type: type,
@@ -72,5 +79,6 @@ module.exports = {
   Control: Control,
   EntryChangeNotificationControl: EntryChangeNotificationControl,
   PagedResultsControl: PagedResultsControl,
-  PersistentSearchControl: PersistentSearchControl
+  PersistentSearchControl: PersistentSearchControl,
+  ServerSideSortingControl: ServerSideSortingControl
 };
diff --git a/lib/controls/server_side_sorting_control.js b/lib/controls/server_side_sorting_control.js
new file mode 100644
index 0000000..995a369
--- /dev/null
+++ b/lib/controls/server_side_sorting_control.js
@@ -0,0 +1,117 @@
+var assert = require('assert');
+var util = require('util');
+
+var asn1 = require('asn1');
+
+var Control = require('./control');
+
+
+
+///--- Globals
+
+var BerReader = asn1.BerReader;
+var BerWriter = asn1.BerWriter;
+
+
+
+///--- API
+
+function ServerSideSortingControl(options) {
+  if (!options)
+    options = {};
+
+  options.type = ServerSideSortingControl.OID;
+  if (options.value) {
+    if (Buffer.isBuffer(options.value)) {
+      this.parse(options.value);
+    } else if (Array.isArray(options.value)) {
+      for (var i = 0; i < options.value.length; i++) {
+        if (!typeof (options.value[i]) === 'object') {
+          throw new TypeError('Elements of options.value must be Objects');
+        } else if (!options.value[i].hasOwnProperty('attributeType')) {
+          throw new Error('Missing required key: attributeType');
+        }
+      }
+      this._value = options.value;
+    } else if (typeof (options.value) === 'object') {
+      if (!options.value.hasOwnProperty('attributeType')) {
+        throw new Error('Missing required key: attributeType');
+      }
+      this._value = options.value;
+    } else {
+      throw new TypeError('options.value must be a Buffer, Array or Object');
+    }
+    options.value = null;
+  }
+  Control.call(this, options);
+
+  var self = this;
+  this.__defineGetter__('value', function () {
+    return self._value || {};
+  });
+}
+util.inherits(ServerSideSortingControl, Control);
+module.exports = ServerSideSortingControl;
+
+
+ServerSideSortingControl.prototype.parse = function parse(buffer) {
+  assert.ok(buffer);
+
+  var ber = new BerReader(buffer);
+  if (ber.readSequence()) {
+    this._value = {};
+    this._value.sortResult = ber.readInt();
+    this._value.attributeType = ber.readString(asn1.Ber.OctetString, true);
+     //readString returns '' instead of a zero-length buffer
+    if (!this._value.attributeType)
+      this._value.attributeType = new Buffer(0);
+
+    return true;
+  }
+
+  return false;
+};
+
+
+ServerSideSortingControl.prototype._toBer = function (ber) {
+  assert.ok(ber);
+
+  if (!this._value)
+    return;
+
+  var writer = new BerWriter();
+  writer.startSequence(0x30);
+
+  if (Array.isArray(this.value)) {
+    for (var i = 0; i < this.value.length; i++) {
+      this._sortKeyListItemToBer(writer, this.value[i]);
+    }
+  } else if (typeof (this.value) === 'object') {
+    this._sortKeyListItemToBer(writer, this.value);
+  }
+
+  writer.endSequence();
+  ber.writeBuffer(writer.buffer, 0x04);
+};
+
+
+ServerSideSortingControl.prototype._json = function (obj) {
+  obj.controlValue = this.value;
+  return obj;
+};
+
+ServerSideSortingControl.prototype._sortKeyListItemToBer = function(writer, obj) {
+  writer.startSequence(0x30);
+  if (obj.attributeType) {
+    writer.writeString(obj.attributeType, asn1.Ber.OctetString);
+  }
+  if (obj.orderingRule) {
+    writer.writeString(obj.orderingRule, 0x80);
+  }
+  if (obj.reverseOrder) {
+    writer.writeBoolean(obj.reverseOrder, 0x81);
+  }
+  writer.endSequence();
+};
+
+ServerSideSortingControl.OID = '1.2.840.113556.1.4.473';

From c1af9a8814a42bbee59bcd547ebc2fa1d53690b8 Mon Sep 17 00:00:00 2001
From: = <jonathan@sofiero.net>
Date: Mon, 26 May 2014 21:12:14 +0000
Subject: [PATCH 2/6] Server side sorting control tests

---
 .../server_side_sorting_control_test.js       | 82 +++++++++++++++++++
 1 file changed, 82 insertions(+)
 create mode 100644 test/controls/server_side_sorting_control_test.js

diff --git a/test/controls/server_side_sorting_control_test.js b/test/controls/server_side_sorting_control_test.js
new file mode 100644
index 0000000..9cca748
--- /dev/null
+++ b/test/controls/server_side_sorting_control_test.js
@@ -0,0 +1,82 @@
+
+var test = require('tap').test;
+
+var asn1 = require('asn1');
+
+var BerReader = asn1.BerReader;
+var BerWriter = asn1.BerWriter;
+var getControl;
+var ServerSideSortingControl;
+
+function bufferEqual(t, a, b) {
+  t.equal(a.toString('hex'), b.toString('hex'));
+}
+
+
+///--- Tests
+
+
+test('load library', function (t) {
+  ServerSideSortingControl =
+    require('../../lib').ServerSideSortingControl;
+  t.ok(ServerSideSortingControl);
+  getControl = require('../../lib').getControl;
+  t.ok(getControl);
+  t.end();
+});
+
+
+test('new no args', function (t) {
+  t.ok(new ServerSideSortingControl());
+  t.end();
+});
+
+
+test('new with args', function (t) {
+  var c = new ServerSideSortingControl({
+    type: '1.2.840.113556.1.4.473',
+    criticality: true,
+    value: {
+      attributeType: 'sn'
+    }
+  });
+  t.ok(c);
+  t.equal(c.type, '1.2.840.113556.1.4.473');
+  t.ok(c.criticality);
+  t.equal(c.value.attributeType, 'sn');
+
+  var writer = new BerWriter();
+  c.toBer(writer);
+  var reader = new BerReader(writer.buffer);
+  var sssc = getControl(reader);
+  t.ok(sssc);
+  console.log('sssc', sssc.value);
+  t.equal(sssc.type, '1.2.840.113556.1.4.473');
+  t.ok(sssc.criticality);
+  t.equal(sssc.value.attributeType, 'sn');
+  bufferEqual(t, sssc.value.cookie, new Buffer(['sn']));
+
+  t.end();
+});
+
+test('tober', function (t) {
+  var sssc = new ServerSideSortingControl({
+    type: '1.2.840.113556.1.4.473',
+    criticality: true,
+    value: {
+      attributeType: 'sn'
+    }
+  });
+
+  var ber = new BerWriter();
+  sssc.toBer(ber);
+
+  var c = getControl(new BerReader(ber.buffer));
+  t.ok(c);
+  t.equal(c.type, '1.2.840.113556.1.4.473');
+  t.ok(c.criticality);
+  t.equal(c.value.attributeType, 'sn');
+  bufferEqual(t, c.value.cookie, new Buffer(0));
+
+  t.end();
+});

From b742e286dbc87f6c78fa5ee6d8b7b2c70bb6eb15 Mon Sep 17 00:00:00 2001
From: = <jonathan@sofiero.net>
Date: Fri, 30 May 2014 20:57:06 +0000
Subject: [PATCH 3/6] Fixed parse method and added some tests

---
 lib/controls/server_side_sorting_control.js   | 34 ++++++++---
 .../server_side_sorting_control_test.js       | 59 ++++++++++++-------
 2 files changed, 65 insertions(+), 28 deletions(-)

diff --git a/lib/controls/server_side_sorting_control.js b/lib/controls/server_side_sorting_control.js
index 995a369..0f6308a 100644
--- a/lib/controls/server_side_sorting_control.js
+++ b/lib/controls/server_side_sorting_control.js
@@ -58,13 +58,18 @@ ServerSideSortingControl.prototype.parse = function parse(buffer) {
   assert.ok(buffer);
 
   var ber = new BerReader(buffer);
-  if (ber.readSequence()) {
-    this._value = {};
-    this._value.sortResult = ber.readInt();
-    this._value.attributeType = ber.readString(asn1.Ber.OctetString, true);
-     //readString returns '' instead of a zero-length buffer
-    if (!this._value.attributeType)
-      this._value.attributeType = new Buffer(0);
+
+  if (ber.readSequence(0x30)) {
+    this._value = [];
+
+    while (ber.readSequence(0x30)) {
+      var sortKeyListItem = this._parseSortKeyListItem(ber)
+      this._value.push(sortKeyListItem);
+    }
+
+    if (this._value.length == 1) {
+      this._value = this._value[0];
+    }
 
     return true;
   }
@@ -114,4 +119,19 @@ ServerSideSortingControl.prototype._sortKeyListItemToBer = function(writer, obj)
   writer.endSequence();
 };
 
+ServerSideSortingControl.prototype._parseSortKeyListItem = function(reader) {
+  var sortKeyListItem = {};
+  sortKeyListItem.attributeType = reader.readString(asn1.Ber.OctetString);
+
+  if (reader.peek() == 0x80) {
+    sortKeyListItem.orderingRule = reader.readString(0x80);
+  }
+
+  if (reader.peek() == 0x81) {
+    sortKeyListItem.reverseOrder = (reader._readTag(0x81) === 0 ? false : true);
+  }
+
+  return sortKeyListItem;
+};
+
 ServerSideSortingControl.OID = '1.2.840.113556.1.4.473';
diff --git a/test/controls/server_side_sorting_control_test.js b/test/controls/server_side_sorting_control_test.js
index 9cca748..dc28a75 100644
--- a/test/controls/server_side_sorting_control_test.js
+++ b/test/controls/server_side_sorting_control_test.js
@@ -8,11 +8,6 @@ var BerWriter = asn1.BerWriter;
 var getControl;
 var ServerSideSortingControl;
 
-function bufferEqual(t, a, b) {
-  t.equal(a.toString('hex'), b.toString('hex'));
-}
-
-
 ///--- Tests
 
 
@@ -45,28 +40,18 @@ test('new with args', function (t) {
   t.ok(c.criticality);
   t.equal(c.value.attributeType, 'sn');
 
-  var writer = new BerWriter();
-  c.toBer(writer);
-  var reader = new BerReader(writer.buffer);
-  var sssc = getControl(reader);
-  t.ok(sssc);
-  console.log('sssc', sssc.value);
-  t.equal(sssc.type, '1.2.840.113556.1.4.473');
-  t.ok(sssc.criticality);
-  t.equal(sssc.value.attributeType, 'sn');
-  bufferEqual(t, sssc.value.cookie, new Buffer(['sn']));
-
   t.end();
 });
 
-test('tober', function (t) {
+test('tober - object', function (t) {
   var sssc = new ServerSideSortingControl({
     type: '1.2.840.113556.1.4.473',
     criticality: true,
     value: {
-      attributeType: 'sn'
-    }
-  });
+      attributeType: 'sn',
+      orderingRule: 'caseIgnoreOrderingMatch',
+      reverseOrder: true
+    }});
 
   var ber = new BerWriter();
   sssc.toBer(ber);
@@ -76,7 +61,39 @@ test('tober', function (t) {
   t.equal(c.type, '1.2.840.113556.1.4.473');
   t.ok(c.criticality);
   t.equal(c.value.attributeType, 'sn');
-  bufferEqual(t, c.value.cookie, new Buffer(0));
+  t.equal(c.value.orderingRule, 'caseIgnoreOrderingMatch');
+  t.equal(c.value.reverseOrder, true);
+
+  t.end();
+});
+
+test('tober - array', function (t) {
+  var sssc = new ServerSideSortingControl({
+    type: '1.2.840.113556.1.4.473',
+    criticality: true,
+    value: [{
+      attributeType: 'sn',
+      orderingRule: 'caseIgnoreOrderingMatch',
+      reverseOrder: true
+    },
+    {
+      attributeType: 'givenName',
+      orderingRule: 'caseIgnoreOrderingMatch'
+      }]
+  });
+
+  var ber = new BerWriter();
+  sssc.toBer(ber);
+
+  var c = getControl(new BerReader(ber.buffer));
+  t.ok(c);
+  t.equal(c.type, '1.2.840.113556.1.4.473');
+  t.ok(c.criticality);
+  t.equal(c.value[0].attributeType, 'sn');
+  t.equal(c.value[0].orderingRule, 'caseIgnoreOrderingMatch');
+  t.equal(c.value[0].reverseOrder, true);
+  t.equal(c.value[1].attributeType, 'givenName');
+  t.equal(c.value[1].orderingRule, 'caseIgnoreOrderingMatch');
 
   t.end();
 });

From f1d4b667c3ba9cf5455892c5d78f5522faa87a06 Mon Sep 17 00:00:00 2001
From: Patrick Mooney <patrick.f.mooney@gmail.com>
Date: Fri, 6 Jun 2014 17:23:27 -0500
Subject: [PATCH 4/6] Refactor ServerSideSortingControl

- Fix lint errors
- Always store SSSC sort fields as array
- Rename test file to match 'make test' pattern
---
 lib/controls/server_side_sorting_control.js   | 75 +++++++------------
 ...js => server_side_sorting_control.test.js} | 54 +++++++------
 2 files changed, 58 insertions(+), 71 deletions(-)
 rename test/controls/{server_side_sorting_control_test.js => server_side_sorting_control.test.js} (63%)

diff --git a/lib/controls/server_side_sorting_control.js b/lib/controls/server_side_sorting_control.js
index 0f6308a..c55b6fa 100644
--- a/lib/controls/server_side_sorting_control.js
+++ b/lib/controls/server_side_sorting_control.js
@@ -13,7 +13,6 @@ var BerReader = asn1.BerReader;
 var BerWriter = asn1.BerWriter;
 
 
-
 ///--- API
 
 function ServerSideSortingControl(options) {
@@ -26,7 +25,7 @@ function ServerSideSortingControl(options) {
       this.parse(options.value);
     } else if (Array.isArray(options.value)) {
       for (var i = 0; i < options.value.length; i++) {
-        if (!typeof (options.value[i]) === 'object') {
+        if (typeof (options.value[i]) !== 'object') {
           throw new TypeError('Elements of options.value must be Objects');
         } else if (!options.value[i].hasOwnProperty('attributeType')) {
           throw new Error('Missing required key: attributeType');
@@ -37,7 +36,7 @@ function ServerSideSortingControl(options) {
       if (!options.value.hasOwnProperty('attributeType')) {
         throw new Error('Missing required key: attributeType');
       }
-      this._value = options.value;
+      this._value = [options.value];
     } else {
       throw new TypeError('options.value must be a Buffer, Array or Object');
     }
@@ -47,7 +46,7 @@ function ServerSideSortingControl(options) {
 
   var self = this;
   this.__defineGetter__('value', function () {
-    return self._value || {};
+    return self._value || [];
   });
 }
 util.inherits(ServerSideSortingControl, Control);
@@ -58,22 +57,23 @@ ServerSideSortingControl.prototype.parse = function parse(buffer) {
   assert.ok(buffer);
 
   var ber = new BerReader(buffer);
-
+  var item;
   if (ber.readSequence(0x30)) {
     this._value = [];
 
     while (ber.readSequence(0x30)) {
-      var sortKeyListItem = this._parseSortKeyListItem(ber)
-      this._value.push(sortKeyListItem);
+      item = {};
+      item.attributeType = ber.readString(asn1.Ber.OctetString);
+      if (ber.peek() == 0x80) {
+        item.orderingRule = ber.readString(0x80);
+      }
+      if (ber.peek() == 0x81) {
+        item.reverseOrder = (ber._readTag(0x81) === 0 ? false : true);
+      }
+      this._value.push(item);
     }
-
-    if (this._value.length == 1) {
-      this._value = this._value[0];
-    }
-
     return true;
   }
-
   return false;
 };
 
@@ -81,20 +81,25 @@ ServerSideSortingControl.prototype.parse = function parse(buffer) {
 ServerSideSortingControl.prototype._toBer = function (ber) {
   assert.ok(ber);
 
-  if (!this._value)
+  if (!this._value || this.value.length === 0)
     return;
 
   var writer = new BerWriter();
   writer.startSequence(0x30);
-
-  if (Array.isArray(this.value)) {
-    for (var i = 0; i < this.value.length; i++) {
-      this._sortKeyListItemToBer(writer, this.value[i]);
+  for (var i = 0; i < this.value.length; i++) {
+    var item = this.value[i];
+    writer.startSequence(0x30);
+    if (item.attributeType) {
+      writer.writeString(item.attributeType, asn1.Ber.OctetString);
     }
-  } else if (typeof (this.value) === 'object') {
-    this._sortKeyListItemToBer(writer, this.value);
+    if (item.orderingRule) {
+      writer.writeString(item.orderingRule, 0x80);
+    }
+    if (item.reverseOrder) {
+      writer.writeBoolean(item.reverseOrder, 0x81);
+    }
+    writer.endSequence();
   }
-
   writer.endSequence();
   ber.writeBuffer(writer.buffer, 0x04);
 };
@@ -105,33 +110,5 @@ ServerSideSortingControl.prototype._json = function (obj) {
   return obj;
 };
 
-ServerSideSortingControl.prototype._sortKeyListItemToBer = function(writer, obj) {
-  writer.startSequence(0x30);
-  if (obj.attributeType) {
-    writer.writeString(obj.attributeType, asn1.Ber.OctetString);
-  }
-  if (obj.orderingRule) {
-    writer.writeString(obj.orderingRule, 0x80);
-  }
-  if (obj.reverseOrder) {
-    writer.writeBoolean(obj.reverseOrder, 0x81);
-  }
-  writer.endSequence();
-};
-
-ServerSideSortingControl.prototype._parseSortKeyListItem = function(reader) {
-  var sortKeyListItem = {};
-  sortKeyListItem.attributeType = reader.readString(asn1.Ber.OctetString);
-
-  if (reader.peek() == 0x80) {
-    sortKeyListItem.orderingRule = reader.readString(0x80);
-  }
-
-  if (reader.peek() == 0x81) {
-    sortKeyListItem.reverseOrder = (reader._readTag(0x81) === 0 ? false : true);
-  }
-
-  return sortKeyListItem;
-};
 
 ServerSideSortingControl.OID = '1.2.840.113556.1.4.473';
diff --git a/test/controls/server_side_sorting_control_test.js b/test/controls/server_side_sorting_control.test.js
similarity index 63%
rename from test/controls/server_side_sorting_control_test.js
rename to test/controls/server_side_sorting_control.test.js
index dc28a75..a144421 100644
--- a/test/controls/server_side_sorting_control_test.js
+++ b/test/controls/server_side_sorting_control.test.js
@@ -12,24 +12,20 @@ var ServerSideSortingControl;
 
 
 test('load library', function (t) {
-  ServerSideSortingControl =
-    require('../../lib').ServerSideSortingControl;
+  ServerSideSortingControl = require('../../lib').ServerSideSortingControl;
   t.ok(ServerSideSortingControl);
   getControl = require('../../lib').getControl;
   t.ok(getControl);
   t.end();
 });
 
-
 test('new no args', function (t) {
   t.ok(new ServerSideSortingControl());
   t.end();
 });
 
-
 test('new with args', function (t) {
   var c = new ServerSideSortingControl({
-    type: '1.2.840.113556.1.4.473',
     criticality: true,
     value: {
       attributeType: 'sn'
@@ -38,14 +34,14 @@ test('new with args', function (t) {
   t.ok(c);
   t.equal(c.type, '1.2.840.113556.1.4.473');
   t.ok(c.criticality);
-  t.equal(c.value.attributeType, 'sn');
+  t.equal(c.value.length, 1);
+  t.equal(c.value[0].attributeType, 'sn');
 
   t.end();
 });
 
-test('tober - object', function (t) {
+test('toBer - object', function (t) {
   var sssc = new ServerSideSortingControl({
-    type: '1.2.840.113556.1.4.473',
     criticality: true,
     value: {
       attributeType: 'sn',
@@ -60,26 +56,27 @@ test('tober - object', function (t) {
   t.ok(c);
   t.equal(c.type, '1.2.840.113556.1.4.473');
   t.ok(c.criticality);
-  t.equal(c.value.attributeType, 'sn');
-  t.equal(c.value.orderingRule, 'caseIgnoreOrderingMatch');
-  t.equal(c.value.reverseOrder, true);
+  t.equal(c.value[0].attributeType, 'sn');
+  t.equal(c.value[0].orderingRule, 'caseIgnoreOrderingMatch');
+  t.equal(c.value[0].reverseOrder, true);
 
   t.end();
 });
 
-test('tober - array', function (t) {
+test('toBer - array', function (t) {
   var sssc = new ServerSideSortingControl({
-    type: '1.2.840.113556.1.4.473',
     criticality: true,
-    value: [{
-      attributeType: 'sn',
-      orderingRule: 'caseIgnoreOrderingMatch',
-      reverseOrder: true
-    },
-    {
-      attributeType: 'givenName',
-      orderingRule: 'caseIgnoreOrderingMatch'
-      }]
+    value: [
+      {
+        attributeType: 'sn',
+        orderingRule: 'caseIgnoreOrderingMatch',
+        reverseOrder: true
+      },
+      {
+        attributeType: 'givenName',
+        orderingRule: 'caseIgnoreOrderingMatch'
+      }
+    ]
   });
 
   var ber = new BerWriter();
@@ -89,6 +86,7 @@ test('tober - array', function (t) {
   t.ok(c);
   t.equal(c.type, '1.2.840.113556.1.4.473');
   t.ok(c.criticality);
+  t.equal(c.value.length, 2);
   t.equal(c.value[0].attributeType, 'sn');
   t.equal(c.value[0].orderingRule, 'caseIgnoreOrderingMatch');
   t.equal(c.value[0].reverseOrder, true);
@@ -97,3 +95,15 @@ test('tober - array', function (t) {
 
   t.end();
 });
+
+test('toBer - empty', function (t) {
+  var sssc = new ServerSideSortingControl();
+  var ber = new BerWriter();
+  sssc.toBer(ber);
+
+  var c = getControl(new BerReader(ber.buffer));
+  t.ok(c);
+  t.equal(c.type, '1.2.840.113556.1.4.473');
+  t.equal(c.value.length, 0);
+  t.end();
+});

From 352e4bbfba5407034363d00ec69fa234b6f8fd45 Mon Sep 17 00:00:00 2001
From: Patrick Mooney <patrick.f.mooney@gmail.com>
Date: Mon, 9 Jun 2014 15:37:19 -0700
Subject: [PATCH 5/6] Rename ServerSideSortingControl

ServerSideSortingRequestControl will be more consistent when
ServerSideSortingResponseControl is implemented.
---
 lib/controls/index.js                            |  9 +++++----
 ...js => server_side_sorting_request_control.js} | 16 ++++++++--------
 ... server_side_sorting_control_request.test.js} | 16 ++++++++--------
 3 files changed, 21 insertions(+), 20 deletions(-)
 rename lib/controls/{server_side_sorting_control.js => server_side_sorting_request_control.js} (83%)
 rename test/controls/{server_side_sorting_control.test.js => server_side_sorting_control_request.test.js} (85%)

diff --git a/lib/controls/index.js b/lib/controls/index.js
index 4157d5f..114e735 100644
--- a/lib/controls/index.js
+++ b/lib/controls/index.js
@@ -7,7 +7,8 @@ var EntryChangeNotificationControl =
   require('./entry_change_notification_control');
 var PersistentSearchControl = require('./persistent_search_control');
 var PagedResultsControl = require('./paged_results_control');
-var ServerSideSortingControl = require('./server_side_sorting_control.js');
+var ServerSideSortingRequestControl =
+  require('./server_side_sorting_request_control.js');
 
 
 
@@ -58,8 +59,8 @@ module.exports = {
         value: value
       });
       break;
-    case ServerSideSortingControl.OID:
-      control = new ServerSideSortingControl({
+    case ServerSideSortingRequestControl.OID:
+      control = new ServerSideSortingRequestControl({
         critical: critical,
         value: value
       });
@@ -80,5 +81,5 @@ module.exports = {
   EntryChangeNotificationControl: EntryChangeNotificationControl,
   PagedResultsControl: PagedResultsControl,
   PersistentSearchControl: PersistentSearchControl,
-  ServerSideSortingControl: ServerSideSortingControl
+  ServerSideSortingRequestControl: ServerSideSortingRequestControl
 };
diff --git a/lib/controls/server_side_sorting_control.js b/lib/controls/server_side_sorting_request_control.js
similarity index 83%
rename from lib/controls/server_side_sorting_control.js
rename to lib/controls/server_side_sorting_request_control.js
index c55b6fa..4d2c6eb 100644
--- a/lib/controls/server_side_sorting_control.js
+++ b/lib/controls/server_side_sorting_request_control.js
@@ -15,11 +15,11 @@ var BerWriter = asn1.BerWriter;
 
 ///--- API
 
-function ServerSideSortingControl(options) {
+function ServerSideSortingRequestControl(options) {
   if (!options)
     options = {};
 
-  options.type = ServerSideSortingControl.OID;
+  options.type = ServerSideSortingRequestControl.OID;
   if (options.value) {
     if (Buffer.isBuffer(options.value)) {
       this.parse(options.value);
@@ -49,11 +49,11 @@ function ServerSideSortingControl(options) {
     return self._value || [];
   });
 }
-util.inherits(ServerSideSortingControl, Control);
-module.exports = ServerSideSortingControl;
+util.inherits(ServerSideSortingRequestControl, Control);
+module.exports = ServerSideSortingRequestControl;
 
 
-ServerSideSortingControl.prototype.parse = function parse(buffer) {
+ServerSideSortingRequestControl.prototype.parse = function parse(buffer) {
   assert.ok(buffer);
 
   var ber = new BerReader(buffer);
@@ -78,7 +78,7 @@ ServerSideSortingControl.prototype.parse = function parse(buffer) {
 };
 
 
-ServerSideSortingControl.prototype._toBer = function (ber) {
+ServerSideSortingRequestControl.prototype._toBer = function (ber) {
   assert.ok(ber);
 
   if (!this._value || this.value.length === 0)
@@ -105,10 +105,10 @@ ServerSideSortingControl.prototype._toBer = function (ber) {
 };
 
 
-ServerSideSortingControl.prototype._json = function (obj) {
+ServerSideSortingRequestControl.prototype._json = function (obj) {
   obj.controlValue = this.value;
   return obj;
 };
 
 
-ServerSideSortingControl.OID = '1.2.840.113556.1.4.473';
+ServerSideSortingRequestControl.OID = '1.2.840.113556.1.4.473';
diff --git a/test/controls/server_side_sorting_control.test.js b/test/controls/server_side_sorting_control_request.test.js
similarity index 85%
rename from test/controls/server_side_sorting_control.test.js
rename to test/controls/server_side_sorting_control_request.test.js
index a144421..e2787dd 100644
--- a/test/controls/server_side_sorting_control.test.js
+++ b/test/controls/server_side_sorting_control_request.test.js
@@ -6,26 +6,26 @@ var asn1 = require('asn1');
 var BerReader = asn1.BerReader;
 var BerWriter = asn1.BerWriter;
 var getControl;
-var ServerSideSortingControl;
+var SSSRControl;
 
 ///--- Tests
 
 
 test('load library', function (t) {
-  ServerSideSortingControl = require('../../lib').ServerSideSortingControl;
-  t.ok(ServerSideSortingControl);
+  SSSRControl = require('../../lib').ServerSideSortingRequestControl;
+  t.ok(SSSRControl);
   getControl = require('../../lib').getControl;
   t.ok(getControl);
   t.end();
 });
 
 test('new no args', function (t) {
-  t.ok(new ServerSideSortingControl());
+  t.ok(new SSSRControl());
   t.end();
 });
 
 test('new with args', function (t) {
-  var c = new ServerSideSortingControl({
+  var c = new SSSRControl({
     criticality: true,
     value: {
       attributeType: 'sn'
@@ -41,7 +41,7 @@ test('new with args', function (t) {
 });
 
 test('toBer - object', function (t) {
-  var sssc = new ServerSideSortingControl({
+  var sssc = new SSSRControl({
     criticality: true,
     value: {
       attributeType: 'sn',
@@ -64,7 +64,7 @@ test('toBer - object', function (t) {
 });
 
 test('toBer - array', function (t) {
-  var sssc = new ServerSideSortingControl({
+  var sssc = new SSSRControl({
     criticality: true,
     value: [
       {
@@ -97,7 +97,7 @@ test('toBer - array', function (t) {
 });
 
 test('toBer - empty', function (t) {
-  var sssc = new ServerSideSortingControl();
+  var sssc = new SSSRControl();
   var ber = new BerWriter();
   sssc.toBer(ber);
 

From e8593f78ce50ba9052b140a2cb9d0222dd06b22b Mon Sep 17 00:00:00 2001
From: Patrick Mooney <patrick.f.mooney@gmail.com>
Date: Mon, 9 Jun 2014 15:58:01 -0700
Subject: [PATCH 6/6] Add ServerSideSortingResponseControl

---
 lib/controls/index.js                         |  11 +-
 .../server_side_sorting_response_control.js   | 105 ++++++++++++++++
 ...rver_side_sorting_control_response.test.js | 117 ++++++++++++++++++
 3 files changed, 232 insertions(+), 1 deletion(-)
 create mode 100644 lib/controls/server_side_sorting_response_control.js
 create mode 100644 test/controls/server_side_sorting_control_response.test.js

diff --git a/lib/controls/index.js b/lib/controls/index.js
index 114e735..94f311a 100644
--- a/lib/controls/index.js
+++ b/lib/controls/index.js
@@ -9,6 +9,8 @@ var PersistentSearchControl = require('./persistent_search_control');
 var PagedResultsControl = require('./paged_results_control');
 var ServerSideSortingRequestControl =
   require('./server_side_sorting_request_control.js');
+var ServerSideSortingResponseControl =
+  require('./server_side_sorting_response_control.js');
 
 
 
@@ -65,6 +67,12 @@ module.exports = {
         value: value
       });
       break;
+    case ServerSideSortingResponseControl.OID:
+      control = new ServerSideSortingResponseControl({
+        critical: critical,
+        value: value
+      });
+      break;
     default:
       control = new Control({
         type: type,
@@ -81,5 +89,6 @@ module.exports = {
   EntryChangeNotificationControl: EntryChangeNotificationControl,
   PagedResultsControl: PagedResultsControl,
   PersistentSearchControl: PersistentSearchControl,
-  ServerSideSortingRequestControl: ServerSideSortingRequestControl
+  ServerSideSortingRequestControl: ServerSideSortingRequestControl,
+  ServerSideSortingResponseControl: ServerSideSortingResponseControl
 };
diff --git a/lib/controls/server_side_sorting_response_control.js b/lib/controls/server_side_sorting_response_control.js
new file mode 100644
index 0000000..9ac2a67
--- /dev/null
+++ b/lib/controls/server_side_sorting_response_control.js
@@ -0,0 +1,105 @@
+var assert = require('assert');
+var util = require('util');
+
+var asn1 = require('asn1');
+
+var Control = require('./control');
+
+var CODES = require('../errors/index');
+
+
+
+///--- Globals
+
+var BerReader = asn1.BerReader;
+var BerWriter = asn1.BerWriter;
+
+var VALID_CODES = [
+  CODES.LDAP_SUCCESS,
+  CODES.LDAP_OPERATIONS_ERROR,
+  CODES.LDAP_TIME_LIMIT_EXCEEDED,
+  CODES.LDAP_STRONG_AUTH_REQUIRED,
+  CODES.LDAP_ADMIN_LIMIT_EXCEEDED,
+  CODES.LDAP_NO_SUCH_ATTRIBUTE,
+  CODES.LDAP_INAPPROPRIATE_MATCHING,
+  CODES.LDAP_INSUFFICIENT_ACCESS_RIGHTS,
+  CODES.LDAP_BUSY,
+  CODES.LDAP_UNWILLING_TO_PERFORM,
+  CODES.LDAP_OTHER
+];
+
+function ServerSideSortingResponseControl(options) {
+  if (!options)
+    options = {};
+
+  options.type = ServerSideSortingResponseControl.OID;
+  options.criticality = false;
+
+  if (options.value) {
+    if (Buffer.isBuffer(options.value)) {
+      this.parse(options.value);
+    } else if (typeof (options.value) === 'object') {
+      if (VALID_CODES.indexOf(options.value.result) === -1) {
+        throw new Error('Invalid result code');
+      }
+      if (options.value.failedAttribute &&
+          typeof (options.value.failedAttribute) !== 'string') {
+        throw new Error('failedAttribute must be String');
+      }
+
+      this._value = options.value;
+    } else {
+      throw new TypeError('options.value must be a Buffer or Object');
+    }
+    options.value = null;
+  }
+  Control.call(this, options);
+
+  var self = this;
+  this.__defineGetter__('value', function () {
+    return self._value || {};
+  });
+}
+util.inherits(ServerSideSortingResponseControl, Control);
+module.exports = ServerSideSortingResponseControl;
+
+
+ServerSideSortingResponseControl.prototype.parse = function parse(buffer) {
+  assert.ok(buffer);
+
+  var ber = new BerReader(buffer);
+  if (ber.readSequence(0x30)) {
+    this._value = {};
+    this._value.result = ber.readEnumeration();
+    if (ber.peek() == 0x80) {
+      this._value.failedAttribute = ber.readString(0x80);
+    }
+    return true;
+  }
+  return false;
+};
+
+
+ServerSideSortingResponseControl.prototype._toBer = function (ber) {
+  assert.ok(ber);
+
+  if (!this._value || this.value.length === 0)
+    return;
+
+  var writer = new BerWriter();
+  writer.startSequence(0x30);
+  writer.writeEnumeration(this.value.result);
+  if (this.value.result !== CODES.LDAP_SUCCESS && this.value.failedAttribute) {
+    writer.writeString(this.value.failedAttribute, 0x80);
+  }
+  writer.endSequence();
+  ber.writeBuffer(writer.buffer, 0x04);
+};
+
+
+ServerSideSortingResponseControl.prototype._json = function (obj) {
+  obj.controlValue = this.value;
+  return obj;
+};
+
+ServerSideSortingResponseControl.OID = '1.2.840.113556.1.4.474';
diff --git a/test/controls/server_side_sorting_control_response.test.js b/test/controls/server_side_sorting_control_response.test.js
new file mode 100644
index 0000000..432edb7
--- /dev/null
+++ b/test/controls/server_side_sorting_control_response.test.js
@@ -0,0 +1,117 @@
+var test = require('tap').test;
+
+var asn1 = require('asn1');
+
+var BerReader = asn1.BerReader;
+var BerWriter = asn1.BerWriter;
+var ldap;
+var getControl;
+var SSSResponseControl;
+var OID = '1.2.840.113556.1.4.474';
+
+///--- Tests
+
+
+test('load library', function (t) {
+  ldap = require('../../lib');
+  SSSResponseControl = ldap.ServerSideSortingResponseControl;
+  t.ok(SSSResponseControl);
+  getControl = ldap.getControl;
+  t.ok(getControl);
+  t.end();
+});
+
+test('new no args', function (t) {
+  var c = new SSSResponseControl();
+  t.ok(c);
+  t.equal(c.type, OID);
+  t.equal(c.criticality, false);
+  t.end();
+});
+
+test('new with args', function (t) {
+  var c = new SSSResponseControl({
+    criticality: true,
+    value: {
+      result: ldap.LDAP_SUCCESS,
+      failedAttribute: 'cn'
+    }
+  });
+  t.ok(c);
+  t.equal(c.type, OID);
+  t.equal(c.criticality, false);
+  t.equal(c.value.result, ldap.LDAP_SUCCESS);
+  t.equal(c.value.failedAttribute, 'cn');
+
+  t.end();
+});
+
+test('toBer - success', function (t) {
+  var sssc = new SSSResponseControl({
+    value: {
+      result: ldap.LDAP_SUCCESS,
+      failedAttribute: 'foobar'
+  }});
+
+  var ber = new BerWriter();
+  sssc.toBer(ber);
+
+  var c = getControl(new BerReader(ber.buffer));
+  t.ok(c);
+  t.equal(c.type, '1.2.840.113556.1.4.474');
+  t.equal(c.criticality, false);
+  t.equal(c.value.result, ldap.LDAP_SUCCESS);
+  t.notOk(c.value.failedAttribute);
+  t.end();
+});
+
+test('toBer - simple failure', function (t) {
+  var sssc = new SSSResponseControl({
+    value: {
+      result: ldap.LDAP_NO_SUCH_ATTRIBUTE
+  }});
+
+  var ber = new BerWriter();
+  sssc.toBer(ber);
+
+  var c = getControl(new BerReader(ber.buffer));
+  t.ok(c);
+  t.equal(c.type, OID);
+  t.equal(c.criticality, false);
+  t.equal(c.value.result, ldap.LDAP_NO_SUCH_ATTRIBUTE);
+  t.notOk(c.value.failedAttribute);
+  t.end();
+});
+
+test('toBer - detailed failure', function (t) {
+  var sssc = new SSSResponseControl({
+    value: {
+      result: ldap.LDAP_NO_SUCH_ATTRIBUTE,
+      failedAttribute: 'foobar'
+  }});
+
+  var ber = new BerWriter();
+  sssc.toBer(ber);
+
+  var c = getControl(new BerReader(ber.buffer));
+  t.ok(c);
+  t.equal(c.type, OID);
+  t.equal(c.criticality, false);
+  t.equal(c.value.result, ldap.LDAP_NO_SUCH_ATTRIBUTE);
+  t.equal(c.value.failedAttribute, 'foobar');
+  t.end();
+});
+
+test('toBer - empty', function (t) {
+  var sssc = new SSSResponseControl();
+  var ber = new BerWriter();
+  sssc.toBer(ber);
+
+  var c = getControl(new BerReader(ber.buffer));
+  t.ok(c);
+  t.equal(c.type, OID);
+  t.equal(c.criticality, false);
+  t.notOk(c.value.result);
+  t.notOk(c.value.failedAttribute);
+  t.end();
+});