Merge SQ1A.220205.002

Bug: 213904741
Merged-In: I224eb0018f557ca82d092a2ca9b2f29f5e68b39b
Change-Id: I8935b917776490c90a9460485cb3c7267f94eb05
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 7fb1636..657c8df 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -3,4 +3,8 @@
 
 [Builtin Hooks]
 clang_format = true
+rustfmt = true
+
+[Builtin Hooks Options]
+rustfmt = --config-path=rustfmt.toml
 
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 0000000..617d425
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1,5 @@
+# Android Format Style
+
+edition = "2018"
+use_small_heuristics = "Max"
+newline_style = "Unix"
diff --git a/src/Android.bp b/src/Android.bp
index f691c4a..788c364 100644
--- a/src/Android.bp
+++ b/src/Android.bp
@@ -18,7 +18,6 @@
         "libcutils",
         "liblog",
         "libdl",
-        "libhardware",
         "libz",
         "libchrome",
         "libbase",
@@ -30,6 +29,9 @@
         "android.hardware.nfc@1.0",
         "android.hardware.nfc@1.1",
         "android.hardware.nfc@1.2",
+        // Add for AIDL
+        "android.hardware.nfc-V1-ndk",
+        "libbinder_ndk",
     ],
     static_libs: [
         "libnfcutils",
@@ -128,6 +130,7 @@
     srcs: [
         "nfc/nci/*.cc",
         "nfc/nfc/*.cc",
+        "adaptation/debug_lmrt.cc",
         "gki/common/*.cc",
         "gki/ulinux/*.cc",
         "fuzzers/*.cc",
@@ -185,3 +188,46 @@
         "fuzzers/llcp/*.cc",
     ],
 }
+
+genrule {
+    name: "NfcGeneratedPackets_rust",
+    tools: [
+        "bluetooth_packetgen",
+    ],
+    cmd: "$(location bluetooth_packetgen) --include=system/nfc/src --out=$(genDir) $(in) --rust",
+    srcs: [
+        "nci_packets.pdl",
+    ],
+    out: [
+        "nci_packets.rs",
+    ],
+}
+
+rust_library {
+    name: "libnfc_packets",
+    defaults: ["nfc_rust_defaults"],
+    crate_name: "nfc_packets",
+    srcs: ["rust/packets/lib.rs", ":NfcGeneratedPackets_rust"],
+    host_supported: true,
+    proc_macros: ["libnum_derive"],
+    rustlibs: [
+        "libbytes",
+        "libnum_traits",
+        "libthiserror",
+        "liblog_rust",
+    ],
+}
+
+rust_test_host {
+    name: "libnfc_packets_test",
+    defaults: ["nfc_rust_defaults"],
+    srcs: ["rust/packets/lib.rs", ":NfcGeneratedPackets_rust"],
+    test_suites: ["general-tests"],
+    proc_macros: ["libnum_derive"],
+    rustlibs: [
+        "libbytes",
+        "libnum_traits",
+        "libthiserror",
+        "liblog_rust",
+    ],
+}
diff --git a/src/adaptation/NfcAdaptation.cc b/src/adaptation/NfcAdaptation.cc
index 7c6a857..8524a17 100644
--- a/src/adaptation/NfcAdaptation.cc
+++ b/src/adaptation/NfcAdaptation.cc
@@ -15,6 +15,18 @@
  *  limitations under the License.
  *
  ******************************************************************************/
+#include <aidl/android/hardware/nfc/BnNfc.h>
+#include <aidl/android/hardware/nfc/BnNfcClientCallback.h>
+#include <aidl/android/hardware/nfc/INfc.h>
+#include <android/binder_ibinder.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+// syslog.h and base/logging.h both try to #define LOG_INFO and LOG_WARNING.
+// We need to #undef these two before including base/logging.h.
+// libchrome => logging.h
+// aidl => syslog.h
+#undef LOG_INFO
+#undef LOG_WARNING
 #include <android-base/stringprintf.h>
 #include <android/hardware/nfc/1.1/INfc.h>
 #include <android/hardware/nfc/1.2/INfc.h>
@@ -50,6 +62,18 @@
 using NfcVendorConfigV1_2 = android::hardware::nfc::V1_2::NfcConfig;
 using android::hardware::nfc::V1_1::INfcClientCallback;
 using android::hardware::hidl_vec;
+using INfcAidl = ::aidl::android::hardware::nfc::INfc;
+using NfcAidlConfig = ::aidl::android::hardware::nfc::NfcConfig;
+using AidlPresenceCheckAlgorithm =
+    ::aidl::android::hardware::nfc::PresenceCheckAlgorithm;
+using INfcAidlClientCallback =
+    ::aidl::android::hardware::nfc::INfcClientCallback;
+using NfcAidlEvent = ::aidl::android::hardware::nfc::NfcEvent;
+using NfcAidlStatus = ::aidl::android::hardware::nfc::NfcStatus;
+using ::aidl::android::hardware::nfc::NfcCloseType;
+using Status = ::ndk::ScopedAStatus;
+
+std::string NFC_AIDL_HAL_SERVICE_NAME = "android.hardware.nfc.INfc/default";
 
 extern bool nfc_debug_enabled;
 
@@ -59,14 +83,15 @@
 
 NfcAdaptation* NfcAdaptation::mpInstance = nullptr;
 ThreadMutex NfcAdaptation::sLock;
-tHAL_NFC_CBACK* NfcAdaptation::mHalCallback = nullptr;
-tHAL_NFC_DATA_CBACK* NfcAdaptation::mHalDataCallback = nullptr;
 ThreadCondVar NfcAdaptation::mHalOpenCompletedEvent;
 ThreadCondVar NfcAdaptation::mHalCloseCompletedEvent;
 sp<INfc> NfcAdaptation::mHal;
 sp<INfcV1_1> NfcAdaptation::mHal_1_1;
 sp<INfcV1_2> NfcAdaptation::mHal_1_2;
 INfcClientCallback* NfcAdaptation::mCallback;
+std::shared_ptr<INfcAidlClientCallback> mAidlCallback;
+::ndk::ScopedAIBinder_DeathRecipient mDeathRecipient;
+std::shared_ptr<INfcAidl> mAidlHal;
 
 bool nfc_debug_enabled = false;
 std::string nfc_storage_path;
@@ -82,21 +107,16 @@
 // Whitelist for hosts allowed to create a pipe
 // See ADM_CREATE_PIPE command in the ETSI test specification
 // ETSI TS 102 622, section 6.1.3.1
-static std::vector<uint8_t> host_whitelist;
+static std::vector<uint8_t> host_allowlist;
 
 namespace {
 void initializeGlobalDebugEnabledFlag() {
   nfc_debug_enabled =
       (NfcConfig::getUnsigned(NAME_NFC_DEBUG_ENABLED, 0) != 0) ? true : false;
 
-  char valueStr[PROPERTY_VALUE_MAX] = {0};
-  int len = property_get("nfc.debug_enabled", valueStr, "");
-  if (len > 0) {
-    // let Android property override .conf variable
-    unsigned debug_enabled = 0;
-    sscanf(valueStr, "%u", &debug_enabled);
-    nfc_debug_enabled = (debug_enabled == 0) ? false : true;
-  }
+  bool debug_enabled = property_get_bool("persist.nfc.debug_enabled", false);
+
+  nfc_debug_enabled = (nfc_debug_enabled || debug_enabled);
 
   DLOG_IF(INFO, nfc_debug_enabled)
       << StringPrintf("%s: level=%u", __func__, nfc_debug_enabled);
@@ -147,13 +167,88 @@
       const wp<::android::hidl::base::V1_0::IBase>& /* who */) {
     ALOGE(
         "NfcHalDeathRecipient::serviceDied - Nfc-Hal service died. Killing "
-        "NfcServie");
+        "NfcService");
     if (mNfcDeathHal) {
       mNfcDeathHal->unlinkToDeath(this);
     }
     mNfcDeathHal = NULL;
     abort();
   }
