Bug 1118102 - Fix error handling by rejecting the promise request with specific SE errors instead of 'Generic Error'. r=allstars.chh r=khuey a=kwierso
authorSiddartha Pothapragada <Siddartha.Pothapragada@telekom.com>
Tue, 09 Jun 2015 11:18:51 -0700
changeset 247826 a40b2c6672cd30671090ac3afc45ad46ec3c4c27
parent 247825 a422730764e5cf43bae3f31fb2e32e8d9e0508e2
child 247831 13f8dcb5b7a5879aeac869b2bc74ac47b20c9c3d
push id60814
push userkwierso@gmail.com
push dateWed, 10 Jun 2015 02:29:27 +0000
treeherdermozilla-inbound@bc32a90612e5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersallstars.chh, khuey, kwierso
bugs1118102
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1118102 - Fix error handling by rejecting the promise request with specific SE errors instead of 'Generic Error'. r=allstars.chh r=khuey a=kwierso
dom/secureelement/DOMSecureElement.js
dom/secureelement/gonk/se_consts.js
dom/webidl/SecureElement.webidl
--- a/dom/secureelement/DOMSecureElement.js
+++ b/dom/secureelement/DOMSecureElement.js
@@ -27,16 +27,26 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "nsISyncMessageSender");
 
 XPCOMUtils.defineLazyGetter(this, "SE", function() {
   let obj = {};
   Cu.import("resource://gre/modules/se_consts.js", obj);
   return obj;
 });
 
+// Extend / Inherit from Error object
+function SEError(name, message) {
+  this.name = name || SE.ERROR_GENERIC;
+  this.message = message || "";
+}
+
+SEError.prototype = {
+  __proto__: Error.prototype,
+};
+
 function PromiseHelpersSubclass(win) {
   this._window = win;
 }
 
 PromiseHelpersSubclass.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
 
   _window: null,
@@ -69,21 +79,21 @@ PromiseHelpersSubclass.prototype = {
 
     // Get the context associated with this resolverId
     let context = this._context[resolverId];
     delete this._context[resolverId];
 
     return {resolver: resolver, context: context};
   },
 
