blob: 35842d32e61fb588da7092ac5edcb2471fb40a88 [file] [log] [blame]
/*
* 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);
}
}
}