/* 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 © 2013, Deutsche Telekom, Inc. */

"use strict";

const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
                                   "@mozilla.org/settingsService;1",
                                   "nsISettingsService");

XPCOMUtils.defineLazyGetter(this, "NFC", function () {
  let obj = {};
  Cu.import("resource://gre/modules/nfc_consts.js", obj);
  return obj;
});

Cu.import("resource://gre/modules/systemlibs.js");
const NFC_ENABLED = libcutils.property_get("ro.moz.nfc.enabled", "false") === "true";

// set to true in nfc_consts.js to see debug messages
var DEBUG = NFC.DEBUG_NFC;

var debug;
function updateDebug() {
  if (DEBUG || NFC.DEBUG_NFC) {
    debug = function (s) {
      dump("-*- Nfc: " + s + "\n");
    };
  } else {
    debug = function (s) {};
  }
};
updateDebug();

const NFC_CONTRACTID = "@mozilla.org/nfc;1";
const NFC_CID =
  Components.ID("{2ff24790-5e74-11e1-b86c-0800200c9a66}");

const NFC_IPC_MSG_ENTRIES = [
  { permission: null,
    messages: ["NFC:AddEventListener",
               "NFC:RemoveEventListener",
               "NFC:QueryInfo",
               "NFC:CallDefaultFoundHandler",
               "NFC:CallDefaultLostHandler"] },

  { permission: "nfc",
    messages: ["NFC:ReadNDEF",
               "NFC:WriteNDEF",
               "NFC:MakeReadOnly",
               "NFC:Format",
               "NFC:Transceive"] },

  { permission: "nfc-share",
    messages: ["NFC:SendFile",
               "NFC:RegisterPeerReadyTarget",
               "NFC:UnregisterPeerReadyTarget"] },

  { permission: "nfc-manager",
    messages: ["NFC:CheckP2PRegistration",
               "NFC:NotifyUserAcceptedP2P",
               "NFC:NotifySendFileStatus",
               "NFC:ChangeRFState",
               "NFC:SetFocusTab"] }
];

// Should be consistent with NfcRequestType defined in NfcOptions.webidl.
const NfcRequestType = {
  CHANGE_RF_STATE: "changeRFState",
  READ_NDEF: "readNDEF",
  WRITE_NDEF: "writeNDEF",
  MAKE_READ_ONLY: "makeReadOnly",
  FORMAT: "format",
  TRANSCEIVE: "transceive"
};

const CommandMsgTable = {};
CommandMsgTable["NFC:ChangeRFState"] = NfcRequestType.CHANGE_RF_STATE;
CommandMsgTable["NFC:ReadNDEF"] = NfcRequestType.READ_NDEF;
CommandMsgTable["NFC:WriteNDEF"] = NfcRequestType.WRITE_NDEF;
CommandMsgTable["NFC:MakeReadOnly"] = NfcRequestType.MAKE_READ_ONLY;
CommandMsgTable["NFC:Format"] = NfcRequestType.FORMAT;
CommandMsgTable["NFC:Transceive"] = NfcRequestType.TRANSCEIVE;

// Should be consistent with NfcResponseType defined in NfcOptions.webidl.
const NfcResponseType = {
  CHANGE_RF_STATE_RSP: "changeRFStateRsp",
  READ_NDEF_RSP: "readNDEFRsp",
  WRITE_NDEF_RSP: "writeNDEFRsp",
  MAKE_READ_ONLY_RSP: "makeReadOnlyRsp",
  FORMAT_RSP: "formatRsp",
  TRANSCEIVE_RSP: "transceiveRsp",
};

const EventMsgTable = {};
EventMsgTable[NfcResponseType.CHANGE_RF_STATE_RSP] = "NFC:ChangeRFStateResponse";
EventMsgTable[NfcResponseType.READ_NDEF_RSP] = "NFC:ReadNDEFResponse";
EventMsgTable[NfcResponseType.WRITE_NDEF_RSP] = "NFC:WriteNDEFResponse";
EventMsgTable[NfcResponseType.MAKE_READ_ONLY_RSP] = "NFC:MakeReadOnlyResponse";
EventMsgTable[NfcResponseType.FORMAT_RSP] = "NFC:FormatResponse";
EventMsgTable[NfcResponseType.TRANSCEIVE_RSP] = "NFC:TransceiveResponse";

