Merge "Weaver applet." am: 06c9c8316c am: 14f5bfe064
am: 3701a512d2

Change-Id: I8af1fb9670acda2aad5f0b704cbcd3bff9a5eb79
diff --git a/apps/Android.bp b/apps/Android.bp
index f8c1283..9e87adb 100644
--- a/apps/Android.bp
+++ b/apps/Android.bp
@@ -25,4 +25,4 @@
     cflags: ["-fvisibility=internal"],
 }
 
-subdirs = ["boot"]
+subdirs = ["boot", "weaver"]
diff --git a/apps/weaver/Android.bp b/apps/weaver/Android.bp
new file mode 100644
index 0000000..65bb0e7
--- /dev/null
+++ b/apps/weaver/Android.bp
@@ -0,0 +1,22 @@
+//
+// Copyright (C) 2017 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.
+//
+
+cc_library {
+    name: "libese-app-weaver",
+    defaults: ["libese-app-defaults"],
+    srcs: ["weaver.c"],
+    shared_libs: ["liblog", "libese", "libese-sysdeps"],
+}
diff --git a/apps/weaver/card/build.xml b/apps/weaver/card/build.xml
new file mode 100644
index 0000000..fdb4ee5
--- /dev/null
+++ b/apps/weaver/card/build.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<!-- Ant XML for out of band building the applet.  -->
+<project basedir="." default="weaver" name="applet build">
+
+<!-- Grab the sdk. -->
+<get src="https://github.com/martinpaljak/oracle_javacard_sdks/archive/master.zip"
+     dest="javacard_sdks.zip" skipexisting="true"/>
+<unzip src="javacard_sdks.zip" dest="." stripAbsolutePathSpec="true">
+  <patternset>
+    <include name="**/jc303_kit/**"/>
+  </patternset>
+  <cutdirsmapper dirs="1" />
+</unzip>
+
+<!-- Grab the awesome ant helper. -->
+<get src="https://github.com/martinpaljak/ant-javacard/releases/download/v1.7/ant-javacard.jar" dest="." skipexisting="true"/>
+<taskdef name="javacard" classname="pro.javacard.ant.JavaCard" classpath="ant-javacard.jar"/>
+
+<target name="weaver">
+<javacard jckit="jc303_kit">
+  <!-- Comm applet -->
+  <cap aid="A00000006203010C01" package="com.android.weaver" version="0.1"
+    output="weaver_comm.cap" sources="src/com/android/weaver" export="export/comm">
+    <applet class="com.android.weaver.Weaver" aid="A00000006203010C0101"/>
+    <import exps="export/comm" />
+  </cap>
+  <!-- Core applet -->
+  <cap aid="A00000006203010C02" package="com.android.weaver.core" version="0.1"
+    output="weaver_core.cap" sources="src/com/android/weaver/core" export="export/core">
+    <!-- TODO: reintroduce jcopx dependency for DSTimer -->
+    <!-- Grab the other interfaces from export/ -->
+    <import exps="export/comm" jar="export/comm/weaver.jar" />
+    <applet class="com.android.weaver.core.WeaverCore" aid="A00000006203010C0201"/>
+  </cap>
+</javacard>
+</target>
+</project>
diff --git a/apps/weaver/card/src/com/android/weaver/Consts.java b/apps/weaver/card/src/com/android/weaver/Consts.java
new file mode 100644
index 0000000..b1beab1
--- /dev/null
+++ b/apps/weaver/card/src/com/android/weaver/Consts.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+package com.android.weaver;
+
+// Keep in sync with native code
+public class Consts {
+    public static final byte SLOT_ID_BYTES = 4; // 32-bit
+    public static final byte SLOT_KEY_BYTES = 16; // 128-bit
+    public static final byte SLOT_VALUE_BYTES = 16; // 128-bit
+
+    // Read errors (first byte of response)
+    public static final byte READ_SUCCESS = 0x00;
+    public static final byte READ_WRONG_KEY = 0x7f;
+    public static final byte READ_BACK_OFF = 0x76;
+
+    // Errors
+    public static final short SW_INVALID_SLOT_ID = 0x6a86;
+
+    public static final byte CLA = (byte) 0x80;
+
+    // Instructions
+    public static final byte INS_GET_NUM_SLOTS = 0x02;
+    public static final byte INS_WRITE = 0x4;
+    public static final byte INS_READ = 0x6;
+    public static final byte INS_ERASE_ALL = 0x8;
+}
diff --git a/apps/weaver/card/src/com/android/weaver/Slots.java b/apps/weaver/card/src/com/android/weaver/Slots.java
new file mode 100644
index 0000000..6f316ad
--- /dev/null
+++ b/apps/weaver/card/src/com/android/weaver/Slots.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+package com.android.weaver;
+
+public interface Slots extends javacard.framework.Shareable {
+    /** @return The number of slots available. */
+    short getNumSlots();
+
+    /**
+     * Write the key and value to the identified slot.
+     *
+     * @param slotId ID of the slot to write to.
+     * @param key Buffer containing the key.
+     * @param keyOffset Offset of the key in the buffer.
+     * @param value Buffer containing the value.
+     * @param valueOffset Offset of the value in the buffer.
+     */
+    void write(short slotId, byte[] key, short keyOffset, byte[] value, short valueOffset);
+
+    /**
+     * Read the value from the identified slot.
+     *
+     * This is only successful if the key matches that stored in the slot.
+     *
+     * @param slotId ID of the slot to write to.
+     * @param key Buffer containing the key.
+     * @param keyOffset Offset of the key in the buffer.
+     * @param value Buffer to receive the value.
+     * @param valueOffset Offset into the buffer to write the value.
+     * @return Status byte indicating the success or otherwise of the read.
+     */
+    byte read(short slotId, byte[] key, short keyOffset, byte[] value, short valueOffset);
+
+    /** Erases the contents of all slots. */
+    void eraseAll();
+}
diff --git a/apps/weaver/card/src/com/android/weaver/Weaver.java b/apps/weaver/card/src/com/android/weaver/Weaver.java
new file mode 100644
index 0000000..af41465
--- /dev/null
+++ b/apps/weaver/card/src/com/android/weaver/Weaver.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+package com.android.weaver;
+
+import javacard.framework.AID;
+import javacard.framework.APDU;
+import javacard.framework.Applet;
+import javacard.framework.ISO7816;
+import javacard.framework.ISOException;
+import javacard.framework.JCSystem;
+import javacard.framework.Shareable;
+import javacard.framework.Util;
+
+public class Weaver extends Applet {
+    // Keep constants in sync with esed
+    public static final byte[] CORE_APPLET_AID
+            = new byte[] {(byte)0xa0, 0x00, 0x00, 0x00, 0x62, 0x03, 0x01, 0x0c, 0x02, 0x01};
+    public static final byte CORE_APPLET_SLOTS_INTERFACE = 0;
+
+    private Slots mSlots;
+
+    protected Weaver() {
+        register();
+    }
+
+    /**
+     * Installs this applet.
+     *
+     * @param params the installation parameters
+     * @param offset the starting offset of the parameters
+     * @param length the length of the parameters
+     */
+    public static void install(byte[] params, short offset, byte length) {
+        new Weaver();
+    }
+
+    /**
+     * Get a handle on the slots after the applet is registered but before and APDUs are received.
+     */
+    @Override
+    public boolean select() {
+        AID coreAid = JCSystem.lookupAID(CORE_APPLET_AID, (short) 0, (byte) CORE_APPLET_AID.length);
+        if (coreAid == null) {
+            return false;
+        }
+
+        mSlots = (Slots) JCSystem.getAppletShareableInterfaceObject(
+                coreAid, CORE_APPLET_SLOTS_INTERFACE);
+        if (mSlots == null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Processes an incoming APDU.
+     *
+     * @param apdu the incoming APDU
+     * @exception ISOException with the response bytes per ISO 7816-4
+     */
+    @Override
+    public void process(APDU apdu) {
+        final byte buffer[] = apdu.getBuffer();
+        final byte cla = buffer[ISO7816.OFFSET_CLA];
+        final byte ins = buffer[ISO7816.OFFSET_INS];
+
+        // Handle standard commands
+        if (apdu.isISOInterindustryCLA()) {
+            if (cla == ISO7816.CLA_ISO7816) {
+                switch (ins) {
+                    case ISO7816.INS_SELECT:
+                        // Do nothing, successfully
+                        return;
+                    default:
+                        ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
+                }
+            } else {
+                ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
+            }
+        }
+
+        // Handle custom applet commands
+        if (cla != Consts.CLA) {
+            ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
+        }
+
+        switch (ins) {
+            case Consts.INS_GET_NUM_SLOTS:
+                getNumSlots(apdu);
+                return;
+
+            case Consts.INS_WRITE:
+                write(apdu);
+                return;
+
+            case Consts.INS_READ:
+                read(apdu);
+                return;
+
+            case Consts.INS_ERASE_ALL:
+                eraseAll(apdu);
+                return;
+
+            default:
+                ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
+        }
+    }
+
+    /**
+     * Get the number of slots.
+     *
+     * p1: 0
+     * p2: 0
+     * data: _
+     */
+    private void getNumSlots(APDU apdu) {
+        p1p2Unused(apdu);
+        //dataUnused(apdu);
+        // TODO(ascull): how to handle the cases of APDU properly?
+        prepareToSend(apdu, (short) 4);
+        apdu.setOutgoingLength((short) 4);
+
+        final byte buffer[] = apdu.getBuffer();
+        Util.setShort(buffer, (short) 0, (short) 0);
+        Util.setShort(buffer, (short) 2, mSlots.getNumSlots());
+
+        apdu.sendBytes((short) 0, (byte) 4);
+    }
+
+    public static final short WRITE_DATA_BYTES
+            = Consts.SLOT_ID_BYTES + Consts.SLOT_KEY_BYTES + Consts.SLOT_VALUE_BYTES;
+    private static final byte WRITE_DATA_SLOT_ID_OFFSET = ISO7816.OFFSET_CDATA;
+    private static final byte WRITE_DATA_KEY_OFFSET
+            = WRITE_DATA_SLOT_ID_OFFSET + Consts.SLOT_ID_BYTES;
+    private static final byte WRITE_DATA_VALUE_OFFSET
+            = WRITE_DATA_KEY_OFFSET + Consts.SLOT_KEY_BYTES;
+
+    /**
+     * Write to a slot.
+     *
+     * p1: 0
+     * p2: 0
+     * data: [slot ID] [key data] [value data]
+     */
+    private void write(APDU apdu) {
+        p1p2Unused(apdu);
+        receiveData(apdu, WRITE_DATA_BYTES);
+
+        final byte buffer[] = apdu.getBuffer();
+        final short slotId = getSlotId(buffer, WRITE_DATA_SLOT_ID_OFFSET);
+        mSlots.write(slotId, buffer, WRITE_DATA_KEY_OFFSET, buffer, WRITE_DATA_VALUE_OFFSET);
+    }
+
+    public static final short READ_DATA_BYTES
+            = Consts.SLOT_ID_BYTES + Consts.SLOT_KEY_BYTES;
+    private static final byte READ_DATA_SLOT_ID_OFFSET = ISO7816.OFFSET_CDATA;
+    private static final byte READ_DATA_KEY_OFFSET
+            = WRITE_DATA_SLOT_ID_OFFSET + Consts.SLOT_ID_BYTES;
+
+    /**
+     * Read a slot.
+     *
+     * p1: 0
+     * p2: 0
+     * data: [slot ID] [key data]
+     */
+    private void read(APDU apdu) {
+        final byte successSize = 1 + Consts.SLOT_VALUE_BYTES;
+        final byte failSize = 1 + 4;
+
+        p1p2Unused(apdu);
+        receiveData(apdu, READ_DATA_BYTES);
+        prepareToSend(apdu, successSize);
+
+        final byte buffer[] = apdu.getBuffer();
+        final short slotId = getSlotId(buffer, READ_DATA_SLOT_ID_OFFSET);
+
+        final byte err = mSlots.read(slotId, buffer, READ_DATA_KEY_OFFSET, buffer, (short) 1);
+        buffer[(short) 0] = err;
+        if (err == Consts.READ_SUCCESS) {
+            apdu.setOutgoingLength(successSize);
+            apdu.sendBytes((short) 0, successSize);
+        } else {
+            apdu.setOutgoingLength(failSize);
+            apdu.sendBytes((short) 0, failSize);
+        }
+    }
+
+    /**
+     * Erase all slots.
+     *
+     * p1: 0
+     * p2: 0
+     * data: _
+     */
+    private void eraseAll(APDU apdu) {
+        p1p2Unused(apdu);
+        dataUnused(apdu);
+        mSlots.eraseAll();
+    }
+
+    /**
+     * Check that the parameters are 0.
+     *
+     * They are not being used but should be under control.
+     */
+    private void p1p2Unused(APDU apdu) {
+        final byte buffer[] = apdu.getBuffer();
+        if (buffer[ISO7816.OFFSET_P1] != 0 || buffer[ISO7816.OFFSET_P2] != 0) {
+            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
+        }
+    }
+
+    /**
+     * Check that no data was provided.
+     */
+    private void dataUnused(APDU apdu) {
+        receiveData(apdu, (short) 0);
+    }
+
+    /**
+     * Calls setIncomingAndReceive() on the APDU and checks the length is as expected.
+     */
+    private void receiveData(APDU apdu, short expectedLength) {
+        final short bytesRead = apdu.setIncomingAndReceive();
+        if (apdu.getIncomingLength() != expectedLength || bytesRead != expectedLength) {
+            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
+        }
+    }
+
+    /**
+     * The slot ID in the API is 32-bits but the applet works with 16-bit IDs.
+     */
+    private short getSlotId(byte[] bArray, short bOff) {
+        if (bArray[bOff] != 0 || bArray[(short) (bOff + 1)] != 0) {
+            ISOException.throwIt(Consts.SW_INVALID_SLOT_ID);
+        }
+        return Util.getShort(bArray,(short) (bOff + 2));
+    }
+
+    /**
+     * Calls setOutgoing() on the APDU, checks the length is as expected and calls
+     * setOutgoingLength() with that length.
+     *
+     * Still need to call setOutgoingLength() after this method.
+     */
+    private void prepareToSend(APDU apdu, short expectedMaxLength) {
+        final short outDataLen = apdu.setOutgoing();
+        if (outDataLen != expectedMaxLength) {
+            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
+        }
+    }
+}
diff --git a/apps/weaver/card/src/com/android/weaver/core/CoreSlots.java b/apps/weaver/card/src/com/android/weaver/core/CoreSlots.java
new file mode 100644
index 0000000..35842d3
--- /dev/null
+++ b/apps/weaver/card/src/com/android/weaver/core/CoreSlots.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+package com.android.weaver.core;
+
+import javacard.framework.ISO7816;
+import javacard.framework.ISOException;
+import javacard.framework.JCSystem;
+import javacard.framework.Util;
+
+import com.android.weaver.Consts;
+import com.android.weaver.Slots;
+
+//import com.nxp.id.jcopx.util.DSTimer;
+
+class CoreSlots implements Slots {
+    static final byte NUM_SLOTS = 64;
+
+    private Slot[] mSlots;
+
+    CoreSlots() {
+        // Allocate all memory up front
+        mSlots = new Slot[NUM_SLOTS];
+        for (short i = 0; i < NUM_SLOTS; ++i) {
+            mSlots[i] = new Slot();
+        }
+
+        final short intBytes = 4;
+        Slot.sRemainingBackoff = JCSystem.makeTransientByteArray(intBytes, JCSystem.CLEAR_ON_RESET);
+    }
+
+    @Override
+    public short getNumSlots() {
+        return NUM_SLOTS;
+    }
+
+    @Override
+    public void write(short rawSlotId, byte[] key, short keyOffset,
+            byte[] value, short valueOffset) {
+        final short slotId = validateSlotId(rawSlotId);
+        mSlots[slotId].write(key, keyOffset, value, valueOffset);
+    }
+
+    @Override
+    public byte read(short rawSlotId, byte[] key, short keyOffset,
+            byte[] outValue, short outOffset) {
+        final short slotId = validateSlotId(rawSlotId);
+        return mSlots[slotId].read(key, keyOffset, outValue, outOffset);
+    }
+
+    @Override
+    public void eraseAll() {
+        for (short i = 0; i < NUM_SLOTS; ++i) {
+            mSlots[i].erase();
+        }
+    }
+
+    /**
+     * Check the slot ID is within range and convert it to a short.
+     */
+    private short validateSlotId(short slotId) {
+        // slotId is unsigned so if the signed version is negative then it is far too big
+        if (slotId < 0 || slotId >= NUM_SLOTS) {
+            ISOException.throwIt(Consts.SW_INVALID_SLOT_ID);
+        }
+        return slotId;
+    }
+
+    private static class Slot {
+        private static byte[] sRemainingBackoff;
+
+        private byte[] mKey = new byte[Consts.SLOT_KEY_BYTES];
+        private byte[] mValue = new byte[Consts.SLOT_VALUE_BYTES];
+        private short mFailureCount;
+        //private DSTimer mBackoffTimer;
+
+        /**
+         * Transactionally reset the slot with a new key and value.
+         *
+         * @param keyBuffer   the buffer containing the key data
+         * @param keyOffset   the offset of the key in its buffer
+         * @param valueBuffer the buffer containing the value data
+         * @param valueOffset the offset of the value in its buffer
+         */
+        public void write(
+                byte[] keyBuffer, short keyOffset, byte[] valueBuffer, short valueOffset) {
+            JCSystem.beginTransaction();
+            Util.arrayCopy(keyBuffer, keyOffset, mKey, (short) 0, Consts.SLOT_KEY_BYTES);
+            Util.arrayCopy(valueBuffer, valueOffset, mValue, (short) 0, Consts.SLOT_VALUE_BYTES);
+            mFailureCount = 0;
+            //mBackoffTimer = DSTimer.getInstance();
+            JCSystem.commitTransaction();
+        }
+
+        /**
+         * Transactionally clear the slot.
+         */
+        public void erase() {
+            JCSystem.beginTransaction();
+            arrayFill(mKey, (short) 0, Consts.SLOT_KEY_BYTES, (byte) 0);
+            arrayFill(mValue, (short) 0, Consts.SLOT_VALUE_BYTES, (byte) 0);
+            mFailureCount = 0;
+            //mBackoffTimer.stopTimer();
+            JCSystem.commitTransaction();
+        }
+
+        /**
+         * Copy the slot's value to the buffer if the provided key matches the slot's key.
+         *
+         * @param keyBuffer the buffer containing the key
+         * @param keyOffset the offset of the key in its buffer
+         * @param outBuffer the buffer to copy the value or backoff time into
+         * @param outOffset the offset into the output buffer
+         * @return status code
+         */
+        public byte read(byte[] keyBuffer, short keyOffset, byte[] outBuffer, short outOffset) {
+            // Check timeout has expired or hasn't been started
+            //mBackoffTimer.getRemainingTime(sRemainingBackoff, (short) 0);
+            if (sRemainingBackoff[0] != 0 || sRemainingBackoff[1] != 0 ||
+                  sRemainingBackoff[2] != 0 || sRemainingBackoff[3] != 0) {
+                Util.arrayCopyNonAtomic(
+                        sRemainingBackoff, (short) 0, outBuffer, outOffset, (byte) 4);
+                return Consts.READ_BACK_OFF;
+            }
+
+            // Assume this to be a failed attempt until proven otherwise. This means losing power
+            // midway cannot be abused for extra attempts.
+            JCSystem.beginTransaction();
+            if (mFailureCount != 0x7fff) {
+                mFailureCount += 1;
+            }
+            throttle(sRemainingBackoff, (short) 0, mFailureCount);
+            //mBackoffTimer.startTimer(
+            //        sRemainingBackoff, (short) 0, DSTimer.DST_POWEROFFMODE_FALLBACK);
+            JCSystem.commitTransaction();
+
+            // Check the key matches and copy out the value if it does
+            if (Util.arrayCompare(
+                    keyBuffer, keyOffset, mKey, (short) 0, Consts.SLOT_KEY_BYTES) != 0) {
+                Util.arrayCopyNonAtomic(
+                        sRemainingBackoff, (short) 0, outBuffer, outOffset, (byte) 4);
+                return Consts.READ_WRONG_KEY;
+            }
+
+            // This attempt was successful so reset the failures
+            JCSystem.beginTransaction();
+            mFailureCount = 0;
+            //mBackoffTimer.stopTimer();
+            JCSystem.commitTransaction();
+
+            Util.arrayCopyNonAtomic(
+                    mValue, (short) 0, outBuffer, outOffset, Consts.SLOT_VALUE_BYTES);
+            return Consts.READ_SUCCESS;
+        }
+
+        /**
+         * 3.0.3 does not offset Util.arrayFill
+         */
+        private static void arrayFill(byte[] bArray, short bOff, short bLen, byte bValue) {
+            for (short i = 0; i < bLen; ++i) {
+                bArray[(short) (bOff + i)] = bValue;
+            }
+        }
+
+        /**
+         * Calculates the timeout in milliseconds as a function of the failure
+         * counter 'x' as follows:
+         *
+         * [0, 5) -> 0
+         * 5 -> 30
+         * [6, 10) -> 0
+         * [11, 30) -> 30
+         * [30, 140) -> 30 * (2^((x - 30)/10))
+         * [140, inf) -> 1 day
+         *
+         * The 32-bit millisecond timeout is written to the array.
+         */
+        private static void throttle(byte[] bArray, short bOff, short failureCount) {
+            short highWord = 0;
+            short lowWord = 0;
+
+            final short thirtySecondsInMilliseconds = 0x7530; // = 1000 * 30
+            if (failureCount == 0) {
+                // 0s
+            } else if (failureCount > 0 && failureCount <= 10) {
+                if (failureCount % 5 == 0) {
+                    // 30s
+                  lowWord = thirtySecondsInMilliseconds;
+                }  else {
+                    // 0s
+                }
+            } else if (failureCount < 30) {
+                // 30s
+                lowWord = thirtySecondsInMilliseconds;
+            } else if (failureCount < 140) {
+                // 30 * (2^((x - 30)/10))
+                final short shift = (short) ((short) (failureCount - 30) / 10);
+                highWord = (short) (thirtySecondsInMilliseconds >> (16 - shift));
+                lowWord = (short) (thirtySecondsInMilliseconds << shift);
+            } else {
+                // 1 day in ms = 1000 * 60 * 60 * 24 = 0x526 5C00
+                highWord = 0x0526;
+                lowWord = 0x5c00;
+            }
+
+            // Write the value to the buffer
+            Util.setShort(bArray, bOff, highWord);
+            Util.setShort(bArray, (short) (bOff + 2), lowWord);
+        }
+    }
+}
diff --git a/apps/weaver/card/src/com/android/weaver/core/WeaverCore.java b/apps/weaver/card/src/com/android/weaver/core/WeaverCore.java
new file mode 100644
index 0000000..35a73c5
--- /dev/null
+++ b/apps/weaver/card/src/com/android/weaver/core/WeaverCore.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+package com.android.weaver.core;
+
+import javacard.framework.AID;
+import javacard.framework.APDU;
+import javacard.framework.Applet;
+import javacard.framework.Shareable;
+
+class WeaverCore extends Applet {
+    public static final byte[] COMMAPP_APPLET_AID
+            = new byte[] {(byte) 0xa0, 0x00, 0x00, 0x00, 0x62, 0x03, 0x01, 0x0c, 0x01, 0x01};
+
+    private CoreSlots mSlots;
+
+    protected WeaverCore() {
+        // Allocate all memory up front
+        mSlots = new CoreSlots();
+        register();
+    }
+
+    /**
+     * Installs this applet.
+     *
+     * @param params the installation parameters
+     * @param offset the starting offset of the parameters
+     * @param length the length of the parameters
+     */
+    public static void install(byte[] params, short offset, byte length) {
+        new WeaverCore();
+    }
+
+    /**
+     * This applet can only be accessed from other applets.
+     */
+    @Override
+    public boolean select() {
+        return false;
+    }
+
+    /**
+     * Returns and instance of the {@link Slots} interface.
+     *
+     * @param AID The requesting applet's AID must be that of the CoreApp.
+     * @param arg Must be {@link #SLOTS_INTERFACE} else returns {@code null}.
+     */
+    @Override
+    public Shareable getShareableInterfaceObject(AID clientAid, byte arg) {
+        if (!clientAid.equals(COMMAPP_APPLET_AID, (short) 0, (byte) COMMAPP_APPLET_AID.length)) {
+            return null;
+        }
+        return (arg == 0) ? mSlots : null;
+    }
+
+    /**
+     * Should never be called.
+     */
+    @Override
+    public void process(APDU apdu) {
+    }
+}
diff --git a/apps/weaver/include/ese/app/weaver.h b/apps/weaver/include/ese/app/weaver.h
new file mode 100644
index 0000000..67bea4b
--- /dev/null
+++ b/apps/weaver/include/ese/app/weaver.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 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 ESE_APP_WEAVER_H_
+#define ESE_APP_WEAVER_H_ 1
+
+#include "../../../../../libese/include/ese/ese.h"
+#include "../../../../../libese/include/ese/log.h"
+#include "../../../../../libese-sysdeps/include/ese/sysdeps.h"
+
+#include "../../../../include/ese/app/result.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * EseWeaverSession carries the necessary start for interfacing
+ * with the methods below.
+ *
+ * Its usage follows a lifecycle like:
+ *
+ *   EseAppResult res;
+ *   EseWeaverSession session;
+ *   ese_weaver_session_init(&session);
+ *   res = ese_weaver_session_open(ese, &session);
+ *   if (res != ESE_APP_RESULT_OK) {
+ *     ... handle error (especially cooldown) ...
+ *   }
+ *   ... ese_weaver_* ...
+ *   ese_weaver_session_close(&session);
+ *
+ */
+struct EseWeaverSession {
+  struct EseInterface *ese;
+  bool active;
+  uint8_t channel_id;
+};
+
+/** The keys are 16 bytes */
+const uint8_t kEseWeaverKeySize = 16;
+
+/** The values are 16 bytes */
+const uint8_t kEseWeaverValueSize = 16;
+
+
+const int ESE_WEAVER_READ_WRONG_KEY = ese_make_app_result(0x6a, 0x85);
+const int ESE_WEAVER_READ_TIMEOUT = ese_make_app_result(0x6a, 0x87);
+
+/**
+ * Initializes a pre-allocated |session| for use.
+ */
+void ese_weaver_session_init(struct EseWeaverSession *session);
+
+/**
+ * Configures a communication session with the Storage applet using a logical
+ * channel on an already open |ese| object.
+ *
+ * @returns ESE_APP_RESULT_OK on success.
+ */
+EseAppResult ese_weaver_session_open(struct EseInterface *ese, struct EseWeaverSession *session);
+
+/**
+ * Shuts down the logical channel with the Storage applet and invalidates
+ * the |session| internal state.
+ *
+ * @returns ESE_APP_RESULT_OK on success.
+ */
+EseAppResult ese_weaver_session_close(struct EseWeaverSession *session);
+
+/**
+ * Retreives the number of slots available.
+ * @returns ESE_APP_RESULT_OK if |numSlots| contains a valid value.
+ */
+EseAppResult ese_weaver_get_num_slots(struct EseWeaverSession *session, uint32_t *numSlots);
+
+/**
+ * Writes a new key-value pair into the slot.
+ *
+ * |key| and |value| must be buffers of sizes |kEseWeaverKeySize| and
+ * |kEseWeaverValueSize| respectively.
+ *
+ * @returns ESE_APP_RESULT_OK on success.
+ */
+EseAppResult ese_weaver_write(struct EseWeaverSession *session, uint32_t slotId,
+                              const uint8_t *key, const uint8_t *value);
+
+
+/**
+ * Reads the value in the slot provided the correct key was passed.
+ *
+ * |key| and |value| must be buffers of sizes |kEseWeaverKeySize| and
+ * |kEseWeaverValueSize| respectively.
+ *
+ * @returns ESE_APP_RESULT_OK if |value| was filled with the value.
+ *          ESE_WEAVER_READ_WRONG_KEY if |key| was wrong and |timeout| contains
+ *          a valid timeout.
+ *          ESE_WEAVER_READ_TIMEOUT if Weaver is in backoff mode and |timeout|
+ *          contains a valid timeout.
+ */
+EseAppResult ese_weaver_read(struct EseWeaverSession *session, uint32_t slotId,
+                             const uint8_t *key, uint8_t *value, uint32_t *timeout);
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif  /* ESE_APP_WEAVER_H_ */
diff --git a/apps/weaver/weaver.c b/apps/weaver/weaver.c
new file mode 100644
index 0000000..942adb0
--- /dev/null
+++ b/apps/weaver/weaver.c
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2017 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/ese/app/weaver.h"
+
+/* Non-static, but visibility=hidden so they can be used in test. */
+const uint8_t kManageChannelOpen[] = {0x00, 0x70, 0x00, 0x00, 0x01};
+const uint32_t kManageChannelOpenLength = (uint32_t)sizeof(kManageChannelOpen);
+const uint8_t kManageChannelClose[] = {0x00, 0x70, 0x80, 0x00, 0x00};
+
+const uint8_t kSelectApplet[] = {0x00, 0xA4, 0x04, 0x00, 0x0A, 0xA0,
+                                 0x00, 0x00, 0x00, 0x62, 0x03, 0x01,
+                                 0x0C, 0x01, 0x01, 0x00};
+const uint32_t kSelectAppletLength = (uint32_t)sizeof(kSelectApplet);
+// Supported commands.
+const uint8_t kGetNumSlots[] = {0x80, 0x02, 0x00, 0x00, 0x04};
+const uint8_t kWrite[] = {0x80, 0x04, 0x00, 0x00,
+                          4 + kEseWeaverKeySize +
+                              kEseWeaverValueSize}; // slotid + key + value
+const uint8_t kRead[] = {0x80, 0x06, 0x00, 0x00,
+                         4 + kEseWeaverKeySize}; // slotid + key
+const uint8_t kEraseAll[] = {0x80, 0x08, 0x00, 0x00};
+
+// Build 32-bit int from big endian bytes
+static uint32_t get_uint32(uint8_t buf[4]) {
+  uint32_t x = buf[3];
+  x |= buf[2] << 8;
+  x |= buf[1] << 16;
+  x |= buf[0] << 24;
+  return x;
+}
+
+static void put_uint32(uint8_t buf[4], uint32_t val) {
+  buf[0] = 0xff & (val >> 24);
+  buf[1] = 0xff & (val >> 16);
+  buf[2] = 0xff & (val >> 8);
+  buf[3] = 0xff & val;
+}
+
+EseAppResult check_apdu_status(uint8_t code[2]) {
+  if (code[0] == 0x90 && code[1] == 0x00) {
+    return ESE_APP_RESULT_OK;
+  }
+  if (code[0] == 0x66 && code[1] == 0xA5) {
+    return ESE_APP_RESULT_ERROR_COOLDOWN;
+  }
+  if (code[0] == 0x6A && code[1] == 0x83) {
+    return ESE_APP_RESULT_ERROR_UNCONFIGURED;
+  }
+  /* TODO(wad) Bubble up the error code if needed. */
+  ALOGE("unhandled response %.2x %.2x", code[0], code[1]);
+  return ese_make_os_result(code[0], code[1]);
+}
+
+ESE_API void ese_weaver_session_init(struct EseWeaverSession *session) {
+  session->ese = NULL;
+  session->active = false;
+  session->channel_id = 0;
+}
+
+ESE_API EseAppResult ese_weaver_session_open(struct EseInterface *ese,
+                                             struct EseWeaverSession *session) {
+  struct EseSgBuffer tx[2];
+  struct EseSgBuffer rx;
+  uint8_t rx_buf[32];
+  int rx_len;
+  if (!ese || !session) {
+    ALOGE("Invalid |ese| or |session|");
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  if (session->active == true) {
+    ALOGE("|session| is already active");
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  /* Instantiate a logical channel */
+  rx_len = ese_transceive(ese, kManageChannelOpen, sizeof(kManageChannelOpen),
+                          rx_buf, sizeof(rx_buf));
+  if (ese_error(ese)) {
+    ALOGE("transceive error: code:%d message:'%s'", ese_error_code(ese),
+          ese_error_message(ese));
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  if (rx_len < 0) {
+    ALOGE("transceive error: rx_len: %d", rx_len);
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  if (rx_len < 2) {
+    ALOGE("transceive error: reply too short");
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  EseAppResult ret;
+  ret = check_apdu_status(&rx_buf[rx_len - 2]);
+  if (ret != ESE_APP_RESULT_OK) {
+    ALOGE("MANAGE CHANNEL OPEN failed with error code: %x %x",
+          rx_buf[rx_len - 2], rx_buf[rx_len - 1]);
+    return ret;
+  }
+  if (rx_len < 3) {
+    ALOGE("transceive error: successful reply unexpectedly short");
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  session->ese = ese;
+  session->channel_id = rx_buf[rx_len - 3];
+
+  /* Select Weaver Applet. */
+  uint8_t chan = kSelectApplet[0] | session->channel_id;
+  tx[0].base = &chan;
+  tx[0].len = 1;
+  tx[1].base = (uint8_t *)&kSelectApplet[1];
+  tx[1].len = sizeof(kSelectApplet) - 1;
+  rx.base = &rx_buf[0];
+  rx.len = sizeof(rx_buf);
+  rx_len = ese_transceive_sg(ese, tx, 2, &rx, 1);
+  if (rx_len < 0 || ese_error(ese)) {
+    ALOGE("transceive error: caller should check ese_error()");
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  if (rx_len < 2) {
+    ALOGE("transceive error: reply too short");
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  ret = check_apdu_status(&rx_buf[rx_len - 2]);
+  if (ret != ESE_APP_RESULT_OK) {
+    ALOGE("SELECT failed with error code: %x %x", rx_buf[rx_len - 2],
+          rx_buf[rx_len - 1]);
+    return ret;
+  }
+  session->active = true;
+  return ESE_APP_RESULT_OK;
+}
+
+ESE_API EseAppResult
+ese_weaver_session_close(struct EseWeaverSession *session) {
+  uint8_t rx_buf[32];
+  int rx_len;
+  if (!session || !session->ese) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  if (!session->active || session->channel_id == 0) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  /* Release the channel */
+  uint8_t close_channel[sizeof(kManageChannelClose)];
+  ese_memcpy(close_channel, kManageChannelClose, sizeof(kManageChannelClose));
+  close_channel[0] |= session->channel_id;
+  close_channel[3] |= session->channel_id;
+  rx_len = ese_transceive(session->ese, close_channel, sizeof(close_channel),
+                          rx_buf, sizeof(rx_buf));
+  if (rx_len < 0 || ese_error(session->ese)) {
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  if (rx_len < 2) {
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  EseAppResult ret;
+  ret = check_apdu_status(&rx_buf[rx_len - 2]);
+  if (ret != ESE_APP_RESULT_OK) {
+    return ret;
+  }
+  session->channel_id = 0;
+  session->active = false;
+  return ESE_APP_RESULT_OK;
+}
+
+ESE_API EseAppResult ese_weaver_get_num_slots(struct EseWeaverSession *session,
+                                              uint32_t *numSlots) {
+  if (!session || !session->ese || !session->active) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  if (!session->active || session->channel_id == 0) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  if (!numSlots) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+
+  // Prepare command
+  uint8_t get_num_slots[sizeof(kGetNumSlots)];
+  ese_memcpy(get_num_slots, kGetNumSlots, sizeof(kGetNumSlots));
+  get_num_slots[0] |= session->channel_id;
+
+  // Send command
+  uint8_t rx_buf[6];
+  const int rx_len =
+      ese_transceive(session->ese, get_num_slots, sizeof(get_num_slots), rx_buf,
+                     sizeof(rx_buf));
+
+  // Check for errors
+  if (rx_len < 2 || ese_error(session->ese)) {
+    ALOGE("Failed to get num slots");
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  if (rx_len == 2) {
+    ALOGE("ese_weaver_get_num_slots: SE exception");
+    EseAppResult ret = check_apdu_status(rx_buf);
+    return ret;
+  }
+  if (rx_len != 6) {
+    ALOGE("Unexpected response from Weaver applet");
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+
+  *numSlots = get_uint32(rx_buf);
+  return ESE_APP_RESULT_OK;
+}
+
+ESE_API EseAppResult ese_weaver_write(struct EseWeaverSession *session,
+                                      uint32_t slotId, const uint8_t *key,
+                                      const uint8_t *value) {
+  if (!session || !session->ese || !session->active) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  if (!session->active || session->channel_id == 0) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  if (!key || !value) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+
+  // Prepare data to send
+  struct EseSgBuffer tx[5];
+  uint8_t chan = kWrite[0] | session->channel_id;
+  tx[0].base = &chan;
+  tx[0].len = 1;
+  tx[1].base = (uint8_t *)&kWrite[1];
+  tx[1].len = sizeof(kWrite) - 1;
+
+  // Slot ID in big endian
+  uint8_t slot_id[4];
+  put_uint32(slot_id, slotId);
+  tx[2].base = slot_id;
+  tx[2].len = sizeof(slot_id);
+
+  // Key and value
+  tx[3].c_base = key;
+  tx[3].len = kEseWeaverKeySize;
+  tx[4].c_base = value;
+  tx[4].len = kEseWeaverValueSize;
+
+  // Prepare buffer for result
+  struct EseSgBuffer rx;
+  uint8_t rx_buf[2];
+  rx.base = rx_buf;
+  rx.len = sizeof(rx_buf);
+
+  // Send the command
+  const int rx_len = ese_transceive_sg(session->ese, tx, 5, &rx, 1);
+
+  // Check for errors
+  if (rx_len < 2 || ese_error(session->ese)) {
+    ALOGE("Failed to write to slot");
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  if (rx_len > 2) {
+    ALOGE("Unexpected response from Weaver applet");
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  return check_apdu_status(rx_buf);
+}
+
+ESE_API EseAppResult ese_weaver_read(struct EseWeaverSession *session,
+                                     uint32_t slotId, const uint8_t *key,
+                                     uint8_t *value, uint32_t *timeout) {
+  if (!session || !session->ese || !session->active) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  if (!session->active || session->channel_id == 0) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+  if (!key || !value || !timeout) {
+    return ESE_APP_RESULT_ERROR_ARGUMENTS;
+  }
+
+  // Prepare data to send
+  struct EseSgBuffer tx[5];
+  uint8_t chan = kRead[0] | session->channel_id;
+  tx[0].base = &chan;
+  tx[0].len = 1;
+  tx[1].base = (uint8_t *)&kRead[1];
+  tx[1].len = sizeof(kRead) - 1;
+
+  // Slot ID in big endian
+  uint8_t slot_id[4];
+  put_uint32(slot_id, slotId);
+  tx[2].base = slot_id;
+  tx[2].len = sizeof(slot_id);
+
+  // Key of 16 bytes
+  tx[3].c_base = key;
+  tx[3].len = kEseWeaverKeySize;
+
+  // Value response is 16 bytes
+  const uint8_t maxResponse = 1 + kEseWeaverValueSize;
+  tx[4].c_base = &maxResponse;
+  tx[4].len = 1;
+
+  // Prepare buffer for result
+  struct EseSgBuffer rx[2];
+  uint8_t appletStatus;
+  rx[0].base = &appletStatus;
+  rx[0].len = 1;
+  rx[1].base = value;
+  rx[1].len = kEseWeaverValueSize;
+  uint8_t rx_buf[2];
+  rx[2].base = rx_buf;
+  rx[2].len = sizeof(rx_buf);
+
+  // Send the command
+  const int rx_len = ese_transceive_sg(session->ese, tx, 5, rx, 2);
+
+  // Check for errors
+  if (rx_len < 2 || ese_error(session->ese)) {
+    ALOGE("Failed to write to slot");
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  if (rx_len == 2) {
+    rx_buf[0] = appletStatus;
+    rx_buf[1] = value[0];
+    ALOGE("ese_weaver_read: SE exception");
+    EseAppResult ret = check_apdu_status(rx_buf);
+    return ret;
+  }
+  if (rx_len < 7) {
+    ALOGE("Unexpected response from Weaver applet");
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  const uint8_t READ_SUCCESS = 0x00;
+  const uint8_t READ_WRONG_KEY = 0x7f;
+  const uint8_t READ_BACK_OFF = 0x76;
+  // wrong key
+  if (appletStatus == READ_WRONG_KEY) {
+    ALOGI("ese_weaver_read wrong key provided");
+    *timeout = get_uint32(value);
+    return ESE_WEAVER_READ_WRONG_KEY;
+  }
+  // backoff
+  if (appletStatus == READ_BACK_OFF) {
+    ALOGI("ese_weaver_read wrong key provided");
+    *timeout = get_uint32(value);
+    return ESE_WEAVER_READ_TIMEOUT;
+  }
+  if (rx_len != 19) {
+    ALOGE("Unexpected response from Weaver applet");
+    return ESE_APP_RESULT_ERROR_COMM_FAILED;
+  }
+  return ESE_APP_RESULT_OK;
+}
+
+// TODO: erase all, not currently used