-  rejectWithSEError: function rejectWithSEError(reason) {
-    return this.createSEPromise((resolverId) => {
-      debug("rejectWithSEError : " + reason);
-      this.takePromiseResolver(resolverId).reject(new Error(reason));
-    });
+  rejectWithSEError: function rejectWithSEError(name, message) {
+    let error = new SEError(name, message);
+    debug("rejectWithSEError - " + error.toString());
+
+    return this._window.Promise.reject(Cu.cloneInto(error, this._window));
   }
 };
 
 // Helper wrapper class to do promises related chores
 let PromiseHelpers;
 
 /**
  * Instance of 'SEReaderImpl' class is the connector to a secure element.
@@ -150,18 +160,19 @@ SEReaderImpl.prototype = {
       }
 
       let resolver = PromiseHelpers.takePromiseResolver(resolverId);
       // Wait till all the promises are resolved
       Promise.all(promises).then(() => {
         this._sessions = [];
         resolver.resolve();
       }, (reason) => {
-        resolver.reject(new Error(SE.ERROR_BADSTATE +
-          " Unable to close all channels associated with this reader"));
+        let error = new SEError(SE.ERROR_BADSTATE,
+          "Unable to close all channels associated with this reader");
+        resolver.reject(Cu.cloneInto(error, this._window));
       });
     });
   },
 
   updateSEPresence: function updateSEPresence(isSEPresent) {
     if (!isSEPresent) {
       this.invalidate();
       return;
@@ -198,23 +209,16 @@ SESessionImpl.prototype = {
   _isClosed: false,
 
   _reader: null,
 
   classID: Components.ID("{2b1809f8-17bd-4947-abd7-bdef1498561c}"),
   contractID: "@mozilla.org/secureelement/session;1",
   QueryInterface: XPCOMUtils.generateQI([]),
 
-  // Private function
-  _checkClosed: function _checkClosed() {
-    if (this._isClosed) {
-      throw new Error(SE.ERROR_BADSTATE + " Session Already Closed!");
-    }
-  },
-
   // Chrome-only function
   onChannelOpen: function onChannelOpen(channelCtx) {
     this._channels.push(channelCtx);
   },
 
   // Chrome-only function
   onChannelClose: function onChannelClose(channelCtx) {
     let index = this._channels.indexOf(channelCtx);
@@ -224,22 +228,25 @@ SESessionImpl.prototype = {
   },
 
   initialize: function initialize(win, readerCtx) {
     this._window = win;
     this._reader = readerCtx;
   },
 
   openLogicalChannel: function openLogicalChannel(aid) {
-    this._checkClosed();
+    if (this._isClosed) {
+      return PromiseHelpers.rejectWithSEError(SE.ERROR_BADSTATE,
+             "Session Already Closed!");
+    }
 
     let aidLen = aid ? aid.length : 0;
     if (aidLen < SE.MIN_AID_LEN || aidLen > SE.MAX_AID_LEN) {
-      return PromiseHelpers.rejectWithSEError(SE.ERROR_GENERIC +
-             " Invalid AID length - " + aidLen);
+      return PromiseHelpers.rejectWithSEError(SE.ERROR_ILLEGALPARAMETER,
+             "Invalid AID length - " + aidLen);
     }
 
     return PromiseHelpers.createSEPromise((resolverId) => {
       /**
        * @params for 'SE:OpenChannel'
        *
        * resolverId  : ID that identifies this IPC request.
        * aid         : AID that identifies the applet on SecureElement
@@ -251,17 +258,20 @@ SESessionImpl.prototype = {
         aid: aid,
         type: this.reader.type,
         appId: this._window.document.nodePrincipal.appId
       });
     }, this);
   },
 
   closeAll: function closeAll() {
-    this._checkClosed();
+    if (this._isClosed) {
+      return PromiseHelpers.rejectWithSEError(SE.ERROR_BADSTATE,
+             "Session Already Closed!");
+    }
 
     return PromiseHelpers.createSEPromise((resolverId) => {
       let promises = [];
       for (let channel of this._channels) {
         if (!channel.isClosed) {
           promises.push(channel.close());
         }
       }
@@ -271,17 +281,17 @@ SESessionImpl.prototype = {
         this._isClosed = true;
         this._channels = [];
         // Notify parent of this session instance's closure, so that its
         // instance entry can be removed from the parent as well.
         this._reader.onSessionClose(this.__DOM_IMPL__);
         resolver.resolve();
       }, (reason) => {
         resolver.reject(new Error(SE.ERROR_BADSTATE +
-          " Unable to close all channels associated with this session"));
+          "Unable to close all channels associated with this session"));
       });
     });
   },
 
   invalidate: function invlidate() {
     this._isClosed = true;
     this._channels.forEach(ch => ch.invalidate());
     this._channels = [];
@@ -315,22 +325,16 @@ SEChannelImpl.prototype = {
   openResponse: [],
 
   type: null,
 
   classID: Components.ID("{181ebcf4-5164-4e28-99f2-877ec6fa83b9}"),
   contractID: "@mozilla.org/secureelement/channel;1",
   QueryInterface: XPCOMUtils.generateQI([]),
 
-  _checkClosed: function _checkClosed() {
-    if (this._isClosed) {
-      throw new Error(SE.ERROR_BADSTATE + " Channel Already Closed!");
-    }
-  },
-
   // Chrome-only function
   onClose: function onClose() {
     this._isClosed = true;
     // Notify the parent
     this._session.onChannelClose(this.__DOM_IMPL__);
   },
 
   initialize: function initialize(win, channelToken, isBasicChannel,
@@ -344,38 +348,41 @@ SEChannelImpl.prototype = {
     this.openResponse = Cu.cloneInto(new Uint8Array(openResponse), win);
     this.type = isBasicChannel ? "basic" : "logical";
   },
 
   transmit: function transmit(command) {
     // TODO remove this once it will be possible to have a non-optional dict
     // in the WebIDL
     if (!command) {
-      return PromiseHelpers.rejectWithSEError(SE.ERROR_GENERIC +
-        " SECommand dict must be defined");
+      return PromiseHelpers.rejectWithSEError(SE.ERROR_ILLEGALPARAMETER,
+             "SECommand dict must be defined");
     }
 
-    this._checkClosed();
+    if (this._isClosed) {
+      return PromiseHelpers.rejectWithSEError(SE.ERROR_BADSTATE,
+             "Channel Already Closed!");
+    }
 
     let dataLen = command.data ? command.data.length : 0;
     if (dataLen > SE.MAX_APDU_LEN) {
-      return PromiseHelpers.rejectWithSEError(SE.ERROR_GENERIC +
+      return PromiseHelpers.rejectWithSEError(SE.ERROR_ILLEGALPARAMETER,
              " Command data length exceeds max limit - 255. " +
              " Extended APDU is not supported!");
     }
 
     if ((command.cla & 0x80 === 0) && ((command.cla & 0x60) !== 0x20)) {
       if (command.ins === SE.INS_MANAGE_CHANNEL) {
-        return PromiseHelpers.rejectWithSEError(SE.ERROR_SECURITY +
-               ", MANAGE CHANNEL command not permitted");
+        return PromiseHelpers.rejectWithSEError(SE.ERROR_SECURITY,
+               "MANAGE CHANNEL command not permitted");
       }
       if ((command.ins === SE.INS_SELECT) && (command.p1 == 0x04)) {
         // SELECT by DF Name (p1=04) is not allowed
-        return PromiseHelpers.rejectWithSEError(SE.ERROR_SECURITY +
-               ", SELECT command not permitted");
+        return PromiseHelpers.rejectWithSEError(SE.ERROR_SECURITY,
+               "SELECT command not permitted");
       }
       debug("Attempting to transmit an ISO command");
     } else {
       debug("Attempting to transmit GlobalPlatform command");
     }
 
     return PromiseHelpers.createSEPromise((resolverId) => {
       /**
@@ -392,17 +399,20 @@ SEChannelImpl.prototype = {
         apdu: command,
         channelToken: this._channelToken,
         appId: this._window.document.nodePrincipal.appId
       });
     }, this);
   },
 
   close: function close() {
-    this._checkClosed();
+    if (this._isClosed) {
+      return PromiseHelpers.rejectWithSEError(SE.ERROR_BADSTATE,
+             "Channel Already Closed!");
+    }
 
     return PromiseHelpers.createSEPromise((resolverId) => {
       /**
        * @params for 'SE:CloseChannel'
        *
        * resolverId  : Id that identifies this IPC request.
        * channelToken: Token that identifies the current channel over which
                        'c-apdu' is being sent.
@@ -578,29 +588,29 @@ SEManagerImpl.prototype = {
           context.onClose();
         }
         resolver.resolve();
         break;
       case "SE:GetSEReadersRejected":
       case "SE:OpenChannelRejected":
       case "SE:CloseChannelRejected":
       case "SE:TransmitAPDURejected":
-        let error = result.error || SE.ERROR_GENERIC;
-        resolver.reject(error);
+        let error = new SEError(result.error, result.reason);
+        resolver.reject(Cu.cloneInto(error, this._window));
         break;
       case "SE:ReaderPresenceChanged":
         debug("Reader " + result.type + " present: " + result.isPresent);
         let reader = this._readers.find(r => r.type === result.type);
         if (reader) {
           reader.updateSEPresence(result.isPresent);
         }
         break;
       default:
         debug("Could not find a handler for " + message.name);
-        resolver.reject();
+        resolver.reject(Cu.cloneInto(new SEError(), this._window));
         break;
     }
   }
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([
   SEResponseImpl, SEChannelImpl, SESessionImpl, SEReaderImpl, SEManagerImpl
 ]);
--- a/dom/secureelement/gonk/se_consts.js
+++ b/dom/secureelement/gonk/se_consts.js
@@ -54,14 +54,15 @@ this.INS_GET_RESPONSE = 0xC0;
 this.ERROR_NONE               = "";
 this.ERROR_SECURITY           = "SESecurityError";
 this.ERROR_IO                 = "SEIoError";
 this.ERROR_BADSTATE           = "SEBadStateError";
 this.ERROR_INVALIDCHANNEL     = "SEInvalidChannelError";
 this.ERROR_INVALIDAPPLICATION = "SEInvalidApplicationError";
 this.ERROR_GENERIC            = "SEGenericError";
 this.ERROR_NOTPRESENT         = "SENotPresentError";
+this.ERROR_ILLEGALPARAMETER   = "SEIllegalParameterError";
 
 this.TYPE_UICC = "uicc";
 this.TYPE_ESE = "eSE";
 
 // Allow this file to be imported via Components.utils.import().
 this.EXPORTED_SYMBOLS = Object.keys(this);
--- a/dom/webidl/SecureElement.webidl
+++ b/dom/webidl/SecureElement.webidl
@@ -11,16 +11,17 @@ enum SEType {
 
 enum SEError {
   "SESecurityError",            // Requested operation does not match the access control rules of the application.
   "SEIoError",                  // I/O Error while communicating with the secure element.
   "SEBadStateError",            // Error occuring as a result of bad state.
   "SEInvalidChannelError",      // Opening a channel failed because no channel is available.
   "SEInvalidApplicationError",  // The requested application was not found on the secure element.
   "SENotPresentError",          // Secure Element is not present
+  "SEIllegalParameterError",    // Request operation does not have valid parameters.
   "SEGenericError"              // Generic failures.
 };
 
 enum SEChannelType {
   "basic",
   "logical"
 };