// Should be consistent with NfcNotificationType defined in NfcOptions.webidl.
const NfcNotificationType = {
  INITIALIZED: "initialized",
  TECH_DISCOVERED: "techDiscovered",
  TECH_LOST: "techLost",
  HCI_EVENT_TRANSACTION: "hciEventTransaction",
  NDEF_RECEIVED: "ndefReceived"
};

XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
                                   "@mozilla.org/parentprocessmessagemanager;1",
                                   "nsIMessageBroadcaster");
XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger",
                                   "@mozilla.org/system-message-internal;1",
                                   "nsISystemMessagesInternal");
XPCOMUtils.defineLazyServiceGetter(this, "UUIDGenerator",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
XPCOMUtils.defineLazyGetter(this, "gMessageManager", function () {
  return {
    QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener,
                                           Ci.nsIObserver]),

    nfc: null,

    // Manage registered Peer Targets
    peerTargets: {},

    eventListeners: {},

    focusId: NFC.SYSTEM_APP_ID,

    init: function init(nfc) {
      this.nfc = nfc;

      if (!NFC.DEBUG_NFC) {
        let lock = gSettingsService.createLock();
        lock.get(NFC.SETTING_NFC_DEBUG, this.nfc);
        Services.obs.addObserver(this, NFC.TOPIC_MOZSETTINGS_CHANGED, false);
      }

      Services.obs.addObserver(this, NFC.TOPIC_XPCOM_SHUTDOWN, false);
      this._registerMessageListeners();
    },

    _shutdown: function _shutdown() {
      this.nfc.shutdown();
      this.nfc = null;

      Services.obs.removeObserver(this, NFC.TOPIC_MOZSETTINGS_CHANGED);
      Services.obs.removeObserver(this, NFC.TOPIC_XPCOM_SHUTDOWN);
      this._unregisterMessageListeners();
    },

    _registerMessageListeners: function _registerMessageListeners() {
      ppmm.addMessageListener("child-process-shutdown", this);

      for (let entry of NFC_IPC_MSG_ENTRIES) {
        for (let message of entry.messages) {
          ppmm.addMessageListener(message, this);
        }
      }
    },

    _unregisterMessageListeners: function _unregisterMessageListeners() {
      ppmm.removeMessageListener("child-process-shutdown", this);

      for (let entry of NFC_IPC_MSG_ENTRIES) {
        for (let message of entry.messages) {
          ppmm.removeMessageListener(message, this);
        }
      }

      ppmm = null;
    },

    registerPeerReadyTarget: function registerPeerReadyTarget(target, appId) {
      if (!this.peerTargets[appId]) {
        this.peerTargets[appId] = target;
      }
    },

    unregisterPeerReadyTarget: function unregisterPeerReadyTarget(appId) {
      if (this.peerTargets[appId]) {
        delete this.peerTargets[appId];
      }
    },

    removePeerTarget: function removePeerTarget(target) {
      Object.keys(this.peerTargets).forEach((appId) => {
        if (this.peerTargets[appId] === target) {
          delete this.peerTargets[appId];
        }
      });
    },

    notifyFocusTab: function notifyFocusTab(options) {
      let tabId = this.getFocusTabId();
      options.tabId = tabId;

      this.notifyDOMEvent(this.eventListeners[tabId], options);
    },

    notifyDOMEvent: function notifyDOMEvent(target, options) {
      if (!target) {
        dump("invalid target");
        return;
      }

      target.sendAsyncMessage("NFC:DOMEvent", options);
    },

    getFocusTabId: function getFocusTabId() {
      return this.eventListeners[this.focusId] ? this.focusId
                                               : NFC.SYSTEM_APP_ID;
    },

    setFocusTab: function setFocusTab(id, isFocus) {
      // if calling setNFCFocus(true) on the browser-element which is already
      // focused, or calling setNFCFocus(false) on the browser-element which has
      // lost focus already, ignore.
      if (isFocus == (id == this.focusId)) {
        return;
      }

      if (this.focusId != NFC.SYSTEM_APP_ID) {
        this.onFocusChanged(this.focusId, false);
      }

      if (isFocus) {
        // Now we only support one focus app.
        this.focusId = id;
        this.onFocusChanged(this.focusId, true);
      } else if (this.focusId == id){
        // Set focusId to SystemApp means currently there is no foreground app.
        this.focusId = NFC.SYSTEM_APP_ID;
      }
    },

    addEventListener: function addEventListener(target, id) {
      if (this.eventListeners[id] !== undefined) {
        return;
      }

      this.eventListeners[id] = target;
    },

    removeEventListener: function removeEventListener(target) {
      for (let id in this.eventListeners) {
        if (target == this.eventListeners[id]) {
          this.removeEventListenerById(id);
          break;
        }
      }
    },

    removeEventListenerById: function removeEventListenerById(id) {
      delete this.eventListeners[id];
    },

    checkP2PRegistration: function checkP2PRegistration(message) {
      let target = this.peerTargets[message.data.appId];
      let sessionToken = SessionHelper.getCurrentP2PToken();
      let isValid = (sessionToken != null) && (target != null);
      let respMsg = { requestId: message.data.requestId };
      if (!isValid) {
        respMsg.errorMsg = this.nfc.getErrorMessage(NFC.NFC_GECKO_ERROR_P2P_REG_INVALID);
      }
      // Notify the content process immediately of the status
      message.target.sendAsyncMessage(message.name + "Response", respMsg);
    },

    notifyUserAcceptedP2P: function notifyUserAcceptedP2P(appId) {
      let target = this.peerTargets[appId];
      let sessionToken = SessionHelper.getCurrentP2PToken();
      if (!sessionToken || !target) {
        debug("Peer already lost or " + appId + " is not a registered PeerReadytarget");
        return;
      }

      this.notifyDOMEvent(target, {tabId: this.getFocusTabId(),
                                   event: NFC.PEER_EVENT_READY,
                                   sessionToken: sessionToken});
    },

    notifySendFileStatus: function notifySendFileStatus(message) {
      if (message.data.status) {
        message.data.errorMsg =
            this.nfc.getErrorMessage(NFC.NFC_GECKO_ERROR_SEND_FILE_FAILED);
      }
      this.nfc.sendFileStatusResponse(message.data);
    },

    callDefaultFoundHandler: function callDefaultFoundHandler(message) {
      let sysMsg = new NfcTechDiscoveredSysMsg(message.sessionToken,
                                               message.isP2P,
                                               message.records || null);
      gSystemMessenger.broadcastMessage("nfc-manager-tech-discovered", sysMsg);
    },

    callDefaultLostHandler: function callDefaultLostHandler(message) {
      // message.isP2P is not used.
      gSystemMessenger.broadcastMessage("nfc-manager-tech-lost", message.sessionToken);
    },

    onTagFound: function onTagFound(message) {
      message.event = NFC.TAG_EVENT_FOUND;
      this.notifyFocusTab(message);
      delete message.event;
    },

    onTagLost: function onTagLost(sessionToken) {
      this.notifyFocusTab({ event: NFC.TAG_EVENT_LOST,
                            sessionToken: sessionToken });
    },

    onPeerEvent: function onPeerEvent(eventType, sessionToken) {
      this.notifyFocusTab({ event: eventType,
                            sessionToken: sessionToken });
    },

    onRFStateChanged: function onRFStateChanged(rfState) {
      for (let id in this.eventListeners) {
        this.notifyDOMEvent(this.eventListeners[id],
                            { tabId: id,
                              event: NFC.RF_EVENT_STATE_CHANGED,
                              rfState: rfState });
      }
    },

    onFocusChanged: function onFocusChanged(focusId, focus) {
      let target = this.eventListeners[focusId];
      if (!target) {
        return;
      }

      this.notifyDOMEvent(target, { tabId: this.focusId,
                                    event: NFC.FOCUS_CHANGED,
                                    focus: focus });
    },

    /**
     * nsIMessageListener interface methods.
     */

    receiveMessage: function receiveMessage(message) {
      DEBUG && debug("Received message from content process: " + JSON.stringify(message));

      if (message.name == "child-process-shutdown") {
        this.removePeerTarget(message.target);
        this.nfc.removeTarget(message.target);
        this.removeEventListener(message.target);
        return null;
      }

      for (let entry of NFC_IPC_MSG_ENTRIES) {
        if (entry.messages.indexOf(message.name) != -1) {
          if (entry.permission &&
              !message.target.assertPermission(entry.permission)) {
            debug("Nfc message " + message.name + "doesn't have " +
                  entry.permission + " permission.");
            return null;
          }
          break;
        }
      }

      switch (message.name) {
        case "NFC:SetFocusTab":
          this.setFocusTab(message.data.tabId, message.data.isFocus);
          return null;
        case "NFC:AddEventListener":
          this.addEventListener(message.target, message.data.tabId);
          return null;
        case "NFC:RemoveEventListener":
          this.removeEventListenerById(message.data.tabId);
          return null;
        case "NFC:RegisterPeerReadyTarget":
          this.registerPeerReadyTarget(message.target, message.data.appId);
          return null;
        case "NFC:UnregisterPeerReadyTarget":
          this.unregisterPeerReadyTarget(message.data.appId);
          return null;
        case "NFC:CheckP2PRegistration":
          this.checkP2PRegistration(message);
          return null;
        case "NFC:NotifyUserAcceptedP2P":
          this.notifyUserAcceptedP2P(message.data.appId);
          return null;
        case "NFC:NotifySendFileStatus":
          // Upon receiving the status of sendFile operation, send the response
          // to appropriate content process.
          this.notifySendFileStatus(message);
          return null;
        case "NFC:CallDefaultFoundHandler":
          this.callDefaultFoundHandler(message.data);
          return null;
        case "NFC:CallDefaultLostHandler":
          this.callDefaultLostHandler(message.data);
          return null;
        case "NFC:SendFile":
          // Chrome process is the arbitrator / mediator between
          // system app (content process) that issued nfc 'sendFile' operation
          // and system app that handles the system message :
          // 'nfc-manager-send-file'. System app subsequently handover's
          // the data to alternate carrier's (BT / WiFi) 'sendFile' interface.

          // Notify system app to initiate BT send file operation
          let sysMsg = new NfcSendFileSysMsg(message.data.requestId,
                                             message.data.sessionToken,
                                             message.data.blob);
          gSystemMessenger.broadcastMessage("nfc-manager-send-file",
                                            sysMsg);
          return null;
        default:
          return this.nfc.receiveMessage(message);
      }
    },

    /**
     * nsIObserver interface methods.
     */

    observe: function observe(subject, topic, data) {
      switch (topic) {
        case NFC.TOPIC_MOZSETTINGS_CHANGED:
          if ("wrappedJSObject" in subject) {
            subject = subject.wrappedJSObject;
          }
          if (subject) {
            this.nfc.handle(subject.key, subject.value);
          }
          break;
        case NFC.TOPIC_XPCOM_SHUTDOWN:
          this._shutdown();
          break;
      }
    },
  };
});

