summaryrefslogtreecommitdiffstats
path: root/dom/secureelement/gonk/UiccConnector.js
diff options
context:
space:
mode:
Diffstat (limited to 'dom/secureelement/gonk/UiccConnector.js')
-rw-r--r--dom/secureelement/gonk/UiccConnector.js360
1 files changed, 360 insertions, 0 deletions
diff --git a/dom/secureelement/gonk/UiccConnector.js b/dom/secureelement/gonk/UiccConnector.js
new file mode 100644
index 000000000..517303de2
--- /dev/null
+++ b/dom/secureelement/gonk/UiccConnector.js
@@ -0,0 +1,360 @@
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Copyright © 2014, Deutsche Telekom, Inc. */
+
+"use strict";
+
+/* globals Components, XPCOMUtils, SE, dump, libcutils, Services,
+ iccService, SEUtils */
+
+const { interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/systemlibs.js");
+
+XPCOMUtils.defineLazyGetter(this, "SE", function() {
+ let obj = {};
+ Cu.import("resource://gre/modules/se_consts.js", obj);
+ return obj;
+});
+
+// set to true in se_consts.js to see debug messages
+var DEBUG = SE.DEBUG_CONNECTOR;
+function debug(s) {
+ if (DEBUG) {
+ dump("-*- UiccConnector: " + s + "\n");
+ }
+}
+
+XPCOMUtils.defineLazyModuleGetter(this, "SEUtils",
+ "resource://gre/modules/SEUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "iccService",
+ "@mozilla.org/icc/iccservice;1",
+ "nsIIccService");
+
+const UICCCONNECTOR_CONTRACTID =
+ "@mozilla.org/secureelement/connector/uicc;1";
+const UICCCONNECTOR_CID =
+ Components.ID("{8e040e5d-c8c3-4c1b-ac82-c00d25d8c4a4}");
+const NS_XPCOM_SHUTDOWN_OBSERVER_ID = "xpcom-shutdown";
+
+// TODO: Bug 1118099 - Add multi-sim support.
+// In the Multi-sim, there is more than one client.
+// For now, use default clientID as 0. Ideally, SE parent process would like to
+// know which clients (uicc slot) are connected to CLF over SWP interface.
+const PREFERRED_UICC_CLIENTID =
+ libcutils.property_get("ro.moz.se.def_client_id", "0");
+
+/**
+ * 'UiccConnector' object is a wrapper over iccService's channel management
+ * related interfaces that implements nsISecureElementConnector interface.
+ */
+function UiccConnector() {
+ this._init();
+}
+
+UiccConnector.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISecureElementConnector,
+ Ci.nsIIccListener]),
+ classID: UICCCONNECTOR_CID,
+ classInfo: XPCOMUtils.generateCI({
+ classID: UICCCONNECTOR_CID,
+ contractID: UICCCONNECTOR_CONTRACTID,
+ classDescription: "UiccConnector",
+ interfaces: [Ci.nsISecureElementConnector,
+ Ci.nsIIccListener,
+ Ci.nsIObserver]
+ }),
+
+ _SEListeners: [],
+ _isPresent: false,
+
+ _init: function() {
+ Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ let icc = iccService.getIccByServiceId(PREFERRED_UICC_CLIENTID);
+ icc.registerListener(this);
+
+ // Update the state in order to avoid race condition.
+ // By this time, 'notifyCardStateChanged (with proper card state)'
+ // may have occurred already before this module initialization.
+ this._updatePresenceState();
+ },
+
+ _shutdown: function() {
+ Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ let icc = iccService.getIccByServiceId(PREFERRED_UICC_CLIENTID);
+ icc.unregisterListener(this);
+ },
+
+ _updatePresenceState: function() {
+ let uiccNotReadyStates = [
+ Ci.nsIIcc.CARD_STATE_UNKNOWN,
+ Ci.nsIIcc.CARD_STATE_ILLEGAL,
+ Ci.nsIIcc.CARD_STATE_PERSONALIZATION_IN_PROGRESS,
+ Ci.nsIIcc.CARD_STATE_PERMANENT_BLOCKED,
+ Ci.nsIIcc.CARD_STATE_UNDETECTED
+ ];
+
+ let cardState = iccService.getIccByServiceId(PREFERRED_UICC_CLIENTID).cardState;
+ let uiccPresent = cardState !== null &&
+ uiccNotReadyStates.indexOf(cardState) == -1;
+
+ if (this._isPresent === uiccPresent) {
+ return;
+ }
+
+ debug("Uicc presence changed " + this._isPresent + " -> " + uiccPresent);
+ this._isPresent = uiccPresent;
+ this._SEListeners.forEach((listener) => {
+ listener.notifySEPresenceChanged(SE.TYPE_UICC, this._isPresent);
+ });
+ },
+
+ // See GP Spec, 11.1.4 Class Byte Coding
+ _setChannelToCLAByte: function(cla, channel) {
+ if (channel < SE.LOGICAL_CHANNEL_NUMBER_LIMIT) {
+ // b7 = 0 indicates the first interindustry class byte coding
+ cla = (cla & 0x9C) & 0xFF | channel;
+ } else if (channel < SE.SUPPLEMENTARY_LOGICAL_CHANNEL_NUMBER_LIMIT) {
+ // b7 = 1 indicates the further interindustry class byte coding
+ cla = (cla & 0xB0) & 0xFF | 0x40 | (channel - SE.LOGICAL_CHANNEL_NUMBER_LIMIT);
+ } else {
+ debug("Channel number must be within [0..19]");
+ return SE.ERROR_GENERIC;
+ }
+ return cla;
+ },
+
+ _doGetOpenResponse: function(channel, length, callback) {
+ // Le value is set. It means that this is a request for all available
+ // response bytes.
+ let cla = this._setChannelToCLAByte(SE.CLA_GET_RESPONSE, channel);
+ this.exchangeAPDU(channel, cla, SE.INS_GET_RESPONSE, 0x00, 0x00,
+ null, length, {
+ notifyExchangeAPDUResponse: function(sw1, sw2, response) {
+ debug("GET Response : " + response);
+ if (callback) {
+ callback({
+ error: SE.ERROR_NONE,
+ sw1: sw1,
+ sw2: sw2,
+ response: response
+ });
+ }
+ },
+
+ notifyError: function(reason) {
+ debug("Failed to get open response: " +
+ ", Rejected with Reason : " + reason);
+ if (callback) {
+ callback({ error: SE.ERROR_INVALIDAPPLICATION, reason: reason });
+ }
+ }
+ });
+ },
+
+ _doIccExchangeAPDU: function(channel, cla, ins, p1, p2, p3,
+ data, appendResp, callback) {
+ let icc = iccService.getIccByServiceId(PREFERRED_UICC_CLIENTID);
+ icc.iccExchangeAPDU(channel, cla & 0xFC, ins, p1, p2, p3, data, {
+ notifyExchangeAPDUResponse: (sw1, sw2, response) => {
+ debug("sw1 : " + sw1 + ", sw2 : " + sw2 + ", response : " + response);
+
+ // According to ETSI TS 102 221 , Section 7.2.2.3.1,
+ // Enforce 'Procedure bytes' checks before notifying the callback.
+ // Note that 'Procedure bytes'are special cases.
+ // There is no need to handle '0x60' procedure byte as it implies
+ // no-action from SE stack perspective. This procedure byte is not
+ // notified to application layer.
+ if (sw1 === 0x6C) {
+ // Use the previous command header with length as second procedure
+ // byte (SW2) as received and repeat the procedure.
+
+ // Recursive! and Pass empty response '' as args, since '0x6C'
+ // procedure does not have to deal with appended responses.
+ this._doIccExchangeAPDU(channel, cla, ins, p1, p2,
+ sw2, data, "", callback);
+ } else if (sw1 === 0x61) {
+ // Since the terminal waited for a second procedure byte and
+ // received it (sw2), send a GET RESPONSE command header to the UICC
+ // with a maximum length of 'XX', where 'XX' is the value of the
+ // second procedure byte (SW2).
+
+ let claWithChannel = this._setChannelToCLAByte(SE.CLA_GET_RESPONSE,
+ channel);
+
+ // Recursive, with GET RESPONSE bytes and '0x61' procedure IS interested
+ // in appended responses. Pass appended response and note that p3=sw2.
+ this._doIccExchangeAPDU(channel, claWithChannel, SE.INS_GET_RESPONSE,
+ 0x00, 0x00, sw2, null,
+ (response ? response + appendResp : appendResp),
+ callback);
+ } else if (callback) {
+ callback.notifyExchangeAPDUResponse(sw1, sw2, response);
+ }
+ },
+
+ notifyError: (reason) => {
+ debug("Failed to trasmit C-APDU over the channel # : " + channel +
+ ", Rejected with Reason : " + reason);
+ if (callback) {
+ callback.notifyError(reason);
+ }
+ }
+ });
+ },
+
+ /**
+ * nsISecureElementConnector interface methods.
+ */
+
+ /**
+ * Opens a channel on a default clientId
+ */
+ openChannel: function(aid, callback) {
+ if (!this._isPresent) {
+ callback.notifyError(SE.ERROR_NOTPRESENT);
+ return;
+ }
+
+ // TODO: Bug 1118106: Handle Resource management / leaks by persisting
+ // the newly opened channel in some persistent storage so that when this
+ // module gets restarted (say after opening a channel) in the event of
+ // some erroneous conditions such as gecko restart /, crash it can read
+ // the persistent storage to check if there are any held resources
+ // (opened channels) and close them.
+ let icc = iccService.getIccByServiceId(PREFERRED_UICC_CLIENTID);
+ icc.iccOpenChannel(aid, {
+ notifyOpenChannelSuccess: (channel) => {
+ this._doGetOpenResponse(channel, 0x00, function(result) {
+ if (callback) {
+ callback.notifyOpenChannelSuccess(channel, result.response);
+ }
+ });
+ },
+
+ notifyError: (reason) => {
+ debug("Failed to open the channel to AID : " + aid +
+ ", Rejected with Reason : " + reason);
+ if (callback) {
+ callback.notifyError(reason);
+ }
+ }
+ });
+ },
+
+ /**
+ * Transmit the C-APDU (command) on default clientId.
+ */
+ exchangeAPDU: function(channel, cla, ins, p1, p2, data, le, callback) {
+ if (!this._isPresent) {
+ callback.notifyError(SE.ERROR_NOTPRESENT);
+ return;
+ }
+
+ if (data && data.length % 2 !== 0) {
+ callback.notifyError("Data should be a hex string with length % 2 === 0");
+ return;
+ }
+
+ cla = this._setChannelToCLAByte(cla, channel);
+ let lc = data ? data.length / 2 : 0;
+ let p3 = lc || le;
+
+ if (lc && (le !== -1)) {
+ data += SEUtils.byteArrayToHexString([le]);
+ }
+
+ // Pass empty response '' as args as we are not interested in appended
+ // responses yet!
+ debug("exchangeAPDU on Channel # " + channel);
+ this._doIccExchangeAPDU(channel, cla, ins, p1, p2, p3, data, "",
+ callback);
+ },
+
+ /**
+ * Closes the channel on default clientId.
+ */
+ closeChannel: function(channel, callback) {
+ if (!this._isPresent) {
+ callback.notifyError(SE.ERROR_NOTPRESENT);
+ return;
+ }
+
+ let icc = iccService.getIccByServiceId(PREFERRED_UICC_CLIENTID);
+ icc.iccCloseChannel(channel, {
+ notifyCloseChannelSuccess: function() {
+ debug("closeChannel successfully closed the channel # : " + channel);
+ if (callback) {
+ callback.notifyCloseChannelSuccess();
+ }
+ },
+
+ notifyError: function(reason) {
+ debug("Failed to close the channel # : " + channel +
+ ", Rejected with Reason : " + reason);
+ if (callback) {
+ callback.notifyError(reason);
+ }
+ }
+ });
+ },
+
+ registerListener: function(listener) {
+ if (this._SEListeners.indexOf(listener) !== -1) {
+ throw Cr.NS_ERROR_UNEXPECTED;
+ }
+
+ this._SEListeners.push(listener);
+ // immediately notify listener about the current state
+ listener.notifySEPresenceChanged(SE.TYPE_UICC, this._isPresent);
+ },
+
+ unregisterListener: function(listener) {
+ let idx = this._SEListeners.indexOf(listener);
+ if (idx !== -1) {
+ this._SEListeners.splice(idx, 1);
+ }
+ },
+
+ /**
+ * nsIIccListener interface methods.
+ */
+ notifyStkCommand: function() {},
+
+ notifyStkSessionEnd: function() {},
+
+ notifyIccInfoChanged: function() {},
+
+ notifyCardStateChanged: function() {
+ debug("Card state changed, updating UICC presence.");
+ this._updatePresenceState();
+ },
+
+ /**
+ * nsIObserver interface methods.
+ */
+
+ observe: function(subject, topic, data) {
+ if (topic === NS_XPCOM_SHUTDOWN_OBSERVER_ID) {
+ this._shutdown();
+ }
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([UiccConnector]);