+  void finalize() {
+    if (mNfcDeathHal) {
+      mNfcDeathHal->unlinkToDeath(this);
+    } else {
+      DLOG_IF(INFO, nfc_debug_enabled)
+          << StringPrintf("%s: mNfcDeathHal is not set", __func__);
+    }
+
+    ALOGI("NfcHalDeathRecipient::destructor - NfcService");
+    mNfcDeathHal = NULL;
+  }
+};
+
+class NfcAidlClientCallback
+    : public ::aidl::android::hardware::nfc::BnNfcClientCallback {
+ public:
+  NfcAidlClientCallback(tHAL_NFC_CBACK* eventCallback,
+                        tHAL_NFC_DATA_CBACK dataCallback) {
+    mEventCallback = eventCallback;
+    mDataCallback = dataCallback;
+  };
+  virtual ~NfcAidlClientCallback() = default;
+
+  ::ndk::ScopedAStatus sendEvent(NfcAidlEvent event,
+                                 NfcAidlStatus event_status) override {
+    uint8_t e_num;
+    uint8_t s_num;
+    switch (event) {
+      case NfcAidlEvent::OPEN_CPLT:
+        e_num = HAL_NFC_OPEN_CPLT_EVT;
+        break;
+      case NfcAidlEvent::CLOSE_CPLT:
+        e_num = HAL_NFC_CLOSE_CPLT_EVT;
+        break;
+      case NfcAidlEvent::POST_INIT_CPLT:
+        e_num = HAL_NFC_POST_INIT_CPLT_EVT;
+        break;
+      case NfcAidlEvent::PRE_DISCOVER_CPLT:
+        e_num = HAL_NFC_PRE_DISCOVER_CPLT_EVT;
+        break;
+      case NfcAidlEvent::HCI_NETWORK_RESET:
+        e_num = HAL_HCI_NETWORK_RESET;
+        break;
+      case NfcAidlEvent::ERROR:
+      default:
+        e_num = HAL_NFC_ERROR_EVT;
+    }
+    switch (event_status) {
+      case NfcAidlStatus::OK:
+        s_num = HAL_NFC_STATUS_OK;
+        break;
+      case NfcAidlStatus::FAILED:
+        s_num = HAL_NFC_STATUS_FAILED;
+        break;
+      case NfcAidlStatus::ERR_TRANSPORT:
+        s_num = HAL_NFC_STATUS_ERR_TRANSPORT;
+        break;
+      case NfcAidlStatus::ERR_CMD_TIMEOUT:
+        s_num = HAL_NFC_STATUS_ERR_CMD_TIMEOUT;
+        break;
+      default:
+        s_num = HAL_NFC_STATUS_FAILED;
+    }
+    mEventCallback(e_num, (tHAL_NFC_STATUS)s_num);
+    return ::ndk::ScopedAStatus::ok();
+  };
+  ::ndk::ScopedAStatus sendData(const std::vector<uint8_t>& data) override {
+    std::vector<uint8_t> copy = data;
+    mDataCallback(copy.size(), &copy[0]);
+    return ::ndk::ScopedAStatus::ok();
+  };
+
+ private:
+  tHAL_NFC_CBACK* mEventCallback;
+  tHAL_NFC_DATA_CBACK* mDataCallback;
 };
 
 /*******************************************************************************
@@ -166,8 +261,9 @@
 **
 *******************************************************************************/
 NfcAdaptation::NfcAdaptation() {
-  mNfcHalDeathRecipient = new NfcHalDeathRecipient(mHal);
   memset(&mHalEntryFuncs, 0, sizeof(mHalEntryFuncs));
+  mDeathRecipient = ::ndk::ScopedAIBinder_DeathRecipient(
+      AIBinder_DeathRecipient_new(NfcAdaptation::HalAidlBinderDied));
 }
 
 /*******************************************************************************
@@ -203,7 +299,10 @@
 void NfcAdaptation::GetVendorConfigs(
     std::map<std::string, ConfigValue>& configMap) {
   NfcVendorConfigV1_2 configValue;
-  if (mHal_1_2) {
+  NfcAidlConfig aidlConfigValue;
+  if (mAidlHal) {
+    mAidlHal->getConfig(&aidlConfigValue);
+  } else if (mHal_1_2) {
     mHal_1_2->getConfig_1_2(
         [&configValue](NfcVendorConfigV1_2 config) { configValue = config; });
   } else if (mHal_1_1) {
@@ -213,7 +312,61 @@
     });
   }
 
-  if (mHal_1_1 || mHal_1_2) {
+  if (mAidlHal) {
+    std::vector<int8_t> nfaPropCfg = {
+        aidlConfigValue.nfaProprietaryCfg.protocol18092Active,
+        aidlConfigValue.nfaProprietaryCfg.protocolBPrime,
+        aidlConfigValue.nfaProprietaryCfg.protocolDual,
+        aidlConfigValue.nfaProprietaryCfg.protocol15693,
+        aidlConfigValue.nfaProprietaryCfg.protocolKovio,
+        aidlConfigValue.nfaProprietaryCfg.protocolMifare,
+        aidlConfigValue.nfaProprietaryCfg.discoveryPollKovio,
+        aidlConfigValue.nfaProprietaryCfg.discoveryPollBPrime,
+        aidlConfigValue.nfaProprietaryCfg.discoveryListenBPrime};
+    configMap.emplace(NAME_NFA_PROPRIETARY_CFG, ConfigValue(nfaPropCfg));
+    configMap.emplace(NAME_NFA_POLL_BAIL_OUT_MODE,
+                      ConfigValue(aidlConfigValue.nfaPollBailOutMode ? 1 : 0));
+    configMap.emplace(NAME_DEFAULT_OFFHOST_ROUTE,
+                      ConfigValue(aidlConfigValue.defaultOffHostRoute));
+    if (configValue.offHostRouteUicc.size() != 0) {
+      configMap.emplace(NAME_OFFHOST_ROUTE_UICC,
+                        ConfigValue(aidlConfigValue.offHostRouteUicc));
+    }
+    if (configValue.offHostRouteEse.size() != 0) {
+      configMap.emplace(NAME_OFFHOST_ROUTE_ESE,
+                        ConfigValue(aidlConfigValue.offHostRouteEse));
+    }
+    configMap.emplace(NAME_DEFAULT_ROUTE,
+                      ConfigValue(aidlConfigValue.defaultRoute));
+    configMap.emplace(NAME_DEFAULT_NFCF_ROUTE,
+                      ConfigValue(aidlConfigValue.defaultOffHostRouteFelica));
+    configMap.emplace(NAME_DEFAULT_ISODEP_ROUTE,
+                      ConfigValue(aidlConfigValue.defaultIsoDepRoute));
+    configMap.emplace(NAME_DEFAULT_SYS_CODE_ROUTE,
+                      ConfigValue(aidlConfigValue.defaultSystemCodeRoute));
+    configMap.emplace(NAME_DEFAULT_SYS_CODE_PWR_STATE,
+                      ConfigValue(aidlConfigValue.defaultSystemCodePowerState));
+    configMap.emplace(NAME_OFF_HOST_SIM_PIPE_ID,
+                      ConfigValue(aidlConfigValue.offHostSIMPipeId));
+    configMap.emplace(NAME_OFF_HOST_ESE_PIPE_ID,
+                      ConfigValue(aidlConfigValue.offHostESEPipeId));
+    configMap.emplace(NAME_ISO_DEP_MAX_TRANSCEIVE,
+                      ConfigValue(aidlConfigValue.maxIsoDepTransceiveLength));
+    if (aidlConfigValue.hostAllowlist.size() != 0) {
+      configMap.emplace(NAME_DEVICE_HOST_ALLOW_LIST,
+                        ConfigValue(aidlConfigValue.hostAllowlist));
+    }
+    /* For Backwards compatibility */
+    if (aidlConfigValue.presenceCheckAlgorithm ==
+        AidlPresenceCheckAlgorithm::ISO_DEP_NAK) {
+      configMap.emplace(NAME_PRESENCE_CHECK_ALGORITHM,
+                        ConfigValue((uint32_t)NFA_RW_PRES_CHK_ISO_DEP_NAK));
+    } else {
+      configMap.emplace(
+          NAME_PRESENCE_CHECK_ALGORITHM,
+          ConfigValue((uint32_t)aidlConfigValue.presenceCheckAlgorithm));
+    }
+  } else if (mHal_1_1 || mHal_1_2) {
     std::vector<uint8_t> nfaPropCfg = {
         configValue.v1_1.nfaProprietaryCfg.protocol18092Active,
         configValue.v1_1.nfaProprietaryCfg.protocolBPrime,
@@ -255,7 +408,7 @@
     configMap.emplace(NAME_ISO_DEP_MAX_TRANSCEIVE,
                       ConfigValue(configValue.v1_1.maxIsoDepTransceiveLength));
     if (configValue.v1_1.hostWhitelist.size() != 0) {
-      configMap.emplace(NAME_DEVICE_HOST_WHITE_LIST,
+      configMap.emplace(NAME_DEVICE_HOST_ALLOW_LIST,
                         ConfigValue(configValue.v1_1.hostWhitelist));
     }
     /* For Backwards compatibility */
@@ -300,9 +453,9 @@
     if (dm_config.size() > 1) nfa_dm_cfg.auto_read_ndef = dm_config[1];
     if (dm_config.size() > 2) nfa_dm_cfg.auto_presence_check = dm_config[2];
     if (dm_config.size() > 3) nfa_dm_cfg.presence_check_option = dm_config[3];
-    // NOTE: The timeout value is not configurable here because the endianess
+    // NOTE: The timeout value is not configurable here because the endianness
     // of a byte array is ambiguous and needlessly difficult to configure.
-    // If this value needs to be configgurable, a numeric config option should
+    // If this value needs to be configurable, a numeric config option should
     // be used.
   }
 
@@ -343,12 +496,12 @@
       nfa_proprietary_cfg.pro_discovery_b_prime_listen = p_config[8];
   }
 
-  // Configure whitelist of HCI host ID's
+  // Configure allowlist of HCI host ID's
   // See specification: ETSI TS 102 622, section 6.1.3.1
-  if (NfcConfig::hasKey(NAME_DEVICE_HOST_WHITE_LIST)) {
-    host_whitelist = NfcConfig::getBytes(NAME_DEVICE_HOST_WHITE_LIST);
-    nfa_hci_cfg.num_whitelist_host = host_whitelist.size();
-    nfa_hci_cfg.p_whitelist = &host_whitelist[0];
+  if (NfcConfig::hasKey(NAME_DEVICE_HOST_ALLOW_LIST)) {
+    host_allowlist = NfcConfig::getBytes(NAME_DEVICE_HOST_ALLOW_LIST);
+    nfa_hci_cfg.num_allowlist_host = host_allowlist.size();
+    nfa_hci_cfg.p_allowlist = &host_allowlist[0];
   }
 
   verify_stack_non_volatile_store();
@@ -393,12 +546,17 @@
 
   NfcConfig::clear();
 
+  if (mHal != nullptr) {
+    mNfcHalDeathRecipient->finalize();
+  }
   DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf("%s: exit", func);
   delete this;
 }
 
 void NfcAdaptation::FactoryReset() {
-  if (mHal_1_2 != nullptr) {
+  if (mAidlHal != nullptr) {
+    mAidlHal->factoryReset();
+  } else if (mHal_1_2 != nullptr) {
     mHal_1_2->factoryReset();
   } else if (mHal_1_1 != nullptr) {
     mHal_1_1->factoryReset();
@@ -406,13 +564,19 @@
 }
 
 void NfcAdaptation::DeviceShutdown() {
-  if (mHal_1_2 != nullptr) {
-    mHal_1_2->closeForPowerOffCase();
-  } else if (mHal_1_1 != nullptr) {
-    mHal_1_1->closeForPowerOffCase();
-  }
-  if (mHal) {
-    mHal->unlinkToDeath(mNfcHalDeathRecipient);
+  if (mAidlHal != nullptr) {
+    mAidlHal->close(NfcCloseType::HOST_SWITCHED_OFF);
+    AIBinder_unlinkToDeath(mAidlHal->asBinder().get(), mDeathRecipient.get(),
+                           this);
+  } else {
+    if (mHal_1_2 != nullptr) {
+      mHal_1_2->closeForPowerOffCase();
+    } else if (mHal_1_1 != nullptr) {
+      mHal_1_1->closeForPowerOffCase();
+    }
+    if (mHal != nullptr) {
+      mHal->unlinkToDeath(mNfcHalDeathRecipient);
+    }
   }
 }
 
@@ -498,7 +662,7 @@
 **
 ** Function:    NfcAdaptation::InitializeHalDeviceContext
 **
-** Description: Ask the generic Android HAL to find the Broadcom-specific HAL.
+** Description: Check validity of current handle to the nfc HAL service
 **
 ** Returns:     None.
 **
@@ -517,6 +681,7 @@
   mHalEntryFuncs.power_cycle = HalPowerCycle;
   mHalEntryFuncs.get_max_ee = HalGetMaxNfcee;
   LOG(INFO) << StringPrintf("%s: INfc::getService()", func);
+  // TODO: Try get the NFC HIDL first before vendor AIDL impl complete
   mHal = mHal_1_1 = mHal_1_2 = INfcV1_2::getService();
   if (mHal_1_2 == nullptr) {
     mHal = mHal_1_1 = INfcV1_1::getService();
@@ -524,11 +689,23 @@
       mHal = INfc::getService();
     }
   }
-  LOG_FATAL_IF(mHal == nullptr, "Failed to retrieve the NFC HAL!");
-  LOG(INFO) << StringPrintf("%s: INfc::getService() returned %p (%s)", func,
-                            mHal.get(),
-                            (mHal->isRemote() ? "remote" : "local"));
-  if (mHal) {
+  if (mHal == nullptr) {
+    // Try get AIDL
+    ::ndk::SpAIBinder binder(
+        AServiceManager_getService(NFC_AIDL_HAL_SERVICE_NAME.c_str()));
+    mAidlHal = INfcAidl::fromBinder(binder);
+    if (mAidlHal != nullptr) {
+      AIBinder_linkToDeath(mAidlHal->asBinder().get(), mDeathRecipient.get(),
+                           this /* cookie */);
+      mHal = mHal_1_1 = mHal_1_2 = nullptr;
+      LOG(INFO) << StringPrintf("%s: INfcAidl::fromBinder returned", func);
+    }
+    LOG_FATAL_IF(mAidlHal == nullptr, "Failed to retrieve the NFC AIDL!");
+  } else {
+    LOG(INFO) << StringPrintf("%s: INfc::getService() returned %p (%s)", func,
+                              mHal.get(),
+                              (mHal->isRemote() ? "remote" : "local"));
+    mNfcHalDeathRecipient = new NfcHalDeathRecipient(mHal);
     mHal->linkToDeath(mNfcHalDeathRecipient, 0);
   }
 }
@@ -576,10 +753,22 @@
                             tHAL_NFC_DATA_CBACK* p_data_cback) {
   const char* func = "NfcAdaptation::HalOpen";
   DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf("%s", func);
-  mCallback = new NfcClientCallback(p_hal_cback, p_data_cback);
-  if (mHal_1_1 != nullptr) {
+
+  if (mAidlHal != nullptr) {
+    mAidlCallback = ::ndk::SharedRefBase::make<NfcAidlClientCallback>(
+        p_hal_cback, p_data_cback);
+    Status status = mAidlHal->open(mAidlCallback);
+    if (!status.isOk()) {
+      LOG(ERROR) << "Open Error: "
+                 << ::aidl::android::hardware::nfc::toString(
+                        static_cast<NfcAidlStatus>(
+                            status.getServiceSpecificError()));
+    }
+  } else if (mHal_1_1 != nullptr) {
+    mCallback = new NfcClientCallback(p_hal_cback, p_data_cback);
     mHal_1_1->open_1_1(mCallback);
   } else {
+    mCallback = new NfcClientCallback(p_hal_cback, p_data_cback);
     mHal->open(mCallback);
   }
 }
@@ -596,42 +785,11 @@
 void NfcAdaptation::HalClose() {
   const char* func = "NfcAdaptation::HalClose";
   DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf("%s", func);
-  mHal->close();
-}
-
-/*******************************************************************************
-**
-** Function:    NfcAdaptation::HalDeviceContextCallback
-**
-** Description: Translate generic Android HAL's callback into Broadcom-specific
-**              callback function.
-**
-** Returns:     None.
-**
-*******************************************************************************/
-void NfcAdaptation::HalDeviceContextCallback(nfc_event_t event,
-                                             nfc_status_t event_status) {
-  const char* func = "NfcAdaptation::HalDeviceContextCallback";
-  DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf("%s: event=%u", func, event);
-  if (mHalCallback) mHalCallback(event, (tHAL_NFC_STATUS)event_status);
-}
-
-/*******************************************************************************
-**
-** Function:    NfcAdaptation::HalDeviceContextDataCallback
-**
-** Description: Translate generic Android HAL's callback into Broadcom-specific
-**              callback function.
-**
-** Returns:     None.
-**
-*******************************************************************************/
-void NfcAdaptation::HalDeviceContextDataCallback(uint16_t data_len,
-                                                 uint8_t* p_data) {
-  const char* func = "NfcAdaptation::HalDeviceContextDataCallback";
-  DLOG_IF(INFO, nfc_debug_enabled)
-      << StringPrintf("%s: len=%u", func, data_len);
-  if (mHalDataCallback) mHalDataCallback(data_len, p_data);
+  if (mAidlHal != nullptr) {
+    mAidlHal->close(NfcCloseType::DISABLE);
+  } else {
+    mHal->close();
+  }
 }
 
 /*******************************************************************************
@@ -646,9 +804,16 @@
 void NfcAdaptation::HalWrite(uint16_t data_len, uint8_t* p_data) {
   const char* func = "NfcAdaptation::HalWrite";
   DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf("%s", func);
-  ::android::hardware::nfc::V1_0::NfcData data;
-  data.setToExternal(p_data, data_len);
-  mHal->write(data);
+
+  if (mAidlHal != nullptr) {
+    int ret;
+    std::vector<uint8_t> aidl_data(p_data, p_data + data_len);
+    mAidlHal->write(aidl_data, &ret);
+  } else {
+    ::android::hardware::nfc::V1_0::NfcData data;
+    data.setToExternal(p_data, data_len);
+    mHal->write(data);
+  }
 }
 
 /*******************************************************************************
@@ -664,10 +829,14 @@
                                        uint8_t* p_core_init_rsp_params) {
   const char* func = "NfcAdaptation::HalCoreInitialized";
   DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf("%s", func);
-  hidl_vec<uint8_t> data;
-  data.setToExternal(p_core_init_rsp_params, data_len);
-
-  mHal->coreInitialized(data);
+  if (mAidlHal != nullptr) {
+    // AIDL coreInitialized doesn't send data to HAL.
+    mAidlHal->coreInitialized();
+  } else {
+    hidl_vec<uint8_t> data;
+    data.setToExternal(p_core_init_rsp_params, data_len);
+    mHal->coreInitialized(data);
+  }
 }
 
 /*******************************************************************************
@@ -687,9 +856,18 @@
 bool NfcAdaptation::HalPrediscover() {
   const char* func = "NfcAdaptation::HalPrediscover";
   DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf("%s", func);
-  bool retval = FALSE;
-  mHal->prediscover();
-  return retval;
+  if (mAidlHal != nullptr) {
+    Status status = mAidlHal->preDiscover();
+    if (status.isOk()) {
+      DLOG_IF(INFO, nfc_debug_enabled)
+          << StringPrintf("%s wait for NFC_PRE_DISCOVER_CPLT_EVT", func);
+      return true;
+    }
+  } else {
+    mHal->prediscover();
+  }
+
+  return false;
 }
 
 /*******************************************************************************
@@ -708,7 +886,11 @@
 void NfcAdaptation::HalControlGranted() {
   const char* func = "NfcAdaptation::HalControlGranted";
   DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf("%s", func);
-  mHal->controlGranted();
+  if (mAidlHal != nullptr) {
+    LOG(ERROR) << StringPrintf("Unsupported function %s", func);
+  } else {
+    mHal->controlGranted();
+  }
 }
 
 /*******************************************************************************
@@ -723,7 +905,11 @@
 void NfcAdaptation::HalPowerCycle() {
   const char* func = "NfcAdaptation::HalPowerCycle";
   DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf("%s", func);
-  mHal->powerCycle();
+  if (mAidlHal != nullptr) {
+    mAidlHal->powerCycle();
+  } else {
+    mHal->powerCycle();
+  }
 }
 
 /*******************************************************************************
@@ -818,6 +1004,31 @@
 
 /*******************************************************************************
 **
+** Function:    NfcAdaptation::HalAidlBinderDiedImpl
+**
+** Description: Abort nfc service when AIDL process died.
+**
+** Returns:     None.
+**
+*******************************************************************************/
+void NfcAdaptation::HalAidlBinderDiedImpl() {
+  LOG(WARNING) << __func__ << "INfc aidl hal died, resetting the state";
+  if (mAidlHal != nullptr) {
+    AIBinder_unlinkToDeath(mAidlHal->asBinder().get(), mDeathRecipient.get(),
+                           this);
+    mAidlHal = nullptr;
+  }
+  abort();
+}
+
+// static
+void NfcAdaptation::HalAidlBinderDied(void* cookie) {
+  auto thiz = static_cast<NfcAdaptation*>(cookie);
+  thiz->HalAidlBinderDiedImpl();
+}
+
+/*******************************************************************************
+**
 ** Function:    ThreadMutex::ThreadMutex()
 **
 ** Description: class constructor
diff --git a/src/adaptation/debug_lmrt.cc b/src/adaptation/debug_lmrt.cc
new file mode 100644
index 0000000..1ef3c7a
--- /dev/null
+++ b/src/adaptation/debug_lmrt.cc
@@ -0,0 +1,156 @@
+/**
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include "include/debug_lmrt.h"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+using android::base::StringPrintf;
+
+extern bool nfc_debug_enabled;
+
+/* The payload of each RF_SET_LISTEN_MODE_ROUTING_CMD when commit routing */
+lmrt_payload_t lmrt_payloads;
+
+/* The committed routing table stored in tlv form  */
+std::vector<uint8_t> committed_lmrt_tlvs(0);
+
+/*******************************************************************************
+**
+** Function         debug_lmrt_init
+**
+** Description      initialize the lmrt_payloads
+**
+** Returns          None
+**
+*******************************************************************************/
+void debug_lmrt_init(void) {
+  std::vector<uint8_t> empty_more(0);
+  std::vector<uint8_t> empty_entry_count(0);
+  std::vector<std::vector<uint8_t>> empty_tlvs(0);
+
+  lmrt_payloads.more.swap(empty_more);
+  lmrt_payloads.entry_count.swap(empty_entry_count);
+  lmrt_payloads.tlvs.swap(empty_tlvs);
+}
+
+/*******************************************************************************
+**
+** Function         lmrt_log
+**
+** Description      print the listen mode routing configuration for debug use
+**
+** Returns          None
+**
+*******************************************************************************/
+void lmrt_log(void) {
+  if (!nfc_debug_enabled) return;
+
+  static const char hexmap[] = {'0', '1', '2', '3', '4', '5', '6', '7',
+                                '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
+  for (int i = 0; i < lmrt_payloads.more.size(); i++) {
+    std::string tlvs_str;
+    for (uint8_t byte : lmrt_payloads.tlvs[i]) {
+      tlvs_str.push_back(hexmap[byte >> 4]);
+      tlvs_str.push_back(hexmap[byte & 0x0F]);
+    }
+
+    LOG(INFO) << StringPrintf("lmrt_log: Packet %d/%d, %d more packet", i + 1,
+                              (int)lmrt_payloads.more.size(),
+                              lmrt_payloads.more[i]);
+    LOG(INFO) << StringPrintf("lmrt_log: %d entries in this packet",
+                              lmrt_payloads.entry_count[i]);
+
+    LOG(INFO) << StringPrintf("lmrt_log: tlv: %s", tlvs_str.c_str());
+  }
+}
+
+/*******************************************************************************
+**
+** Function         lmrt_capture
+**
+** Description      record the last RF_SET_LISTEN_MODE_ROUTING_CMD
+**
+** Returns          None
+**
+*******************************************************************************/
+void lmrt_capture(uint8_t* buf, uint8_t buf_size) {
+  if (buf == nullptr || buf_size < 5) return;
+
+  if (lmrt_payloads.more.size() > 0) {
+    if (lmrt_payloads.more.back() == 0) {
+      /* if the MORE setting of the last lmrt command is 0x00,
+       * that means the data in lmrt_payloads are obsolete, empty it */
+      debug_lmrt_init();
+    }
+  }
+
+  /* push_back the last lmrt command to lmrt_payloads */
+  lmrt_payloads.more.push_back(buf[3]);
+  lmrt_payloads.entry_count.push_back(buf[4]);
+  if (buf_size == 5) {
+    lmrt_payloads.tlvs.push_back(std::vector<uint8_t>(0));
+  } else {
+    lmrt_payloads.tlvs.push_back(std::vector<uint8_t>(buf + 5, buf + buf_size));
+  }
+}
+
+/*******************************************************************************
+**
+** Function         lmrt_update
+**
+** Description      Update the committed tlvs to committed_lmrt_tlvs
+**
+** Returns          None
+**
+*******************************************************************************/
+void lmrt_update(void) {
+  lmrt_log();
+  std::vector<uint8_t> temp(0);
+
+  /* combine all tlvs in lmrt_payloads.tlvs */
+  for (auto tlv : lmrt_payloads.tlvs) {
+    temp.insert(temp.end(), tlv.begin(), tlv.end());
+  }
+
+  committed_lmrt_tlvs.swap(temp);
+}
+
+/*******************************************************************************
+**
+** Function         lmrt_get_max_size
+**
+** Description      This function is used to get the max size of the routing
+**                  table from cache
+**
+** Returns          Max Routing Table Size
+**
+*******************************************************************************/
+int lmrt_get_max_size(void) { return nfc_cb.max_ce_table; }
+
+/*******************************************************************************
+**
+** Function         lmrt_get_tlvs
+**
+** Description      This function is used to get the committed listen mode
+**                  routing configuration command
+**
+** Returns          The committed listen mode routing configuration command
+**
+*******************************************************************************/
+std::vector<uint8_t>* lmrt_get_tlvs() { return &committed_lmrt_tlvs; }
diff --git a/src/gki/ulinux/gki_ulinux.cc b/src/gki/ulinux/gki_ulinux.cc
index 7262b5f..8a7eea1 100644
--- a/src/gki/ulinux/gki_ulinux.cc
+++ b/src/gki/ulinux/gki_ulinux.cc
@@ -125,7 +125,7 @@
   pthread_mutexattr_init(&attr);
 
 #ifndef __CYGWIN__
-  pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP);
+  pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
 #endif
   p_os = &gki_cb.os;
   pthread_mutex_init(&p_os->GKI_mutex, &attr);
@@ -290,8 +290,6 @@
    * GKI_exception problem due to btu->hci sleep request events  */
   for (task_id = GKI_MAX_TASKS; task_id > 0; task_id--) {
     if (gki_cb.com.OSRdyTbl[task_id - 1] != TASK_DEAD) {
-      gki_cb.com.OSRdyTbl[task_id - 1] = TASK_DEAD;
-
       /* paranoi settings, make sure that we do not execute any mailbox events
        */
       gki_cb.com.OSWaitEvt[task_id - 1] &=
@@ -629,7 +627,7 @@
     if (gki_cb.com.OSTaskQFirst[rtask][3])
       gki_cb.com.OSWaitEvt[rtask] |= TASK_MBOX_3_EVT_MASK;
 
-    if (gki_cb.com.OSRdyTbl[rtask] == TASK_DEAD) {
+    if (gki_cb.com.OSWaitEvt[rtask] == EVENT_MASK(GKI_SHUTDOWN_EVT)) {
       gki_cb.com.OSWaitEvt[rtask] = 0;
       /* unlock thread_evt_mutex as pthread_cond_wait() does auto lock when cond
        * is met */
@@ -1073,6 +1071,11 @@
     return;
   }
   GKI_disable();
+  if (gki_cb.com.OSRdyTbl[task_id] == TASK_DEAD) {
+      GKI_enable();
+      LOG(WARNING) << StringPrintf("%s: task_id %d was already stopped.", __func__, task_id);
+      return;
+  }
   gki_cb.com.OSRdyTbl[task_id] = TASK_DEAD;
 
   /* Destroy mutex and condition variable objects */
diff --git a/src/include/NfcAdaptation.h b/src/include/NfcAdaptation.h
index df12a44..8bbb9c2 100644
--- a/src/include/NfcAdaptation.h
+++ b/src/include/NfcAdaptation.h
@@ -16,14 +16,14 @@
  *
  ******************************************************************************/
 #pragma once
+
 #include <pthread.h>
+#include <utils/RefBase.h>
 
 #include "config.h"
 #include "nfc_hal_api.h"
 #include "nfc_target.h"
 
-#include <utils/RefBase.h>
-
 using ::android::sp;
 
 namespace android {
@@ -138,4 +138,9 @@
                                           nfc_status_t event_status);
   static void HalDownloadFirmwareDataCallback(uint16_t data_len,
                                               uint8_t* p_data);
+
+  // Death recipient callback that is called when INfcAidl dies.
+  // The cookie is a pointer to a NfcAdaptation object.
+  static void HalAidlBinderDied(void* cookie);
+  void HalAidlBinderDiedImpl();
 };
diff --git a/src/include/debug_lmrt.h b/src/include/debug_lmrt.h
new file mode 100644
index 0000000..b050285
--- /dev/null
+++ b/src/include/debug_lmrt.h
@@ -0,0 +1,102 @@
+/**
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _DEBUG_LMRT_
+#define _DEBUG_LMRT_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "nfc_int.h"
+
+/* The type definition of a group of RF_SET_LISTEN_MODE_ROUTING_CMD(s) */
+typedef struct lmrt_payload_t {
+  std::vector<uint8_t> more;
+  std::vector<uint8_t> entry_count;
+  std::vector<std::vector<uint8_t>> tlvs;
+} __attribute__((__packed__)) lmrt_payload_t;
+
+/*******************************************************************************
+**
+** Function         debug_lmrt_init
+**
+** Description      initialize the lmrt_payloads
+**
+** Returns          None
+**
+*******************************************************************************/
+void debug_lmrt_init(void);
+
+/*******************************************************************************
+**
+** Function         lmrt_log
+**
+** Description      print the listen mode routing configuration for debug use
+**
+** Returns          None
+**
+*******************************************************************************/
+void lmrt_log(void);
+
+/*******************************************************************************
+**
+** Function         lmrt_capture
+**
+** Description      record the last RF_SET_LISTEN_MODE_ROUTING_CMD
+**
+** Returns          None
+**
+*******************************************************************************/
+void lmrt_capture(uint8_t* buf, uint8_t buf_size);
+
+/*******************************************************************************
+**
+** Function         lmrt_update
+**
+** Description      Update the committed tlvs to committed_lmrt_tlvs
+**
+** Returns          None
+**
+*******************************************************************************/
+void lmrt_update(void);
+
+/*******************************************************************************
+**
+** Function         lmrt_get_max_size
+**
+** Description      This function is used to get the max size of the routing
+**                  table from cache
+**
+** Returns          Max Routing Table Size
+**
+*******************************************************************************/
+int lmrt_get_max_size(void);
+
+/*******************************************************************************
+**
+** Function         lmrt_get_tlvs
+**
+** Description      This function is used to get the committed listen mode
+**                  routing configuration command
+**
+** Returns          The committed listen mode routing configuration command
+**
+*******************************************************************************/
+std::vector<uint8_t>* lmrt_get_tlvs();
+
+#endif /* _DEBUG_LMRT_ */
diff --git a/src/include/hardware_nfc.h b/src/include/hardware_nfc.h
new file mode 100644
index 0000000..b266541
--- /dev/null
+++ b/src/include/hardware_nfc.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * 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.
+ */
+#pragma once
+
+typedef uint8_t nfc_event_t;
+typedef uint8_t nfc_status_t;
+
+/*
+ * The callback passed in from the NFC stack that the HAL
+ * can use to pass events back to the stack.
+ */
+typedef void(nfc_stack_callback_t)(nfc_event_t event,
+                                   nfc_status_t event_status);
+
+/*
+ * The callback passed in from the NFC stack that the HAL
+ * can use to pass incomming data to the stack.
+ */
+typedef void(nfc_stack_data_callback_t)(uint16_t data_len, uint8_t* p_data);
+
+enum {
+  HAL_NFC_OPEN_CPLT_EVT = 0u,
+  HAL_NFC_CLOSE_CPLT_EVT = 1u,
+  HAL_NFC_POST_INIT_CPLT_EVT = 2u,
+  HAL_NFC_PRE_DISCOVER_CPLT_EVT = 3u,
+  HAL_NFC_REQUEST_CONTROL_EVT = 4u,
+  HAL_NFC_RELEASE_CONTROL_EVT = 5u,
+  HAL_NFC_ERROR_EVT = 6u,
+  HAL_HCI_NETWORK_RESET = 7u,
+};
+
+enum {
+  HAL_NFC_STATUS_OK = 0u,
+  HAL_NFC_STATUS_FAILED = 1u,
+  HAL_NFC_STATUS_ERR_TRANSPORT = 2u,
+  HAL_NFC_STATUS_ERR_CMD_TIMEOUT = 3u,
+  HAL_NFC_STATUS_REFUSED = 4u,
+};
diff --git a/src/include/nfc_config.h b/src/include/nfc_config.h
index aa7ac66..4731e97 100644
--- a/src/include/nfc_config.h
+++ b/src/include/nfc_config.h
@@ -57,7 +57,7 @@
 #define NAME_OFF_HOST_ESE_PIPE_ID "OFF_HOST_ESE_PIPE_ID"
 #define NAME_OFF_HOST_SIM_PIPE_ID "OFF_HOST_SIM_PIPE_ID"
 #define NAME_ISO_DEP_MAX_TRANSCEIVE "ISO_DEP_MAX_TRANSCEIVE"
-#define NAME_DEVICE_HOST_WHITE_LIST "DEVICE_HOST_WHITE_LIST"
+#define NAME_DEVICE_HOST_ALLOW_LIST "DEVICE_HOST_ALLOW_LIST"
 #define NAME_DEFAULT_ISODEP_ROUTE "DEFAULT_ISODEP_ROUTE"
 
 class NfcConfig {
diff --git a/src/include/nfc_hal_api.h b/src/include/nfc_hal_api.h
index 205c6f7..8934a63 100644
--- a/src/include/nfc_hal_api.h
+++ b/src/include/nfc_hal_api.h
@@ -23,8 +23,8 @@
  ******************************************************************************/
 #ifndef NFC_HAL_API_H
 #define NFC_HAL_API_H
-#include <hardware/nfc.h>
 #include "data_types.h"
+#include "hardware_nfc.h"
 #include "nfc_hal_target.h"
 
 typedef uint8_t tHAL_NFC_STATUS;
diff --git a/src/nci_packets.pdl b/src/nci_packets.pdl
new file mode 100644
index 0000000..6fea6e0
--- /dev/null
+++ b/src/nci_packets.pdl
@@ -0,0 +1,441 @@
+little_endian_packets
+
+enum PacketBoundaryFlag : 1 {
+  COMPLETE_OR_FINAL = 0,
+  INCOMPLETE = 1,
+}
+
+enum NciMsgType : 3 {
+  DATA = 0,
+  COMMAND = 1,
+  RESPONSE = 2,
+  NOTIFICATION = 3,
+}
+
+enum Opcode : 8 {
+  CORE_RESET = 0x0,
+  CORE_INIT = 0x1,
+  CORE_SET_CONFIG = 0x2,
+  CORE_GET_CONFIG = 0x3,
+  CORE_CONN_CREATE = 0x4,
+  CORE_CONN_CLOSE = 0x5,
+  CORE_CONN_CREDITS = 0x6,
+  CORE_GENERIC_ERROR = 0x7,
+  CORE_INTERFACE_ERROR = 0x8,
+  CORE_SET_POWER_SUBSTATE = 0x9,
+  RF_DISCOVER_MAP = 0x40,
+  RF_SET_LISTEN_MODE_ROUTING = 0x41,
+  RF_GET_LISTEN_MODE_ROUTING = 0x42,
+  RF_DISCOVER = 0x43,
+  RF_DISCOVER_SELECT = 0x44,
+  RF_INTF_ACTIVATED = 0x45,
+  RF_DIACTIVATE = 0x46,
+  RF_FIELD_INFO = 0x47,
+  RF_T3T_POLLING = 0x48,
+  RF_NFCEE_ACTION = 0x49,
+  RF_NFCEE_DISCOVERY_REQ = 0x4A,
+  RF_PARAMETER_UPDATE = 0x4B,
+  RF_INTF_EXT_START = 0x4C,
+  RF_INTF_EXT_STOP = 0x4D,
+  RF_EXT_AGG_ABORT = 0x4E,
+  RF_NDEF_ABORT = 0x4F,
+  RF_ISO_DEP_NAK_PRESENCE = 0x50,
+  RF_SET_FORCED_NFCEE_ROUTING_CMD = 0x51,
+}
+
+enum Status : 8 {
+  OK = 0x00,
+  REJECTED = 0x01,
+  FAILED = 0x03,
+  NOT_INITIALIZED = 0x04,
+  SYNTAX_ERROR = 0x05,
+  SEMANTIC_ERROR = 0x06,
+  INVALID_PARAM = 0x09,
+  MESSAGE_SIZE_EXCEEDED = 0x0A,
+  OK_1_BIT = 0x11,
+  OK_2_BIT = 0x12,
+  OK_3_BIT = 0x13,
+  OK_4_BIT = 0x14,
+  OK_5_BIT = 0x15,
+  OK_6_BIT = 0x16,
+  OK_7_BIT = 0x17,
+  DISCOVERY_ALREADY_STARTED = 0xA0,
+  DISCOVERY_TARGET_ACTIVATION_FAILED = 0xA1,
+  DISCOVERY_TEAR_DOWN = 0xA2,
+  RF_FRAME_CORRUPTED = 0x02,
+  RF_TRANSMISSION_EXCEPTION = 0xB0,
+  RF_PROTOCOL_EXCEPTION = 0xB1,
+  RF_TIMEOUT_EXCEPTION = 0xB2,
+  RF_UNEXPECTED_DATA = 0xB3,
+  NFCEE_INTERFACE_ACTIVATION_FAILED = 0xC0,
+  NFCEE_TRANSMISSION_ERROR = 0xC1,
+  NFCEE_PROTOCOL_ERROR = 0xC2,
+  NFCEE_TIMEOUT_ERROR = 0xC3,
+}
+
+packet Nci {
+  gid : 4,
+  pbf : PacketBoundaryFlag,
+  mt : NciMsgType,
+  _payload_,
+}
+
+packet Command : Nci (mt = COMMAND) {
+  op : Opcode,
+  _size_(_payload_) : 8,
+  _payload_,
+}
+
+packet Response : Nci (mt = RESPONSE) {
+  cmd_op : Opcode,
+  _size_(_payload_) : 8,
+  _payload_,
+}
+
+packet Notification : Nci (mt = NOTIFICATION) {
+  cmd_op : Opcode,
+  _size_(_payload_) : 8,
+  _payload_,
+}
+
+packet Data {
+  conn_id : 4,
+  pbf : PacketBoundaryFlag,
+  _fixed_ =  0x0 : 3,
+  cr : 8,
+  _size_(_payload_) : 8,
+  _payload_,
+}
+
+enum ResetType : 8 {
+  KEEP_CONFIG = 0,
+  RESET_CONFIG = 1,
+}
+
+packet ResetCommand : Command (op = CORE_RESET) {
+  reset_type: ResetType,
+}
+
+test ResetCommand {
+  "\x20\x00\x01\x01",
+}
+
+packet ResetResponse : Response (cmd_op = CORE_RESET) {
+  status: Status,
+}
+
+test ResetResponse {
+  "\x40\x00\x01\x00",
+}
+
+enum ResetTrigger : 8 {
+  UNRECOVERABLE_ERROR = 0,
+  POWER_ON = 1,
+  RESET_COMMAND = 2,
+}
+
+enum NciVersion : 8 {
+  VERSION_1_0 = 0x10,
+  VERSION_1_1 = 0x11,
+  VERSION_2_0 = 0x20,
+}
+
+enum ConfigStatus : 8 {
+  CONFIG_KEPT = 0x00,
+  CONFIG_RESET = 0x01,
+}
+
+packet ResetNotification : Notification (cmd_op = CORE_RESET) {
+  trigger : ResetTrigger,
+  config_status : ConfigStatus,
+  nci_version : NciVersion,
+  manufacturer_id: 8,
+  _size_(mfsi) : 8,
+  mfsi : 8[],
+}
+
+test ResetNotification {
+  "\x60\x00\x1f\x02\x01\x20\x02\x1a\x05\x03\x03\x06\x88\x97\x01\x06\x00\x00\x44\x64\xd6\x00\x00\xec\x10\x00\x00\x00\x01\x00\x00\xee\xe7\x02",
+}
+
+enum FeatureEnable : 16 {
+  RFU = 0,
+}
+
+
+enum DiscConfMode : 2 {
+  DH_ONLY = 0x0,
+  DH_AND_EE = 0x1,
+}
+
+enum FeatureState : 1 {
+  DISABLED = 0,
+  AVAILABLE = 1,
+}
+
+struct PropCaps {
+  b7 : 1,
+  b6 : 1,
+  b5 : 1,
+  b4 : 1,
+  b3 : 1,
+  b2 : 1,
+  b1 : 1,
+  b0 : 1,
+}
+
+struct NfccFeatures {
+  disc_freq_conf : FeatureState,
+  disc_conf_mode : DiscConfMode,
+  hci_net_support : FeatureState,
+  active_comm_mode : FeatureState,
+  _reserved_ : 3,
+  _reserved_: 1,
+  tech_routing : FeatureState,
+  proto_routing : FeatureState,
+  aid_routing : FeatureState,
+  syc_code_routing : FeatureState,
+  apdu_pttn_routing : FeatureState,
+  forced_nfcee_routing : FeatureState,
+  _reserved_ : 1,
+  batt_off_st : FeatureState,
+  soff_st : FeatureState,
+  swon_subst : FeatureState,
+  rf_conf_soff: FeatureState,
+  _reserved_ : 4,
+  prop_caps: PropCaps,
+}
+
+enum Intf : 8 {
+  NFCEE_DIRECT_RF = 0x00,
+  FRAME_RF = 0x01,
+  ISO_DEP_RF = 0x02,
+  NFC_DEP_RF = 0x03,
+  NDEF_RF = 0x06,
+}
+
+enum Extns : 8 {
+  FR_AGREG_RF_EXT = 0x00,
+  LLCP_SYM_RF_EXT = 0x01,
+}
+
+struct ExtList {
+  ext : Extns,
+}
+
+struct RfInterface {
+  intf : 8,
+  _size_(extns) : 8,
+  extns : 8[],
+}
+
+packet InitCommand : Command (op = CORE_INIT) {
+  feature_enable : FeatureEnable,
+}
+
+test InitCommand {
+  "\x20\x01\x02\x00\x00",
+}
+
+packet InitResponse : Response (cmd_op = CORE_INIT) {
+  status : Status,
+  nfcc_features : NfccFeatures,
+  max_log_conns : 4, //TODO set max to 0x0E
+  _reserved_ : 4,
+  max_rout_tbls_size : 16,
+  max_ctrl_payload : 8,  //TODO 32 <= val <= 255
+  max_data_payload : 8,
+  num_of_credits : 8,
+  max_nfcv_rf_frame_sz : 16,
+  _count_(rf_interface) : 8,
+  rf_interface: RfInterface[],
+}
+
+test InitResponse {
+  "\x40\x01\x18\x00\x1a\x7e\x06\x00\x01\x00\x04\xff\xff\x00\x0c\x01\x05\x01\x00\x02\x00\x03\x00\x00\x00\x90\x00",
+}
+
+enum ParamIds : 8 {
+  TOTAL_DURATION = 0x00,
+  CON_DISCOVERY_PARAM = 0x02,
+  POWER_STATE = 0x03,
+  PA_BAIL_OUT = 0x08,
+  PA_DEVICES_LIMIT = 0x09,
+  PB_AFI = 0x10,
+  PB_BAIL_OUT = 0x11,
+  PB_ATTRIB_PARAM1 = 0x12,
+  PB_SENSB_REQ_PARAM = 0x13,
+  PB_DEVICES_LIMIT = 0x14,
+  PF_BIT_RATE = 0x18,
+  PF_BAIL_OUT = 0x19,
+  PF_DEVICES_LIMIT = 0x1A,
+  PI_B_H_INFO = 0x20,
+  PI_BIT_RATE = 0x21,
+  PN_NFC_DEP_PSL = 0x28,
+  PN_ATR_REQ_GEN_BYTES = 0x29,
+  PN_ATR_REQ_CONFIG = 0x2A,
+  PV_DEVICES_LIMIT = 0x2F,
+  LA_BIT_FRAME_SDD = 0x30,
+  LA_PLATFORM_CONFIG = 0x31,
+  LA_SEL_INFO = 0x32,
+  LA_NFCID1 = 0x33,
+  LB_SENSB_INFO = 0x38,
+  LB_NFCID0 = 0x39,
+  LB_APPLICATION_DATA = 0x3A,
+  LB_SFGI = 0x3B,
+  LB_FWI_ADC_FO = 0x3C,
+  LB_BIT_RATE = 0x3E,
+  LF_T3T_IDENTIFIERS_1 = 0x40,
+  LF_T3T_IDENTIFIERS_2 = 0x41,
+  LF_T3T_IDENTIFIERS_3 = 0x42,
+  LF_T3T_IDENTIFIERS_4 = 0x43,
+  LF_T3T_IDENTIFIERS_5 = 0x44,
+  LF_T3T_IDENTIFIERS_6 = 0x45,
+  LF_T3T_IDENTIFIERS_7 = 0x46,
+  LF_T3T_IDENTIFIERS_8 = 0x47,
+  LF_T3T_IDENTIFIERS_9 = 0x48,
+  LF_T3T_IDENTIFIERS_10 = 0x49,
+  LF_T3T_IDENTIFIERS_11 = 0x4A,
+  LF_T3T_IDENTIFIERS_12 = 0x4B,
+  LF_T3T_IDENTIFIERS_13 = 0x4C,
+  LF_T3T_IDENTIFIERS_14 = 0x4D,
+  LF_T3T_IDENTIFIERS_15 = 0x4E,
+  LF_T3T_IDENTIFIERS_16 = 0x4F,
+  LF_T3T_MAX = 0x52,
+  LF_T3T_FLAGS = 0x53,
+  LF_T3T_RD_ALLOWED = 0x55,
+  LF_PROTOCOL_TYPE = 0x50,
+  LI_A_RATS_TB1 = 0x58,
+  LI_A_HIST_BY = 0x59,
+  LI_B_H_INFO_RESP = 0x5A,
+  LI_A_BIT_RATE = 0x5B,
+  LI_A_RATS_TC1 = 0x5C,
+  LN_WT = 0x60,
+  LN_ATR_RES_GEN_BYTES = 0x61,
+  LN_ATR_RES_CONFIG = 0x62,
+  PACM_BIT_RATE = 0x68,
+  RF_FIELD_INFO = 0x80,
+  RF_NFCEE_ACTION = 0x81,
+  NFCDEP_OP = 0x82,
+  LLCP_VERSION = 0x83,
+  NFCC_CONFIG_CONTROL = 0x85,
+}
+
+struct ConfigParams {
+  paramid : ParamIds,
+  _size_(valm) : 8,
+  valm : 8[],
+}
+
+struct ParamList {
+  pids : ParamIds,
+}
+
+packet SetConfigCommand : Command (op = CORE_SET_CONFIG) {
+  _count_(params) : 8,
+  params : ConfigParams[],
+}
+
+packet SetConfigResponse : Response (cmd_op = CORE_SET_CONFIG) {
+  status : Status,
+  _count_(paramids) : 8,
+  paramids : ParamList[],
+}
+
+packet GetConfigCommand : Command (op = CORE_GET_CONFIG) {
+  _count_(paramids) : 8,
+  paramids : ParamList[],
+}
+
+packet GetConfigResponse : Response (cmd_op = CORE_GET_CONFIG) {
+  status : Status,
+  _count_(params) : 8,
+  params : ConfigParams[],
+}
+
+enum RfProtocols : 8 {
+  PROTOCOL_UNDETERMINED = 0x00,
+  PROTOCOL_T1T = 0x01,
+  PROTOCOL_T2T = 0x02,
+  PROTOCOL_T3T = 0x03,
+  PROTOCOL_ISO_DEP = 0x04,
+  PROTOCOL_NFC_DEP = 0x05,
+  PROTOCOL_T5T = 0x06,
+  PROTOCOL_NDEF = 0x07,
+}
+
+enum NfceeProtocols : 8 {
+  APDU = 0x00,
+  RFU = 0x01,
+  T3CS = 0x02,
+  TRANSPARENT = 0x04,
+}
+
+enum DestTypes : 8 {
+  RFU = 0x00,
+  NFCC_LPBK = 0x01,
+  REMOTE = 0x02,
+  NFCEE = 0x03,
+}
+
+enum DestParamTypes : 8 {
+  RF_DISC = 0x00,
+  NFCEE = 0x01,
+}
+
+struct RfDiscType {
+  id : 8,
+  proto: RfProtocols,
+}
+
+struct NfceeType {
+  id : 8,
+  proto : NfceeProtocols,
+}
+
+struct DestParams {
+  ptype : DestParamTypes,
+  _size_(valdsp) : 8,
+  valdsp : 8[],
+}
+
+packet ConnCreateCommand : Command (op = CORE_CONN_CREATE) {
+  dt : DestTypes,
+  _count_(destparams) : 8,
+  destparams : DestParams[],
+}
+
+packet ConnCreateResponse : Response (cmd_op = CORE_CONN_CREATE) {
+  status : Status,
+  mpps : 8,
+  ncreds : 8,
+  conn_id : 8,
+}
+
+packet ConnCloseCommand : Command (op = CORE_CONN_CLOSE) {
+  conn_id : 8,
+}
+
+packet ConnCloseResponse : Response (cmd_op = CORE_CONN_CLOSE) {
+  status : Status,
+}
+
+struct CreditsPerConn {
+  conn_id : 8,
+  ncredits : 8,
+}
+
+packet ConnCreditsNotification : Notification (cmd_op = CORE_CONN_CREDITS) {
+  _count_(conns) : 8,
+  conns : CreditsPerConn[],
+}
+
+packet GenericError : Notification (cmd_op = CORE_GENERIC_ERROR) {
+  status : Status,
+}
+
+packet InterfaceError : Notification (cmd_op = CORE_INTERFACE_ERROR) {
+  status : Status,
+  conn_id : 8,
+}
+
diff --git a/src/nfa/ee/nfa_ee_act.cc b/src/nfa/ee/nfa_ee_act.cc
index 8b637d6..94fc4db 100644
--- a/src/nfa/ee/nfa_ee_act.cc
+++ b/src/nfa/ee/nfa_ee_act.cc
@@ -21,20 +21,19 @@
  *  This file contains the action functions for NFA-EE
  *
  ******************************************************************************/
-#include <string.h>
-
 #include <android-base/stringprintf.h>
 #include <base/logging.h>
+#include <statslog.h>
+#include <string.h>
 
+#include "include/debug_lmrt.h"
+#include "metrics.h"
 #include "nfa_api.h"
 #include "nfa_dm_int.h"
 #include "nfa_ee_int.h"
 #include "nfa_hci_int.h"
 #include "nfc_int.h"
 
-#include <statslog.h>
-#include "metrics.h"
-
 using android::base::StringPrintf;
 
 extern bool nfc_debug_enabled;
@@ -646,8 +645,8 @@
                                     int* p_offset, int* p_entry) {
   int xx, yy, aid_len_offset, offset;
   tNFA_EE_ECB *p_ret = nullptr, *p_ecb;
-
-  p_ecb = &nfa_ee_cb.ecb[NFA_EE_CB_4_DH];
+  /* NFA_EE_CB_4_DH + Empty aid ECB */
+  p_ecb = &nfa_ee_cb.ecb[NFA_EE_CB_4_DH + 1];
   aid_len_offset = 1; /* skip the tag */
   for (yy = 0; yy <= nfa_ee_cb.cur_ee; yy++) {
     if (p_ecb->aid_entries) {
@@ -2251,7 +2250,9 @@
 
     if (nfa_ee_cb.ee_wait_evt & NFA_EE_WAIT_UPDATE) {
       nfa_ee_cb.ee_wait_evt &= ~NFA_EE_WAIT_UPDATE;
-      /* finished updating NFCC; report NFA_EE_UPDATED_EVT now */
+      /* finished updating NFCC; record the committed listen mode routing
+       * configuration command; report NFA_EE_UPDATED_EVT now */
+      lmrt_update();
       evt_data.status = NFA_STATUS_OK;
       nfa_ee_report_event(nullptr, NFA_EE_UPDATED_EVT, &evt_data);
     }
@@ -2859,6 +2860,16 @@
         << StringPrintf("%s --add the routing for DH!!", __func__);
     nfa_ee_route_add_one_ecb_by_route_order(&nfa_ee_cb.ecb[NFA_EE_CB_4_DH], rt,
                                             &max_len, more, p, &cur_offset);
+
+    if (rt == NCI_ROUTE_ORDER_AID) {
+      p_cb = &nfa_ee_cb.ecb[NFA_EE_EMPTY_AID_ECB];
+      if (p_cb->ee_status == NFC_NFCEE_STATUS_ACTIVE) {
+        DLOG_IF(INFO, nfc_debug_enabled)
+            << StringPrintf("%s --add the routing for Empty Aid!!", __func__);
+        nfa_ee_route_add_one_ecb_by_route_order(p_cb, rt, &max_len, more, p,
+                                                &cur_offset);
+      }
+    }
   }
 
   GKI_freebuf(p);
diff --git a/src/nfa/ee/nfa_ee_api.cc b/src/nfa/ee/nfa_ee_api.cc
index 4881261..428ac21 100644
--- a/src/nfa/ee/nfa_ee_api.cc
+++ b/src/nfa/ee/nfa_ee_api.cc
@@ -127,6 +127,9 @@
     p_info->ee_status = p_cb->ee_status;
     p_info->num_interface = p_cb->num_interface;
     p_info->num_tlvs = p_cb->num_tlvs;
+    p_info->la_protocol = p_cb->la_protocol;
+    p_info->lb_protocol = p_cb->lb_protocol;
+    p_info->lf_protocol = p_cb->lf_protocol;
     memcpy(p_info->ee_interface, p_cb->ee_interface, p_cb->num_interface);
     memcpy(p_info->ee_tlv, p_cb->ee_tlv, p_cb->num_tlvs * sizeof(tNFA_EE_TLV));
     p_info->ee_power_supply_status = p_cb->ee_power_supply_status;
@@ -558,7 +561,11 @@
   tNFA_EE_ECB* p_cb;
 
   DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf("handle:<0x%x>", ee_handle);
-  p_cb = nfa_ee_find_ecb(nfcee_id);
+  if (aid_len == 0) {
+    p_cb = &nfa_ee_cb.ecb[NFA_EE_EMPTY_AID_ECB];
+  } else {
+    p_cb = nfa_ee_find_ecb(nfcee_id);
+  }
 
   /* validate parameters - make sure the AID is in valid length range */
   if ((p_cb == nullptr) ||
@@ -570,6 +577,7 @@
     LOG(ERROR) << StringPrintf("Bad ee_handle or AID (len=%d)", aid_len);
     status = NFA_STATUS_INVALID_PARAM;
   } else {
+    p_cb->nfcee_id = nfcee_id;
     p_msg = (tNFA_EE_API_ADD_AID*)GKI_getbuf(size);
     if (p_msg != nullptr) {
       if (p_aid != nullptr)
diff --git a/src/nfa/hci/nfa_hci_act.cc b/src/nfa/hci/nfa_hci_act.cc
index 70c519c..ae9c3b1 100644
--- a/src/nfa/hci/nfa_hci_act.cc
+++ b/src/nfa/hci/nfa_hci_act.cc
@@ -1356,7 +1356,7 @@
           /* Set WHITELIST */
           nfa_hciu_send_set_param_cmd(
               NFA_HCI_ADMIN_PIPE, NFA_HCI_WHITELIST_INDEX,
-              p_nfa_hci_cfg->num_whitelist_host, p_nfa_hci_cfg->p_whitelist);
+              p_nfa_hci_cfg->num_allowlist_host, p_nfa_hci_cfg->p_allowlist);
         } else if (nfa_hci_cb.param_in_use == NFA_HCI_WHITELIST_INDEX) {
           if ((nfa_hci_cb.hci_state == NFA_HCI_STATE_STARTUP) ||
               (nfa_hci_cb.hci_state == NFA_HCI_STATE_RESTORE))
@@ -1408,7 +1408,7 @@
             /* Session has not changed, Set WHITELIST */
             nfa_hciu_send_set_param_cmd(
                 NFA_HCI_ADMIN_PIPE, NFA_HCI_WHITELIST_INDEX,
-                p_nfa_hci_cfg->num_whitelist_host, p_nfa_hci_cfg->p_whitelist);
+                p_nfa_hci_cfg->num_allowlist_host, p_nfa_hci_cfg->p_allowlist);
           } else {
             /* Something wrong, NVRAM data could be corrupt or first start with
              * default session id */
diff --git a/src/nfa/include/nfa_api.h b/src/nfa/include/nfa_api.h
index d89c6f3..b94f45e 100755
--- a/src/nfa/include/nfa_api.h
+++ b/src/nfa/include/nfa_api.h
@@ -573,10 +573,10 @@
   uint16_t hci_netwk_enable_timeout;
   /* Maximum time to wait for EE DISC REQ NTF(s) after HOT PLUG EVT(s) */
   uint16_t hcp_response_timeout;
-  /* Number of host in the whitelist of Terminal host */
-  uint8_t num_whitelist_host;
-  /* Whitelist of Terminal Host */
-  uint8_t* p_whitelist;
+  /* Number of host in the allowlist of Terminal host */
+  uint8_t num_allowlist_host;
+  /* Allowlist of Terminal Host */
+  uint8_t* p_allowlist;
 } tNFA_HCI_CFG;
 
 /*
diff --git a/src/nfa/include/nfa_ee_api.h b/src/nfa/include/nfa_ee_api.h
index 30a0867..ec9b20a 100644
--- a/src/nfa/include/nfa_ee_api.h
+++ b/src/nfa/include/nfa_ee_api.h
@@ -119,6 +119,9 @@
   uint8_t num_tlvs;                       /* number of TLVs           */
   tNFA_EE_TLV ee_tlv[NFC_MAX_EE_TLVS];    /* the TLV                  */
   uint8_t ee_power_supply_status;         /* The NFCEE Power supply */
+  tNFA_NFC_PROTOCOL la_protocol;          /* Listen A protocol    */
+  tNFA_NFC_PROTOCOL lb_protocol;          /* Listen B protocol    */
+  tNFA_NFC_PROTOCOL lf_protocol;          /* Listen F protocol    */
 } tNFA_EE_INFO;
 
 typedef struct {
diff --git a/src/nfa/include/nfa_ee_int.h b/src/nfa/include/nfa_ee_int.h
index cf1781a..b280562 100644
--- a/src/nfa/include/nfa_ee_int.h
+++ b/src/nfa/include/nfa_ee_int.h
@@ -30,10 +30,12 @@
 /*****************************************************************************
 **  Constants and data types
 *****************************************************************************/
-/* the number of tNFA_EE_ECBs (for NFCEEs and DH) */
-#define NFA_EE_NUM_ECBS (NFA_EE_MAX_EE_SUPPORTED + 1)
+/* the number of tNFA_EE_ECBs (for NFCEEs and DH) + Empty aid ECB */
+#define NFA_EE_NUM_ECBS (NFA_EE_MAX_EE_SUPPORTED + 2)
 /* The index for DH in nfa_ee_cb.ee_cb[] */
 #define NFA_EE_CB_4_DH NFA_EE_MAX_EE_SUPPORTED
+/* The index for Empty aid in nfa_ee_cb.ee_cb[] */
+#define NFA_EE_EMPTY_AID_ECB (NFA_EE_CB_4_DH + 1)
 #define NFA_EE_INVALID 0xFF
 /* only A, B, F, Bprime are supported by UICC now */
 #define NFA_EE_MAX_TECH_ROUTE 4
diff --git a/src/nfc/include/nfc_int.h b/src/nfc/include/nfc_int.h
index f357965..5ecc879 100644
--- a/src/nfc/include/nfc_int.h
+++ b/src/nfc/include/nfc_int.h
@@ -199,6 +199,8 @@
   bool reassembly; /* Reassemble fragmented data pkt */
   uint8_t last_hdr[NFC_SAVED_HDR_SIZE]; /* part of last NCI command header */
   uint8_t last_cmd[NFC_SAVED_CMD_SIZE]; /* part of last NCI command payload */
+  uint8_t
+      last_nfcee_cmd[NFC_SAVED_CMD_SIZE]; /* part of last NCI command payload */
   void* p_vsc_cback;       /* the callback function for last VSC command */
   BUFFER_Q nci_cmd_xmit_q; /* NCI command queue */
   TIMER_LIST_ENT
diff --git a/src/nfc/nci/nci_hmsgs.cc b/src/nfc/nci/nci_hmsgs.cc
index 885ebaa..ed7caaa 100644
--- a/src/nfc/nci/nci_hmsgs.cc
+++ b/src/nfc/nci/nci_hmsgs.cc
@@ -22,13 +22,15 @@
  *  commands (for DH).
  *
  ******************************************************************************/
-#include <string.h>
-#include "nfc_target.h"
-
-#include "nci_defs.h"
 #include "nci_hmsgs.h"
+
+#include <string.h>
+
+#include "include/debug_lmrt.h"
+#include "nci_defs.h"
 #include "nfc_api.h"
 #include "nfc_int.h"
+#include "nfc_target.h"
 
 /*******************************************************************************
 **
@@ -655,6 +657,10 @@
     UINT8_TO_STREAM(pp, num_tlv);
     ARRAY_TO_STREAM(pp, p_param_tlvs, tlv_size);
   }
+
+  uint8_t* lmrt_head_ptr = (uint8_t*)(p + 1) + p->offset;
+  lmrt_capture(lmrt_head_ptr, size + 3);
+
   nfc_ncif_send_cmd(p);
 
   return (NCI_STATUS_OK);
diff --git a/src/nfc/nci/nci_hrcv.cc b/src/nfc/nci/nci_hrcv.cc
index 50a6297..7be4c21 100644
--- a/src/nfc/nci/nci_hrcv.cc
+++ b/src/nfc/nci/nci_hrcv.cc
@@ -333,7 +333,7 @@
   tNFC_RESPONSE_CBACK* p_cback = nfc_cb.p_resp_cback;
   tNFC_RESPONSE nfc_response;
   tNFC_RESPONSE_EVT event = NFC_NFCEE_INFO_REVT;
-  uint8_t* p_old = nfc_cb.last_cmd;
+  uint8_t* p_old = nfc_cb.last_nfcee_cmd;
 
   /* find the start of the NCI message and parse the NCI header */
   p = (uint8_t*)(p_msg + 1) + p_msg->offset;
@@ -412,7 +412,7 @@
   tNFC_RESPONSE_CBACK* p_cback = nfc_cb.p_resp_cback;
   tNFC_RESPONSE nfc_response;
   tNFC_RESPONSE_EVT event = NFC_NFCEE_INFO_REVT;
-  uint8_t* p_old = nfc_cb.last_cmd;
+  uint8_t* p_old = nfc_cb.last_nfcee_cmd;
   uint8_t xx;
   uint8_t yy;
   tNFC_NFCEE_TLV* p_tlv;
diff --git a/src/nfc/nfc/nfc_main.cc b/src/nfc/nfc/nfc_main.cc
index a03fa7a..1b25eeb 100644
--- a/src/nfc/nfc/nfc_main.cc
+++ b/src/nfc/nfc/nfc_main.cc
@@ -148,7 +148,7 @@
       return "HAL_NFC_RELEASE_CONTROL_EVT";
     case HAL_NFC_ERROR_EVT:
       return "HAL_NFC_ERROR_EVT";
-    case (uint32_t)NfcEvent::HCI_NETWORK_RESET:
+    case HAL_HCI_NETWORK_RESET:
       return "HCI_NETWORK_RESET";
     default:
       return "???? UNKNOWN EVENT";
@@ -526,7 +526,7 @@
           }
           break;
 
-        case (uint32_t)NfcEvent::HCI_NETWORK_RESET:
+        case HAL_HCI_NETWORK_RESET:
           delete_stack_non_volatile_store(true);
           break;
 
@@ -630,7 +630,7 @@
     case HAL_NFC_REQUEST_CONTROL_EVT:
     case HAL_NFC_RELEASE_CONTROL_EVT:
     case HAL_NFC_ERROR_EVT:
-    case (uint32_t)NfcEvent::HCI_NETWORK_RESET:
+    case HAL_HCI_NETWORK_RESET:
       nfc_main_post_hal_evt(event, status);
       break;
 
diff --git a/src/nfc/nfc/nfc_ncif.cc b/src/nfc/nfc/nfc_ncif.cc
index 1eb8d82..0fc0fbf 100644
--- a/src/nfc/nfc/nfc_ncif.cc
+++ b/src/nfc/nfc/nfc_ncif.cc
@@ -282,6 +282,11 @@
       ps = (uint8_t*)(p_buf + 1) + p_buf->offset;
       memcpy(nfc_cb.last_hdr, ps, NFC_SAVED_HDR_SIZE);
       memcpy(nfc_cb.last_cmd, ps + NCI_MSG_HDR_SIZE, NFC_SAVED_CMD_SIZE);
+      // Check first byte to check if this is an NFCEE command
+      if (*ps == ((NCI_MT_CMD << NCI_MT_SHIFT) | NCI_GID_EE_MANAGE)) {
+        memcpy(nfc_cb.last_nfcee_cmd, ps + NCI_MSG_HDR_SIZE,
+               NFC_SAVED_CMD_SIZE);
+      }
       if (p_buf->layer_specific == NFC_WAIT_RSP_VSC) {
         /* save the callback for NCI VSCs)  */
         nfc_cb.p_vsc_cback = (void*)((tNFC_NCI_VS_MSG*)p_buf)->p_cback;
@@ -2046,7 +2051,7 @@
   LOG(ERROR) << StringPrintf("%s", __func__);
   tNFC_RESPONSE nfc_response;
   nfc_response.mode_set.status = NCI_STATUS_FAILED;
-  nfc_response.mode_set.nfcee_id = *nfc_cb.last_cmd;
+  nfc_response.mode_set.nfcee_id = *nfc_cb.last_nfcee_cmd;
   nfc_response.mode_set.mode = NCI_NFCEE_MD_DEACTIVATE;
 
   tNFC_RESPONSE_CBACK* p_cback = nfc_cb.p_resp_cback;
diff --git a/src/nfc/tags/rw_i93.cc b/src/nfc/tags/rw_i93.cc
index 5bf9afc..d4391d2 100644
--- a/src/nfc/tags/rw_i93.cc
+++ b/src/nfc/tags/rw_i93.cc
@@ -2287,6 +2287,9 @@
 
     if (p_resp->len > 0) {
       (*(rw_cb.p_cback))(RW_I93_NDEF_READ_EVT, &rw_data);
+    } else {
+       // free buffer, if len == 0
+       GKI_freebuf(p_resp);
     }
 
     /* this will make read data from next block */
diff --git a/src/nfc/tags/rw_mfc.cc b/src/nfc/tags/rw_mfc.cc
index 649f333..8803f2d 100644
--- a/src/nfc/tags/rw_mfc.cc
+++ b/src/nfc/tags/rw_mfc.cc
@@ -694,7 +694,9 @@
   }
 
   if ((p_mfc->state != RW_MFC_STATE_IDLE) && (mfc_data == NULL)) {
-    LOG(ERROR) << StringPrintf("%s NULL pointer", __func__);
+    if (p_mfc->state != RW_MFC_STATE_NOT_ACTIVATED) {
+      LOG(ERROR) << StringPrintf("%s NULL pointer", __func__);
+    }
     return;
   }
 
diff --git a/src/rust/Android.bp b/src/rust/Android.bp
new file mode 100644
index 0000000..f91266d
--- /dev/null
+++ b/src/rust/Android.bp
@@ -0,0 +1,106 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "system_nfc_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["system_nfc_license"],
+}
+
+rust_defaults {
+    name: "nfc_rust_defaults",
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+    host_supported: true,
+}
+
+cc_defaults {
+    name: "nfc_ffi_defaults",
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+}
+
+rust_library {
+    name: "libnfc_rnci",
+    defaults: ["nfc_rust_defaults"],
+    crate_name: "nfc_rnci",
+    srcs: ["nci/nci.rs"],
+    host_supported: true,
+    rustlibs: [
+        "libnfc_packets",
+        "libnfc_hal",
+        "libtokio",
+        "libcxx",
+        "liblazy_static",
+        "liblog_rust",
+    ],
+    proc_macros: ["libnum_derive"],
+}
+
+rust_library {
+    name: "libnfc_hal",
+    defaults: ["nfc_rust_defaults"],
+    crate_name: "nfc_hal",
+    srcs: ["hal/hal.rs"],
+    host_supported: true,
+    rustlibs: [
+        "libnfc_packets",
+        "libbytes",
+        "libthiserror",
+        "libtokio",
+        "libcxx",
+        "liblazy_static",
+        "liblog_rust",
+    ],
+    proc_macros: ["libnum_derive"],
+    target: {
+        android: {
+                whole_static_libs: ["libnfc_hidl_hal_cxx"],
+                shared_libs: [
+                    "android.hardware.nfc@1.0",
+                    "android.hardware.nfc@1.1",
+                    "android.hardware.nfc@1.2",
+                    "libhidlbase",
+                    "libutils",
+                ],
+        },
+    },
+}
+
+genrule {
+    name: "libnfc_hidl_hal_bridge_header",
+    tools: ["cxxbridge"],
+    cmd: "$(location cxxbridge) $(in) --header > $(out)",
+    srcs: ["hal/hidl_hal.rs"],
+    out: ["hal/hidl_hal.rs.h"],
+}
+
+genrule {
+    name: "libnfc_hidl_hal_bridge_code",
+    tools: ["cxxbridge"],
+    cmd: "$(location cxxbridge) $(in) >> $(out)",
+    srcs: ["hal/hidl_hal.rs"],
+    out: ["hidl_hal_generated.cc"],
+}
+
+cc_library_static {
+    name: "libnfc_hidl_hal_cxx",
+    defaults: ["nfc_ffi_defaults"],
+    srcs: ["hal/ffi/hidl.cc"],
+    local_include_dirs: ["hal/ffi"],
+    generated_headers: ["libnfc_hidl_hal_bridge_header", "cxx-bridge-header"],
+    generated_sources: ["libnfc_hidl_hal_bridge_code"],
+    shared_libs: [
+        "android.hardware.nfc@1.0",
+        "android.hardware.nfc@1.1",
+        "android.hardware.nfc@1.2",
+        "libhidlbase",
+        "libutils",
+    ],
+}
diff --git a/src/rust/hal/ffi/hidl.cc b/src/rust/hal/ffi/hidl.cc
new file mode 100644
index 0000000..fb3b5b3
--- /dev/null
+++ b/src/rust/hal/ffi/hidl.cc
@@ -0,0 +1,115 @@
+#include "hal/ffi/hidl.h"
+
+#include <log/log.h>
+#include <stdlib.h>
+
+using ::android::wp;
+using ::android::hardware::hidl_death_recipient;
+using ::android::hidl::base::V1_0::IBase;
+
+using android::OK;
+using android::sp;
+using android::status_t;
+
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using android::hardware::nfc::V1_0::INfc;
+using INfcV1_1 = android::hardware::nfc::V1_1::INfc;
+using INfcV1_2 = android::hardware::nfc::V1_2::INfc;
+using android::hardware::nfc::V1_1::INfcClientCallback;
+
+namespace nfc {
+namespace hal {
+namespace {
+
+class NfcHalDeathRecipient : public hidl_death_recipient {
+ public:
+  virtual void serviceDied(
+      uint64_t /*cookie*/,
+      const android::wp<::android::hidl::base::V1_0::IBase>& /*who*/) {
+    LOG_FATAL("Nfc HAL service died!");
+    abort();
+  }
+};
+
+class NfcCallbackTrampoline : public INfcClientCallback {
+ public:
+  NfcCallbackTrampoline() {}
+
+  Return<void> sendEvent_1_1(
+      ::android::hardware::nfc::V1_1::NfcEvent event,
+      ::android::hardware::nfc::V1_0::NfcStatus event_status) override {
+    on_event(event, event_status);
+    return Void();
+  }
+  Return<void> sendEvent(
+      ::android::hardware::nfc::V1_0::NfcEvent event,
+      ::android::hardware::nfc::V1_0::NfcStatus event_status) override {
+    on_event((::android::hardware::nfc::V1_1::NfcEvent)event, event_status);
+    return Void();
+  }
+
+  Return<void> sendData(const ::android::hardware::nfc::V1_0::NfcData& data) {
+    on_data(rust::Slice(&data[0], data.size()));
+    return Void();
+  }
+};
+
+android::sp<NfcHalDeathRecipient> nfc_death_recipient_;
+android::sp<INfc> nci_;
+android::sp<INfcV1_1> nci_1_1_;
+android::sp<INfcV1_2> nci_1_2_;
+android::sp<NfcCallbackTrampoline> trampoline_;
+
+}  // namespace
+
+void start_hal() {
+  ALOG_ASSERT(nci_ != nullptr, "Stale value of the NCI port");
+
+  nci_ = nci_1_1_ = nci_1_2_ = INfcV1_2::getService();
+  if (nci_1_2_ == nullptr) {
+    nci_ = nci_1_1_ = INfcV1_1::getService();
+    if (nci_1_1_ == nullptr) {
+      nci_ = INfc::getService();
+    }
+  }
+  LOG_FATAL_IF(nci_ == nullptr, "Failed to retrieve the NFC HAL!");
+  ALOGI("%s: INfc::getService() returned %p (%s)", __func__, nci_.get(),
+        (nci_->isRemote() ? "remote" : "local"));
+  if (nci_) {
+    nfc_death_recipient_ = new NfcHalDeathRecipient();
+    auto death_link = nci_->linkToDeath(nfc_death_recipient_, 0);
+    ALOG_ASSERT(death_link.isOk(),
+                "Unable to set the death recipient for the Nfc HAL");
+  }
+
+  trampoline_ = new NfcCallbackTrampoline();
+  if (nci_1_1_ != nullptr) {
+    nci_1_1_->open_1_1(trampoline_);
+  } else {
+    nci_->open(trampoline_);
+  }
+}
+
+void stop_hal() {
+  ALOG_ASSERT(nci_ == nullptr, "The NCI communication was already closed");
+
+  auto death_unlink = nci_->unlinkToDeath(nfc_death_recipient_);
+  if (!death_unlink.isOk()) {
+    ALOGE("Error unlinking death recipient from the Bluetooth HAL");
+  }
+  nci_->close();
+  nci_ = nullptr;
+  nci_1_1_ = nullptr;
+  nci_1_2_ = nullptr;
+  trampoline_ = nullptr;
+}
+
+void send_command(rust::Slice<const uint8_t> data) {
+  ALOG_ASSERT(nci_ == nullptr, "The NCI communication was already closed");
+  nci_->write(hidl_vec<uint8_t>(data.data(), data.data() + data.length()));
+}
+
+}  // namespace hal
+}  // namespace nfc
diff --git a/src/rust/hal/ffi/hidl.h b/src/rust/hal/ffi/hidl.h
new file mode 100644
index 0000000..b4b7761
--- /dev/null
+++ b/src/rust/hal/ffi/hidl.h
@@ -0,0 +1,25 @@
+#pragma once
+#include <android/hardware/nfc/1.0/INfc.h>
+#include <android/hardware/nfc/1.1/INfc.h>
+#include <android/hardware/nfc/1.2/INfc.h>
+
+namespace nfc {
+namespace hal {
+
+using android::hardware::nfc::V1_0::NfcStatus;
+using android::hardware::nfc::V1_1::NfcEvent;
+
+}  // namespace hal
+}  // namespace nfc
+
+#include "hal/hidl_hal.rs.h"
+
+namespace nfc {
+namespace hal {
+
+void start_hal();
+void stop_hal();
+void send_command(rust::Slice<const uint8_t> data);
+
+}  // namespace hal
+}  // namespace nfc
diff --git a/src/rust/hal/hal.rs b/src/rust/hal/hal.rs
new file mode 100644
index 0000000..fd4ae32
--- /dev/null
+++ b/src/rust/hal/hal.rs
@@ -0,0 +1,146 @@
+// Copyright 2021, The Android Open Source Project
+//
+// 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.
+
+//! NCI Hardware Abstraction Layer
+//! Supports sending NCI commands to the HAL and receiving
+//! NCI events from the HAL
+
+use nfc_packets::nci::{DataPacket, NciPacket};
+use std::collections::HashMap;
+use std::sync::Arc;
+use thiserror::Error;
+use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
+use tokio::sync::{oneshot, Mutex};
+
+#[cfg(target_os = "android")]
+#[path = "hidl_hal.rs"]
+pub mod ihal;
+
+#[cfg(not(target_os = "android"))]
+#[path = "rootcanal_hal.rs"]
+pub mod ihal;
+
+/// HAL module interface
+pub struct Hal {
+    /// HAL events
+    pub hal_events: HalEventRegistry,
+    /// HAL outbound channel for Command messages
+    pub out_cmd_tx: UnboundedSender<NciPacket>,
+    /// HAL inbound channel for Response and Notification messages
+    pub in_cmd_rx: UnboundedReceiver<NciPacket>,
+    /// HAL outbound channel for Data messages
+    pub out_data_tx: UnboundedSender<DataPacket>,
+    /// HAL inbound channel for Data messages
+    pub in_data_rx: UnboundedReceiver<DataPacket>,
+}
+
+/// Initialize the module and connect the channels
+pub async fn init() -> Hal {
+    ihal::init().await
+}
+
+/// NFC HAL specific events
+#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)]
+pub enum HalEvent {
+    /// HAL CLOSE_CPLT event
+    CloseComplete,
+}
+
+/// Status of a NFC HAL event
+#[derive(Debug)]
+pub enum HalEventStatus {
+    /// HAL OK status
+    Success,
+    /// HAL FAILED status
+    Failed,
+    /// HAL ERR_TRANSPORT status
+    TransportError,
+    /// HAL ERR_CMD_TIMEOUT status
+    Timeout,
+    /// HAL REFUSED status
+    Refused,
+}
+
+/// Provides ability to register and unregister for HAL event notifications
+#[derive(Clone)]
+pub struct HalEventRegistry {
+    handlers: Arc<Mutex<HashMap<HalEvent, oneshot::Sender<HalEventStatus>>>>,
+}
+
+impl HalEventRegistry {
+    /// Indicate interest in specific HAL event
+    pub async fn register(&mut self, event: HalEvent, sender: oneshot::Sender<HalEventStatus>) {
+        assert!(
+            self.handlers.lock().await.insert(event, sender).is_none(),
+            "A handler for {:?} is already registered",
+            event
+        );
+    }
+
+    /// Remove interest in specific HAL event
+    pub async fn unregister(&mut self, event: HalEvent) -> Option<oneshot::Sender<HalEventStatus>> {
+        self.handlers.lock().await.remove(&event)
+    }
+}
+
+mod internal {
+    use crate::{Hal, HalEventRegistry};
+    use nfc_packets::nci::{DataPacket, NciPacket};
+    use std::collections::HashMap;
+    use std::sync::Arc;
+    use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
+    use tokio::sync::Mutex;
+
+    pub struct InnerHal {
+        pub out_cmd_rx: UnboundedReceiver<NciPacket>,
+        pub in_cmd_tx: UnboundedSender<NciPacket>,
+        pub out_data_rx: UnboundedReceiver<DataPacket>,
+        pub in_data_tx: UnboundedSender<DataPacket>,
+    }
+
+    impl InnerHal {
+        pub fn new() -> (Hal, Self) {
+            let (out_cmd_tx, out_cmd_rx) = unbounded_channel();
+            let (in_cmd_tx, in_cmd_rx) = unbounded_channel();
+            let (out_data_tx, out_data_rx) = unbounded_channel();
+            let (in_data_tx, in_data_rx) = unbounded_channel();
+            let handlers = Arc::new(Mutex::new(HashMap::new()));
+            let hal_events = HalEventRegistry { handlers };
+            (
+                Hal { hal_events, out_cmd_tx, in_cmd_rx, out_data_tx, in_data_rx },
+                Self { out_cmd_rx, in_cmd_tx, out_data_rx, in_data_tx },
+            )
+        }
+    }
+}
+
+/// Is this NCI control stream or data response
+pub fn is_control_packet(data: &[u8]) -> bool {
+    // Check the MT bits
+    (data[0] >> 5) & 0x7 != 0
+}
+
+/// Result type
+type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
+
+/// Errors that can be encountered while dealing with the HAL
+#[derive(Error, Debug)]
+pub enum HalError {
+    /// Invalid rootcanal host error
+    #[error("Invalid rootcanal host")]
+    InvalidAddressError,
+    /// Error while connecting to rootcanal
+    #[error("Connection to rootcanal failed: {0}")]
+    RootcanalConnectError(#[from] tokio::io::Error),
+}
diff --git a/src/rust/hal/hidl_hal.rs b/src/rust/hal/hidl_hal.rs
new file mode 100644
index 0000000..91e67ef
--- /dev/null
+++ b/src/rust/hal/hidl_hal.rs
@@ -0,0 +1,174 @@
+// Copyright 2021, The Android Open Source Project
+//
+// 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.
+
+//! Implementation of the HAl that talks to NFC controller over Android's HIDL
+use crate::internal::InnerHal;
+#[allow(unused)]
+use crate::{is_control_packet, Hal, HalEvent, HalEventRegistry, HalEventStatus, Result};
+use lazy_static::lazy_static;
+use log::{debug, error};
+use nfc_packets::nci::{DataPacket, NciPacket, Packet};
+use std::sync::Mutex;
+use tokio::select;
+use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
+use tokio::sync::oneshot;
+
+/// Initialize the module
+pub async fn init() -> Hal {
+    let (raw_hal, inner_hal) = InnerHal::new();
+    let (hal_open_evt_tx, hal_open_evt_rx) = oneshot::channel::<ffi::NfcStatus>();
+    let (hal_close_evt_tx, hal_close_evt_rx) = oneshot::channel::<ffi::NfcStatus>();
+    *CALLBACKS.lock().unwrap() = Some(Callbacks {
+        hal_open_evt_tx: Some(hal_open_evt_tx),
+        hal_close_evt_tx: Some(hal_close_evt_tx),
+        in_cmd_tx: inner_hal.in_cmd_tx,
+        in_data_tx: inner_hal.in_data_tx,
+    });
+    ffi::start_hal();
+    hal_open_evt_rx.await.unwrap();
+
+    tokio::spawn(dispatch_outgoing(
+        raw_hal.hal_events.clone(),
+        inner_hal.out_cmd_rx,
+        inner_hal.out_data_rx,
+        hal_close_evt_rx,
+    ));
+
+    raw_hal
+}
+
+#[cxx::bridge(namespace = nfc::hal)]
+// TODO Either use or remove these functions, this shouldn't be the long term state
+#[allow(dead_code)]
+mod ffi {
+
+    #[repr(u32)]
+    #[derive(Debug)]
+    enum NfcEvent {
+        OPEN_CPLT = 0,
+        CLOSE_CPLT = 1,
+        POST_INIT_CPLT = 2,
+        PRE_DISCOVER_CPLT = 3,
+        REQUEST_CONTROL = 4,
+        RELEASE_CONTROL = 5,
+        ERROR = 6,
+        HCI_NETWORK_RESET = 7,
+    }
+
+    #[repr(u32)]
+    #[derive(Debug)]
+    enum NfcStatus {
+        OK = 0,
+        FAILED = 1,
+        ERR_TRANSPORT = 2,
+        ERR_CMD_TIMEOUT = 3,
+        REFUSED = 4,
+    }
+
+    unsafe extern "C++" {
+        include!("hal/ffi/hidl.h");
+        fn start_hal();
+        fn stop_hal();
+        fn send_command(data: &[u8]);
+
+        #[namespace = "android::hardware::nfc::V1_1"]
+        type NfcEvent;
+
+        #[namespace = "android::hardware::nfc::V1_0"]
+        type NfcStatus;
+    }
+
+    extern "Rust" {
+        fn on_event(evt: NfcEvent, status: NfcStatus);
+        fn on_data(data: &[u8]);
+    }
+}
+
+impl From<ffi::NfcStatus> for HalEventStatus {
+    fn from(ffi_nfc_status: ffi::NfcStatus) -> Self {
+        match ffi_nfc_status {
+            ffi::NfcStatus::OK => HalEventStatus::Success,
+            ffi::NfcStatus::FAILED => HalEventStatus::Failed,
+            ffi::NfcStatus::ERR_TRANSPORT => HalEventStatus::TransportError,
+            ffi::NfcStatus::ERR_CMD_TIMEOUT => HalEventStatus::Timeout,
+            ffi::NfcStatus::REFUSED => HalEventStatus::Refused,
+            _ => HalEventStatus::Failed,
+        }
+    }
+}
+
+struct Callbacks {
+    hal_open_evt_tx: Option<oneshot::Sender<ffi::NfcStatus>>,
+    hal_close_evt_tx: Option<oneshot::Sender<ffi::NfcStatus>>,
+    in_cmd_tx: UnboundedSender<NciPacket>,
+    in_data_tx: UnboundedSender<DataPacket>,
+}
+
+lazy_static! {
+    static ref CALLBACKS: Mutex<Option<Callbacks>> = Mutex::new(None);
+}
+
+fn on_event(evt: ffi::NfcEvent, status: ffi::NfcStatus) {
+    debug!("got event: {:?} with status {:?}", evt, status);
+    let mut callbacks = CALLBACKS.lock().unwrap();
+    match evt {
+        ffi::NfcEvent::OPEN_CPLT => {
+            if let Some(evt_tx) = callbacks.as_mut().unwrap().hal_open_evt_tx.take() {
+                evt_tx.send(status).unwrap();
+            }
+        }
+        ffi::NfcEvent::CLOSE_CPLT => {
+            if let Some(evt_tx) = callbacks.as_mut().unwrap().hal_close_evt_tx.take() {
+                evt_tx.send(status).unwrap();
+            }
+        }
+        _ => error!("Unhandled HAL event {:?}", evt),
+    }
+}
+
+fn on_data(data: &[u8]) {
+    debug!("got packet: {:02x?}", data);
+    let callbacks = CALLBACKS.lock().unwrap();
+    if is_control_packet(data) {
+        match NciPacket::parse(data) {
+            Ok(p) => callbacks.as_ref().unwrap().in_cmd_tx.send(p).unwrap(),
+            Err(e) => error!("failure to parse response: {:?} data: {:02x?}", e, data),
+        }
+    } else {
+        match DataPacket::parse(data) {
+            Ok(p) => callbacks.as_ref().unwrap().in_data_tx.send(p).unwrap(),
+            Err(e) => error!("failure to parse response: {:?} data: {:02x?}", e, data),
+        }
+    }
+}
+
+async fn dispatch_outgoing(
+    mut hal_events: HalEventRegistry,
+    mut out_cmd_rx: UnboundedReceiver<NciPacket>,
+    mut out_data_rx: UnboundedReceiver<DataPacket>,
+    hal_close_evt_rx: oneshot::Receiver<ffi::NfcStatus>,
+) {
+    loop {
+        select! {
+            Some(cmd) = out_cmd_rx.recv() => ffi::send_command(&cmd.to_bytes()),
+            Some(data) = out_data_rx.recv() => ffi::send_command(&data.to_bytes()),
+            else => break,
+        }
+    }
+    ffi::stop_hal();
+    let status = hal_close_evt_rx.await.unwrap();
+    if let Some(evt) = hal_events.unregister(HalEvent::CloseComplete).await {
+        evt.send(HalEventStatus::from(status)).unwrap();
+    }
+}
diff --git a/src/rust/hal/rootcanal_hal.rs b/src/rust/hal/rootcanal_hal.rs
new file mode 100644
index 0000000..b52154d
--- /dev/null
+++ b/src/rust/hal/rootcanal_hal.rs
@@ -0,0 +1,128 @@
+// Copyright 2021, The Android Open Source Project
+//
+// 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.
+
+//! Rootcanal HAL
+//! This connects to "rootcanal" which provides a simulated
+//! Nfc chip as well as a simulated environment.
+
+use crate::internal::InnerHal;
+use crate::{is_control_packet, Hal, HalEvent, HalEventRegistry, HalEventStatus, Result};
+use bytes::{BufMut, BytesMut};
+use log::{debug, error};
+use nfc_packets::nci::{DataPacket, NciPacket, Packet};
+use std::convert::TryInto;
+use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader};
+use tokio::net::TcpStream;
+use tokio::select;
+use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
+
+/// Initialize the module
+pub async fn init() -> Hal {
+    let (raw_hal, inner_hal) = InnerHal::new();
+    let (reader, writer) = TcpStream::connect("127.0.0.1:54323")
+        .await
+        .expect("unable to create stream to rootcanal")
+        .into_split();
+
+    let reader = BufReader::new(reader);
+    tokio::spawn(dispatch_incoming(inner_hal.in_cmd_tx, inner_hal.in_data_tx, reader));
+    tokio::spawn(dispatch_outgoing(
+        raw_hal.hal_events.clone(),
+        inner_hal.out_cmd_rx,
+        inner_hal.out_data_rx,
+        writer,
+    ));
+
+    raw_hal
+}
+
+/// Send NCI events received from the HAL to the NCI layer
+async fn dispatch_incoming<R>(
+    in_cmd_tx: UnboundedSender<NciPacket>,
+    in_data_tx: UnboundedSender<DataPacket>,
+    mut reader: R,
+) -> Result<()>
+where
+    R: AsyncReadExt + Unpin,
+{
+    loop {
+        let mut buffer = BytesMut::with_capacity(1024);
+        let len: usize = reader.read_u16().await?.into();
+        buffer.resize(len, 0);
+        reader.read_exact(&mut buffer).await?;
+        let frozen = buffer.freeze();
+        debug!("{:?}", &frozen);
+        if is_control_packet(&frozen[..]) {
+            match NciPacket::parse(&frozen) {
+                Ok(p) => {
+                    if in_cmd_tx.send(p).is_err() {
+                        break;
+                    }
+                }
+                Err(e) => error!("dropping invalid cmd event packet: {}: {:02x}", e, frozen),
+            }
+        } else {
+            match DataPacket::parse(&frozen) {
+                Ok(p) => {
+                    if in_data_tx.send(p).is_err() {
+                        break;
+                    }
+                }
+                Err(e) => error!("dropping invalid data event packet: {}: {:02x}", e, frozen),
+            }
+        }
+    }
+    debug!("Dispatch incoming finished.");
+    Ok(())
+}
+
+/// Send commands received from the NCI later to rootcanal
+async fn dispatch_outgoing<W>(
+    mut hal_events: HalEventRegistry,
+    mut out_cmd_rx: UnboundedReceiver<NciPacket>,
+    mut out_data_rx: UnboundedReceiver<DataPacket>,
+    mut writer: W,
+) -> Result<()>
+where
+    W: AsyncWriteExt + Unpin,
+{
+    loop {
+        select! {
+            Some(cmd) = out_cmd_rx.recv() => write_nci(&mut writer, cmd).await?,
+            Some(data) = out_data_rx.recv() => write_nci(&mut writer, data).await?,
+            else => break,
+        }
+    }
+
+    writer.shutdown().await?;
+    if let Some(evt) = hal_events.unregister(HalEvent::CloseComplete).await {
+        evt.send(HalEventStatus::Success).unwrap();
+    }
+    debug!("Dispatch outgoing finished.");
+    Ok(())
+}
+
+async fn write_nci<W, P>(writer: &mut W, cmd: P) -> Result<()>
+where
+    W: AsyncWriteExt + Unpin,
+    P: Packet,
+{
+    let b = cmd.to_bytes();
+    let mut data = BytesMut::with_capacity(b.len() + 2);
+    data.put_u16(b.len().try_into().unwrap());
+    data.extend(b);
+    writer.write_all(&data[..]).await?;
+    debug!("Sent {:?}", data);
+    Ok(())
+}
diff --git a/src/rust/nci/api.rs b/src/rust/nci/api.rs
new file mode 100644
index 0000000..2bd4ae6
--- /dev/null
+++ b/src/rust/nci/api.rs
@@ -0,0 +1,137 @@
+// Copyright 2021, The Android Open Source Project
+//
+// 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.
+
+//! NCI API module
+
+use crate::{CommandSender, Result};
+use log::debug;
+use nfc_hal::{HalEvent, HalEventRegistry, HalEventStatus};
+use nfc_packets::nci::{FeatureEnable, PacketBoundaryFlag, ResetType};
+use nfc_packets::nci::{InitCommandBuilder, ResetCommandBuilder};
+use tokio::sync::oneshot;
+
+/// NCI API object to manage static API data
+pub struct NciApi {
+    /// Command Sender external interface
+    commands: Option<CommandSender>,
+    /// The NFC response callback
+    callback: Option<fn(u16, &[u8])>,
+    /// HalEventRegistry is used to register for HAL events
+    hal_events: Option<HalEventRegistry>,
+}
+
+impl NciApi {
+    /// NciApi constructor
+    pub fn new() -> NciApi {
+        NciApi { commands: None, callback: None, hal_events: None }
+    }
+
+    /** ****************************************************************************
+     **
+     ** Function         nfc_enable
+     **
+     ** Description      This function enables NFC. Prior to calling NFC_Enable:
+     **                  - the NFCC must be powered up, and ready to receive
+     **                    commands.
+     **
+     **                  This function opens the NCI transport (if applicable),
+     **                  resets the NFC controller, and initializes the NFC
+     **                  subsystems.
+     **
+     **                  When the NFC startup procedure is completed, an
+     **                  NFC_ENABLE_REVT is returned to the application using the
+     **                  tNFC_RESPONSE_CBACK.
+     **
+     ** Returns          tNFC_STATUS
+     **
+     *******************************************************************************/
+    /// extern tNFC_STATUS NFC_Enable(tNFC_RESPONSE_CBACK* p_cback);
+    pub async fn nfc_enable(&mut self, callback: fn(u16, &[u8])) {
+        let nci = crate::init().await;
+
+        self.commands = Some(nci.commands);
+        self.callback = Some(callback);
+        self.hal_events = Some(nci.hal_events);
+    }
+    /** ****************************************************************************
+     **
+     ** Function         NFC_Disable
+     **
+     ** Description      This function performs clean up routines for shutting down
+     **                  NFC and closes the NCI transport (if using dedicated NCI
+     **                  transport).
+     **
+     **                  When the NFC shutdown procedure is completed, an
+     **                  NFC_DISABLED_REVT is returned to the application using the
+     **                  tNFC_RESPONSE_CBACK.
+     **
+     ** Returns          nothing
+     **
+     *******************************************************************************/
+    // extern void NFC_Disable(void);
+    pub async fn nfc_disable(&mut self) {
+        let (tx, rx) = oneshot::channel::<HalEventStatus>();
+        if let Some(mut hr) = self.hal_events.take() {
+            hr.register(HalEvent::CloseComplete, tx).await;
+
+            if let Some(cmd) = self.commands.take() {
+                drop(cmd);
+            }
+            let status = rx.await.unwrap();
+            debug!("Shutdown complete {:?}.", status);
+
+            if let Some(cb) = self.callback.take() {
+                cb(1, &[]);
+            }
+        }
+    }
+
+    /** ****************************************************************************
+     **
+     ** Function         NFC_Init
+     **
+     ** Description      This function initializes control blocks for NFC
+     **
+     ** Returns          nothing
+     **
+     *******************************************************************************/
+    /// extern void NFC_Init(tHAL_NFC_ENTRY* p_hal_entry_tbl);
+    pub async fn nfc_init(&mut self) -> Result<()> {
+        let pbf = PacketBoundaryFlag::CompleteOrFinal;
+        if let Some(cmd) = self.commands.as_mut() {
+            let reset = cmd
+                .send_and_notify(
+                    ResetCommandBuilder { gid: 0, pbf, reset_type: ResetType::ResetConfig }
+                        .build()
+                        .into(),
+                )
+                .await?;
+            let _notification_packet = reset.notification.await?;
+            let _init = cmd
+                .send(
+                    InitCommandBuilder { gid: 0, pbf, feature_enable: FeatureEnable::Rfu }
+                        .build()
+                        .into(),
+                )
+                .await?;
+        }
+        Ok(())
+    }
+}
+
+impl Default for NciApi {
+    fn default() -> Self {
+        Self::new()
+    }
+}
diff --git a/src/rust/nci/nci.rs b/src/rust/nci/nci.rs
new file mode 100644
index 0000000..d0743f1
--- /dev/null
+++ b/src/rust/nci/nci.rs
@@ -0,0 +1,231 @@
+// Copyright 2021, The Android Open Source Project
+//
+// 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.
+
+//! NCI Protocol Abstraction Layer
+//! Supports sending NCI commands to the HAL and receiving
+//! NCI messages back
+
+use log::{debug, error};
+use nfc_hal::{Hal, HalEventRegistry};
+use nfc_packets::nci::NciChild::{Notification, Response};
+use nfc_packets::nci::{CommandPacket, DataPacket, NotificationPacket, Opcode, ResponsePacket};
+use std::collections::HashMap;
+use std::sync::Arc;
+use tokio::select;
+use tokio::sync::mpsc::{channel, Receiver, Sender};
+use tokio::sync::{oneshot, Mutex};
+use tokio::time::{sleep, Duration, Instant};
+
+pub mod api;
+
+/// Result type
+type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
+
+/// Initialize the module and connect the channels
+pub async fn init() -> Nci {
+    let hc = nfc_hal::init().await;
+    // Channel to handle data downstream messages
+    let (out_data_ext, out_data_int) = channel::<DataPacket>(10);
+    // Channel to handle data upstream messages
+    let (in_data_int, in_data_ext) = channel::<DataPacket>(10);
+    // Internal data channels
+    let ic = InternalChannels { out_data_int, in_data_int };
+
+    let (cmd_tx, cmd_rx) = channel::<QueuedCommand>(10);
+    let commands = CommandSender { cmd_tx };
+    let hal_events = hc.hal_events.clone();
+
+    let notifications = EventRegistry { handlers: Arc::new(Mutex::new(HashMap::new())) };
+
+    tokio::spawn(dispatch(notifications, hc, ic, cmd_rx));
+    Nci { hal_events, commands, out_data_ext, in_data_ext }
+}
+
+/// NCI module external interface
+pub struct Nci {
+    /// HAL events
+    pub hal_events: HalEventRegistry,
+    /// NCI command communication interface
+    pub commands: CommandSender,
+    /// NCI outbound channel for Data messages
+    pub out_data_ext: Sender<DataPacket>,
+    /// NCI inbound channel for Data messages
+    pub in_data_ext: Receiver<DataPacket>,
+}
+
+struct InternalChannels {
+    out_data_int: Receiver<DataPacket>,
+    in_data_int: Sender<DataPacket>,
+}
+
+#[derive(Debug)]
+struct PendingCommand {
+    cmd: CommandPacket,
+    response: oneshot::Sender<ResponsePacket>,
+}
+
+#[derive(Debug)]
+struct QueuedCommand {
+    pending: PendingCommand,
+    notification: Option<oneshot::Sender<NotificationPacket>>,
+}
+
+/// Sends raw commands. Only useful for facades & shims, or wrapped as a CommandSender.
+// #[derive(Clone)]
+pub struct CommandSender {
+    cmd_tx: Sender<QueuedCommand>,
+}
+
+/// The data returned by send_notify() method.
+pub struct ResponsePendingNotification {
+    /// Command response
+    pub response: ResponsePacket,
+    /// Pending notification receiver
+    pub notification: oneshot::Receiver<NotificationPacket>,
+}
+
+impl CommandSender {
+    /// Send a command, but do not expect notification to be returned
+    pub async fn send(&mut self, cmd: CommandPacket) -> Result<ResponsePacket> {
+        let (tx, rx) = oneshot::channel::<ResponsePacket>();
+        self.cmd_tx
+            .send(QueuedCommand {
+                pending: PendingCommand { cmd, response: tx },
+                notification: None,
+            })
+            .await?;
+        let event = rx.await?;
+        Ok(event)
+    }
+    /// Send a command which expects notification as a result
+    pub async fn send_and_notify(
+        &mut self,
+        cmd: CommandPacket,
+    ) -> Result<ResponsePendingNotification> {
+        let (tx, rx) = oneshot::channel::<ResponsePacket>();
+        let (ntx, nrx) = oneshot::channel::<NotificationPacket>();
+        self.cmd_tx
+            .send(QueuedCommand {
+                pending: PendingCommand { cmd, response: tx },
+                notification: Some(ntx),
+            })
+            .await?;
+        let event = rx.await?;
+        Ok(ResponsePendingNotification { response: event, notification: nrx })
+    }
+}
+
+impl Drop for CommandSender {
+    fn drop(&mut self) {
+        debug!("CommandSender is dropped");
+    }
+}
+
+/// Provides ability to register and unregister for NCI notifications
+#[derive(Clone)]
+pub struct EventRegistry {
+    handlers: Arc<Mutex<HashMap<Opcode, oneshot::Sender<NotificationPacket>>>>,
+}
+
+impl EventRegistry {
+    /// Indicate interest in specific NCI notification
+    pub async fn register(&mut self, code: Opcode, sender: oneshot::Sender<NotificationPacket>) {
+        assert!(
+            self.handlers.lock().await.insert(code, sender).is_none(),
+            "A handler for {:?} is already registered",
+            code
+        );
+    }
+
+    /// Remove interest in specific NCI notification
+    pub async fn unregister(
+        &mut self,
+        code: Opcode,
+    ) -> Option<oneshot::Sender<NotificationPacket>> {
+        self.handlers.lock().await.remove(&code)
+    }
+}
+
+async fn dispatch(
+    mut ntfs: EventRegistry,
+    mut hc: Hal,
+    mut ic: InternalChannels,
+    mut cmd_rx: Receiver<QueuedCommand>,
+) -> Result<()> {
+    let mut pending: Option<PendingCommand> = None;
+    let timeout = sleep(Duration::MAX);
+    // The max_deadline is used to set  the sleep() deadline to a very distant moment in
+    // the future, when the notification from the timer is not required.
+    let max_deadline = timeout.deadline();
+    tokio::pin!(timeout);
+    loop {
+        select! {
+            Some(cmd) = hc.in_cmd_rx.recv() => {
+                match cmd.specialize() {
+                    Response(rsp) => {
+                        timeout.as_mut().reset(max_deadline);
+                        let this_opcode = rsp.get_cmd_op();
+                        match pending.take() {
+                            Some(PendingCommand{cmd, response}) if cmd.get_op() == this_opcode => {
+                                if let Err(e) = response.send(rsp) {
+                                    error!("failure dispatching command status {:?}", e);
+                                }
+                            },
+                            Some(PendingCommand{cmd, ..}) => panic!("Waiting for {}, got {}", cmd.get_op(), this_opcode),
+                            None => panic!("Unexpected status event with opcode {}", this_opcode),
+                        }
+                    }
+                    Notification(ntfy) => {
+                        let code = ntfy.get_cmd_op();
+                        match ntfs.unregister(code).await {
+                            Some(sender) => {
+                                if let Err(e) = sender.send(ntfy) {
+                                    error!("notification channel closed {:?}", e);
+                                }
+                            },
+                            None => panic!("Unhandled notification {:?}", code),
+                        }
+                    }
+                    _ => error!("Unexpected NCI data received {:?}", cmd),
+                }
+            },
+            qc = cmd_rx.recv(), if pending.is_none() => if let Some(queued) = qc {
+                debug!("cmd_rx got a q");
+                if let Some(nsender) = queued.notification {
+                    ntfs.register(queued.pending.cmd.get_op(), nsender).await;
+                }
+                if let Err(e) = hc.out_cmd_tx.send(queued.pending.cmd.clone().into()) {
+                    error!("command queue closed: {:?}", e);
+                }
+                timeout.as_mut().reset(Instant::now() + Duration::from_millis(20));
+                pending = Some(queued.pending);
+            } else {
+                break;
+            },
+            () = &mut timeout => {
+                error!("Command processing timeout");
+                timeout.as_mut().reset(max_deadline);
+                pending = None;
+            },
+            Some(data) = hc.in_data_rx.recv() => ic.in_data_int.send(data).await.unwrap(),
+            Some(data) = ic.out_data_int.recv() => hc.out_data_tx.send(data).unwrap(),
+            else => {
+                debug!("Select is done");
+                break;
+            },
+        }
+    }
+    debug!("NCI dispatch is terminated.");
+    Ok(())
+}
diff --git a/src/rust/packets/lib.rs b/src/rust/packets/lib.rs
new file mode 100644
index 0000000..c459c75
--- /dev/null
+++ b/src/rust/packets/lib.rs
@@ -0,0 +1,9 @@
+//! reimport of generated packets (to go away once rust_genrule exists)
+
+#![allow(clippy::all)]
+#![allow(unused)]
+#![allow(missing_docs)]
+
+pub mod nci {
+    include!(concat!(env!("OUT_DIR"), "/nci_packets.rs"));
+}
diff --git a/src/rust/rootcanal/Android.bp b/src/rust/rootcanal/Android.bp
new file mode 100644
index 0000000..8673273
--- /dev/null
+++ b/src/rust/rootcanal/Android.bp
@@ -0,0 +1,23 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "system_nfc_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["system_nfc_license"],
+}
+
+rust_binary {
+    name: "nfc_rootcanal",
+    defaults: ["nfc_rust_defaults"],
+    srcs: ["main.rs"],
+    rustlibs: [
+        "libnfc_packets",
+        "libbytes",
+        "libthiserror",
+        "liblogger",
+        "liblog_rust",
+        "libtokio",
+    ],
+    proc_macros: ["libnum_derive"],
+}
diff --git a/src/rust/rootcanal/main.rs b/src/rust/rootcanal/main.rs
new file mode 100644
index 0000000..e7d20a4
--- /dev/null
+++ b/src/rust/rootcanal/main.rs
@@ -0,0 +1,185 @@
+// Copyright 2021, The Android Open Source Project
+//
+// 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.
+
+//! This connects to "rootcanal" and provides a simulated
+//! Nfc chip as well as a simulated environment.
+
+use bytes::{BufMut, BytesMut};
+use log::{debug, Level};
+use logger::{self, Config};
+use nfc_packets::nci;
+use nfc_packets::nci::{CommandChild, NciChild};
+use nfc_packets::nci::{
+    ConfigStatus, NciVersion, ResetNotificationBuilder, ResetResponseBuilder, ResetTrigger,
+    ResetType,
+};
+use nfc_packets::nci::{InitResponseBuilder, NfccFeatures, RfInterface};
+use nfc_packets::nci::{NciMsgType, NciPacket, Packet, PacketBoundaryFlag};
+use std::convert::TryInto;
+use thiserror::Error;
+use tokio::io;
+use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader, ErrorKind};
+use tokio::net::TcpListener;
+
+/// Result type
+type Result<T> = std::result::Result<T, RootcanalError>;
+
+#[derive(Debug, Error)]
+enum RootcanalError {
+    #[error("Termination request")]
+    TerminateTask,
+    #[error("Socket error")]
+    IoError(#[from] io::Error),
+    #[error("Unsupported command packet")]
+    UnsupportedCommand,
+    #[error("Packet did not parse correctly")]
+    InvalidPacket,
+    #[error("Packet type not supported")]
+    UnsupportedPacket,
+}
+
+const TERMINATION: u8 = 4u8;
+
+#[tokio::main]
+async fn main() -> io::Result<()> {
+    logger::init(Config::default().with_tag_on_device("nfc-rc").with_min_level(Level::Trace));
+
+    let listener = TcpListener::bind("127.0.0.1:54323").await?;
+
+    for _ in 0..2 {
+        let (mut sock, _) = listener.accept().await?;
+
+        tokio::spawn(async move {
+            let (rd, mut wr) = sock.split();
+            let mut rd = BufReader::new(rd);
+            loop {
+                if let Err(e) = process(&mut rd, &mut wr).await {
+                    match e {
+                        RootcanalError::TerminateTask => break,
+                        RootcanalError::IoError(e) => {
+                            if e.kind() == ErrorKind::UnexpectedEof {
+                                break;
+                            }
+                        }
+                        _ => panic!("Communication error: {:?}", e),
+                    }
+                }
+            }
+        })
+        .await?;
+    }
+    Ok(())
+}
+
+async fn process<R, W>(reader: &mut R, writer: &mut W) -> Result<()>
+where
+    R: AsyncReadExt + Unpin,
+    W: AsyncWriteExt + Unpin,
+{
+    let mut buffer = BytesMut::with_capacity(1024);
+    let len: usize = reader.read_u16().await?.into();
+    buffer.resize(len, 0);
+    reader.read_exact(&mut buffer).await?;
+    let frozen = buffer.freeze();
+    debug!("{:?}", &frozen);
+    let pkt_type = (frozen[0] >> 5) & 0x7;
+    debug!("packet {} received len={}", &pkt_type, &len);
+    if pkt_type == NciMsgType::Command as u8 {
+        match NciPacket::parse(&frozen) {
+            Ok(p) => command_response(writer, p).await,
+            Err(_) => Err(RootcanalError::InvalidPacket),
+        }
+    } else if pkt_type == TERMINATION {
+        Err(RootcanalError::TerminateTask)
+    } else {
+        Err(RootcanalError::UnsupportedPacket)
+    }
+}
+
+async fn command_response<W>(out: &mut W, cmd: NciPacket) -> Result<()>
+where
+    W: AsyncWriteExt + Unpin,
+{
+    let pbf = PacketBoundaryFlag::CompleteOrFinal;
+    let gid = 0u8;
+    match cmd.specialize() {
+        NciChild::Command(cmd) => match cmd.specialize() {
+            CommandChild::ResetCommand(rst) => {
+                write_nci(
+                    out,
+                    (ResetResponseBuilder { gid, pbf, status: nci::Status::Ok }).build(),
+                )
+                .await?;
+                write_nci(
+                    out,
+                    (ResetNotificationBuilder {
+                        gid,
+                        pbf,
+                        trigger: ResetTrigger::ResetCommand,
+                        config_status: if rst.get_reset_type() == ResetType::KeepConfig {
+                            ConfigStatus::ConfigKept
+                        } else {
+                            ConfigStatus::ConfigReset
+                        },
+                        nci_version: NciVersion::Version20,
+                        manufacturer_id: 0,
+                        mfsi: Vec::new(),
+                    })
+                    .build(),
+                )
+                .await
+            }
+            CommandChild::InitCommand(_) => {
+                let nfcc_feat = [0u8; 5];
+                let rf_int = [0u8; 2];
+                write_nci(
+                    out,
+                    (InitResponseBuilder {
+                        gid,
+                        pbf,
+                        status: nci::Status::Ok,
+                        nfcc_features: NfccFeatures::parse(&nfcc_feat).unwrap(),
+                        max_log_conns: 0,
+                        max_rout_tbls_size: 0x0000,
+                        max_ctrl_payload: 255,
+                        max_data_payload: 255,
+                        num_of_credits: 0,
+                        max_nfcv_rf_frame_sz: 64,
+                        rf_interface: vec![RfInterface::parse(&rf_int).unwrap(); 1],
+                    })
+                    .build(),
+                )
+                .await
+            }
+            _ => Err(RootcanalError::UnsupportedCommand),
+        },
+        _ => Err(RootcanalError::InvalidPacket),
+    }
+}
+
+async fn write_nci<W, T>(writer: &mut W, rsp: T) -> Result<()>
+where
+    W: AsyncWriteExt + Unpin,
+    T: Into<NciPacket>,
+{
+    let pkt = rsp.into();
+    let b = pkt.to_bytes();
+    let mut data = BytesMut::with_capacity(b.len() + 2);
+    data.put_u16(b.len().try_into().unwrap());
+    data.extend(b);
+    let frozen = data.freeze();
+    writer.write_all(frozen.as_ref()).await?;
+    debug!("command written");
+    Ok(())
+}
diff --git a/src/rust/test/Android.bp b/src/rust/test/Android.bp
new file mode 100644
index 0000000..138d551
--- /dev/null
+++ b/src/rust/test/Android.bp
@@ -0,0 +1,24 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "system_nfc_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["system_nfc_license"],
+}
+
+rust_binary {
+    name: "nfc_rootcanal_test",
+    defaults: ["nfc_rust_defaults"],
+    srcs: ["main.rs"],
+    rustlibs: [
+        "libnfc_packets",
+        "libnfc_rnci",
+        "libbytes",
+        "libthiserror",
+	"liblogger",
+        "liblog_rust",
+        "libtokio",
+    ],
+    proc_macros: ["libnum_derive"],
+}
diff --git a/src/rust/test/main.rs b/src/rust/test/main.rs
new file mode 100644
index 0000000..aa01629
--- /dev/null
+++ b/src/rust/test/main.rs
@@ -0,0 +1,43 @@
+// Copyright 2021, The Android Open Source Project
+//
+// 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.
+
+//! Rootcanal HAL
+//! This connects to "rootcanal" which provides a simulated
+//! Nfc chip as well as a simulated environment.
+
+use log::{debug, Level};
+use logger::{self, Config};
+use nfc_rnci::api::NciApi;
+
+/// Result type
+type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
+
+/// The NFC response callback
+pub fn nfc_callback(kind: u16, _val: &[u8]) {
+    debug!("Callback {} received", kind);
+}
+
+#[tokio::main]
+async fn main() -> Result<()> {
+    logger::init(Config::default().with_tag_on_device("lnfc").with_min_level(Level::Trace));
+
+    let mut nci = NciApi::new();
+    nci.nfc_enable(nfc_callback).await;
+    nci.nfc_init().await?;
+    nci.nfc_disable().await;
+    nci.nfc_enable(nfc_callback).await;
+    nci.nfc_init().await?;
+    nci.nfc_disable().await;
+    Ok(())
+}
diff --git a/utils/config.cc b/utils/config.cc
index 6354a2e..40be00f 100644
--- a/utils/config.cc
+++ b/utils/config.cc
@@ -59,6 +59,13 @@
   value_unsigned_ = value;
 }
 
+ConfigValue::ConfigValue(std::vector<int8_t> value) {
+  CHECK(!(value.empty()));
+  type_ = BYTES;
+  value_bytes_ = std::vector<uint8_t>(value.begin(), value.end());
+  value_unsigned_ = 0;
+}
+
 ConfigValue::ConfigValue(std::vector<uint8_t> value) {
   CHECK(!(value.empty()));
   type_ = BYTES;
diff --git a/utils/include/config.h b/utils/include/config.h
index f4bcdec..e48717b 100644
--- a/utils/include/config.h
+++ b/utils/include/config.h
@@ -27,6 +27,7 @@
   explicit ConfigValue(std::string);
   explicit ConfigValue(unsigned);
   explicit ConfigValue(std::vector<uint8_t>);
+  explicit ConfigValue(std::vector<int8_t>);
   Type getType() const;
   std::string getString() const;
   unsigned getUnsigned() const;
diff --git a/utils/include/ringbuffer.h b/utils/include/ringbuffer.h
index d2c310a..35cc023 100644
--- a/utils/include/ringbuffer.h
+++ b/utils/include/ringbuffer.h
@@ -19,6 +19,7 @@
 #pragma once
 
 #include <stdint.h>
+#include <sys/types.h>
 
 typedef struct ringbuffer_t ringbuffer_t;