var SessionHelper = {
  tokenMap: {},

  registerSession: function registerSession(id, isP2P) {
    if (this.tokenMap[id]) {
      return this.tokenMap[id].token;
    }

    this.tokenMap[id] = {
      token: UUIDGenerator.generateUUID().toString(),
      isP2P: isP2P
    };

    return this.tokenMap[id].token;
  },

  unregisterSession: function unregisterSession(id) {
    if (this.tokenMap[id]) {
      delete this.tokenMap[id];
    }
  },

  getToken: function getToken(id) {
    return this.tokenMap[id] ? this.tokenMap[id].token : null;
  },

  getCurrentP2PToken: function getCurrentP2PToken() {
    for (let id in this.tokenMap) {
      if (this.tokenMap[id] && this.tokenMap[id].isP2P) {
        return this.tokenMap[id].token;
      }
    }
    return null;
  },

  getId: function getId(token) {
    for (let id in this.tokenMap) {
      if (this.tokenMap[id].token == token) {
        return id;
      }
    }

    return 0;
  },

  isP2PSession: function isP2PSession(id) {
    return (this.tokenMap[id] != null) && this.tokenMap[id].isP2P;
  }
};

function Nfc() {
  gMessageManager.init(this);

  this.targetsByRequestId = {};
}

Nfc.prototype = {

  classID:   NFC_CID,
  classInfo: XPCOMUtils.generateCI({classID: NFC_CID,
                                    classDescription: "Nfc",
                                    interfaces: [Ci.nsINfcService]}),

  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsINfcGonkEventListener]),

  rfState: NFC.NFC_RF_STATE_IDLE,

  nfcService: null,

  targetsByRequestId: null,

  // temporary variables while NFC initialization is pending
  pendingNfcService: null,
  pendingMessageQueue: [],

  /**
   * Start NFC service
   */
  startNfcService: function startNfcService() {
    debug("Starting Nfc Service");

    let nfcService =
      Cc["@mozilla.org/nfc/service;1"].getService(Ci.nsINfcService);
    if (!nfcService) {
      debug("No nfc service component available!");
      return false;
    }

    nfcService.start(this);
    this.pendingNfcService = nfcService;

    return true;
  },

  /**
   * Shutdown NFC service
   */
  shutdownNfcService : function shutdownNfcService() {
    debug("Shutting down Nfc Service");

    this.nfcService.shutdown();
    this.nfcService = null;
  },

  /**
   * Send arbitrary message to Nfc service.
   *
   * @param nfcMessageType
   *        A text message type.
   * @param message [optional]
   *        An optional message object to send.
   */
  sendToNfcService: function sendToNfcService(nfcMessageType, message) {
    message = message || {};
    message.type = nfcMessageType;
    this.nfcService.sendCommand(message);
  },

  sendFileStatusResponse: function sendFileStatusResponse(message) {
    let target = this.getTargetByRequestId(message.requestId);
    if (!target) {
      return;
    }

    target.sendAsyncMessage("NFC:NotifySendFileStatusResponse", message);
  },

  sendNfcResponse: function sendNfcResponse(message) {
    let target = this.getTargetByRequestId(message.requestId);
    if (!target) {
      return;
    }

    target.sendAsyncMessage(EventMsgTable[message.type], message);
  },

  getTargetByRequestId: function getTargetByRequestId(requestId) {
    let target = this.targetsByRequestId[requestId];
    if (!target) {
      debug("No target for requestId: " + requestId);
      return null;
    }
    delete this.targetsByRequestId[requestId];

    return target;
  },

  /**
   * Send Error response to content process.
   *
   * @param message
   *        An nsIMessageListener's message parameter.
   * @param errorMsg
   *        A string with an error message.
   */
  sendNfcErrorResponse: function sendNfcErrorResponse(message, errorMsg) {
    if (!message.target) {
      return;
    }

    let nfcMsgType = message.name + "Response";
    message.data.errorMsg = errorMsg;
    message.target.sendAsyncMessage(nfcMsgType, message.data);
  },

  getErrorMessage: function getErrorMessage(errorCode) {
    return NFC.NFC_ERROR_MSG[errorCode];
  },

  /**
   * Process the incoming message from the NFC Service.
   */
  onEvent: function onEvent(event) {
    let message = Cu.cloneInto(event, this);
    DEBUG && debug("Received message from NFC Service: " + JSON.stringify(message));

    message.type = message.rspType || message.ntfType;
    switch (message.type) {
      case NfcNotificationType.INITIALIZED:
        this.nfcService = this.pendingNfcService;
        // Send messages that have been queued up during initialization
        // TODO: Bug 1141007: send error responses if the message
        // indicates an error during initialization.
        while (this.pendingMessageQueue.length) {
          this.receiveMessage(this.pendingMessageQueue.shift());
        }
        this.pendingNfcService = null;
        break;
      case NfcNotificationType.TECH_DISCOVERED:
        // Update the upper layers with a session token (alias)
        message.sessionToken =
          SessionHelper.registerSession(message.sessionId, message.isP2P);
        // Do not expose the actual session to the content
        let sessionId = message.sessionId;
        delete message.sessionId;

        if (SessionHelper.isP2PSession(sessionId)) {
          gMessageManager.onPeerEvent(NFC.PEER_EVENT_FOUND, message.sessionToken);
        } else {
          gMessageManager.onTagFound(message);
        }
        break;
      case NfcNotificationType.TECH_LOST:
        // Update the upper layers with a session token (alias)
        message.sessionToken = SessionHelper.getToken(message.sessionId);
        if (SessionHelper.isP2PSession(message.sessionId)) {
          gMessageManager.onPeerEvent(NFC.PEER_EVENT_LOST, message.sessionToken);
        } else {
          gMessageManager.onTagLost(message.sessionToken);
        }

        SessionHelper.unregisterSession(message.sessionId);
        break;
      case NfcNotificationType.HCI_EVENT_TRANSACTION:
        this.notifyHCIEventTransaction(message);
        break;
      case NfcNotificationType.NDEF_RECEIVED:
        message.sessionToken = SessionHelper.getToken(message.sessionId);
        delete message.sessionId;
        message.isP2P = true;
        // TODO: Bug 1082493.
        // This event should be sent to the focus app, but before Bug 1082493
        // is landed we forward this to System app.
        gMessageManager.callDefaultFoundHandler(message);
        break;
      case NfcResponseType.CHANGE_RF_STATE_RSP:
        this.sendNfcResponse(message);

        if (!message.errorMsg) {
          this.rfState = message.rfState;
          gMessageManager.onRFStateChanged(this.rfState);
        }
        if (this.rfState == NFC.NFC_RF_STATE_IDLE) {
          this.shutdownNfcService();
        }
        break;
      case NfcResponseType.READ_NDEF_RSP: // Fall through.
      case NfcResponseType.WRITE_NDEF_RSP:
      case NfcResponseType.MAKE_READ_ONLY_RSP:
      case NfcResponseType.FORMAT_RSP:
      case NfcResponseType.TRANSCEIVE_RSP:
        this.sendNfcResponse(message);
        break;
      default:
        throw new Error("Don't know about this message type: " + message.type);
    }
  },

  // HCI Event Transaction
  notifyHCIEventTransaction: function notifyHCIEventTransaction(message) {
    delete message.type;
    /**
     * FIXME:
     * GSMA 6.0 7.4 UI Application triggering requirements
     * This specifies the need for the following parameters to be derived and
     * sent. One unclear spec is what the URI format "secure:0" refers to, given
     * SEName can be something like "SIM1" or "SIM2".
     *
     * 1) Mime-type - Secure Element application dependent
     * 2) URI,  of the format:  nfc://secure:0/<SEName>/<AID>
     *     - SEName reflects the originating SE. It must be compliant with
     *       SIMAlliance Open Mobile APIs
     *     - AID reflects the originating UICC applet identifier
     * 3) Data - Data payload of the transaction notification, if any.
     */
    gSystemMessenger.broadcastMessage("nfc-hci-event-transaction", message);
  },

  /**
   * Process a message from the gMessageManager.
   */
  receiveMessage: function receiveMessage(message) {
    // Return early if we don't need the NFC Service. We won't start
    // the NFC daemon here.
    switch (message.name) {
      case "NFC:QueryInfo":
        return {rfState: this.rfState};
      default:
        break;
    }

    // Start NFC Service if necessary. Messages are held in a
    // queue while initialization is being performed.
    if (!this.nfcService) {
      if ((message.name == "NFC:ChangeRFState") &&
          (message.data.rfState != "idle") &&
          !this.pendingNfcService) {
        this.startNfcService(); // error handled in next branch
      }
      if (this.pendingNfcService) {
        this.pendingMessageQueue.push(message);
      } else {
        this.sendNfcErrorResponse(message, "NotInitialize");
      }
      return;
    }

    // NFC Service is running and we have a message for it. This
    // is the case during normal operation.
    if (message.name != "NFC:ChangeRFState") {
      // Update the current sessionId before sending to the NFC service.
      message.data.sessionId = SessionHelper.getId(message.data.sessionToken);
    }

    let command = CommandMsgTable[message.name];
    if (!command) {
      debug("Unknown message: " + message.name);
      return null;
    }
    this.targetsByRequestId[message.data.requestId] = message.target;
    this.sendToNfcService(command, message.data);
    return null;
  },

  removeTarget: function removeTarget(target) {
    Object.keys(this.targetsByRequestId).forEach((requestId) => {
      if (this.targetsByRequestId[requestId] === target) {
        delete this.targetsByRequestId[requestId];
      }
    });
  },

  /**
   * nsISettingsServiceCallback
   */
  handle: function handle(name, result) {
    switch (name) {
      case NFC.SETTING_NFC_DEBUG:
        DEBUG = result;
        updateDebug();
        break;
    }
  },

  /**
   * nsIObserver interface methods.
   */
  observe: function(subject, topic, data) {
    if (topic != "profile-after-change") {
      debug("Should receive 'profile-after-change' only, received " + topic);
    }
  },

  shutdown: function shutdown() {
    // We shutdown before initialization has been completed. The
    // pending messages will receive an error response.
    while (this.pendingMessageQueue.length) {
      this.sendNfcErrorResponse(this.pendingMessageQueue.shift(), "NotInitialize");
    }
    if (this.nfcService) {
      this.shutdownNfcService();
    }
  }
};

function NfcTechDiscoveredSysMsg(sessionToken, isP2P, records) {
  this.sessionToken = sessionToken;
  this.isP2P = isP2P;
  this.records = records;
}
NfcTechDiscoveredSysMsg.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsINfcTechDiscoveredSysMsg]),

  sessionToken: null,
  isP2P: null,
  records: null
};

function NfcSendFileSysMsg(requestId, sessionToken, blob) {
  this.requestId = requestId;
  this.sessionToken = sessionToken;
  this.blob = blob;
}
NfcSendFileSysMsg.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsINfcSendFileSysMsg]),

  requestId: null,
  sessionToken: null,
  blob: null
};

if (NFC_ENABLED) {
  this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Nfc]);
}
