/**
Copyright 2015 Tim Engler, Rareventure LLC
This file is part of Tiny Travel Tracker.
Tiny Travel Tracker is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Tiny Travel Tracker is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Tiny Travel Tracker. If not, see <http://www.gnu.org/licenses/>.
*/
package com.rareventure.android.encryption;
import android.util.Log;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.security.SecureRandom;
import com.rareventure.android.Util;
import com.rareventure.android.database.CachableRow;
import com.rareventure.android.database.Cache;
import com.rareventure.gps2.GTG;
import com.rareventure.gps2.GpsTrailerCrypt;
public abstract class EncryptedRow extends CachableRow {
/**
* Amout of extra data to add to the array when inserting an int (or other things, I guess)
*/
private static final int EXTRA_DATA_TO_ADD_AT_A_TIME = 8 * (Integer.SIZE>>3);
public static final int EXTRA_BYTES_FOR_USER_DATA_KEY = (Integer.SIZE>>3);
public byte[] data2;
public static class Column
{
public static final int INTEGER_BYTE_SIZE = (Integer.SIZE >> 3);
public int pos = Integer.MIN_VALUE;
int size = -1;
public Class type;
public String name = "<unk>";
public Column(Class<?> type) {
this.type = type;
}
public Column(String name, Class<?> type) {
this.name = name;
this.type = type;
}
public Column(int size)
{
this.size = size;
}
public Column(String name, int size)
{
this.name = name;
this.size = size;
}
/**
* This is for debugging
*/
public String getValue(byte[] data) {
if(type == null)
{
return Util.toHex(data,pos,size);
}
if(type == Integer.class)
return String.valueOf(Util.byteArrayToInt(data, pos));
if(type == Long.class)
return String.valueOf(Util.byteArrayToLong(data, pos));
if(type == Float.class)
return String.valueOf(Float.intBitsToFloat(Util.byteArrayToInt(data, pos)));
if(type == Double.class)
return String.valueOf(Double.longBitsToDouble(Util.byteArrayToLong(data, pos)));
if(type == Byte.class)
return Util.toHex(data, pos, 1);
if(type == String.class)
return Util.toHex(data, pos, 99999); //TODO 3: hack... assumes String column is always the last
return null;
}
}
public static int figurePosAndSizeForColumns(Column [] columns)
{
int totalSize = 0;
for(Column c : columns)
{
c.pos = totalSize;
//if size wasn't already determined
if(c.size == -1)
{
if(String.class.isAssignableFrom(c.type))
{
c.size = 0;
}
else
{
Field f;
try {
f = c.type.getField("SIZE");
c.size = (int) Math.ceil(((Integer) f.get(null)) / 8f);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
totalSize += c.size;
}
return totalSize; //we want size in bytes
}
public EncryptedRow()
{
this.id = -1;
}
/**
* Copies the data from other row to this row
*/
public void copyRow2(EncryptedRow row) {
System.arraycopy(row.data2, 0, data2, 0 , row.data2.length);
this.id = -1;
}
protected void clearFk(int pos)
{
setInt(pos, Integer.MIN_VALUE);
}
/**
* Returns the actual length of data. Note we can't just use data2.length because
* the decryption utilities will demand more space then actual result, so the
* length of data2 will be greater than the actual length when decrypting.
*/
protected abstract int getDataLength();
protected int getInt(int pos) {
return Util.byteArrayToInt(data2, pos);
}
protected int getInt(Column c)
{
return Util.byteArrayToInt(data2, c.pos);
}
protected byte getByte(Column c)
{
return data2[c.pos];
}
protected long getLong(Column c) {
return Util.byteArrayToLong(data2, c.pos);
}
protected double getDouble(Column c) {
return Double.longBitsToDouble(Util.byteArrayToLong(data2, c.pos));
}
protected float getFloat(Column c) {
return Float.intBitsToFloat(Util.byteArrayToInt(data2, c.pos));
}
public final void decryptRow(int userDataKeyFk, byte [] encryptedData) {
GpsTrailerCrypt c = GpsTrailerCrypt.instance(userDataKeyFk);
int dataLength = c.getDecryptedSize(encryptedData.length);
if(data2 == null || data2.length < dataLength)
data2 = new byte [dataLength];
try {
c.crypt.decryptData(data2, encryptedData);
}
catch(Exception e)
{
Log.e(GTG.TAG,"Decryption failed for row "+this.id,e);
Log.e(GTG.TAG,"... row is "+this.toString(),e);
}
}
public final byte [] encryptRow(byte [] outEncryptedData) {
GTG.crypt.crypt.encryptData(outEncryptedData, 0, data2, 0, getDataLength());
return outEncryptedData;
}
public final byte[] encryptRowWithEncodedUserDataKey(byte [] outEncryptedData) {
Util.intToByteArray2(GTG.crypt.userDataKeyId, outEncryptedData, 0);
GTG.crypt.crypt.encryptData(outEncryptedData, (Integer.SIZE>>3), data2, 0, getDataLength());
return outEncryptedData;
}
public final void decryptRowWithEncodedUserDatakey(byte [] encryptedDataWUD)
{
int userDataKeyFk = Util.byteArrayToInt(encryptedDataWUD, 0);
GpsTrailerCrypt c = GpsTrailerCrypt.instance(userDataKeyFk);
int dataLength = c.getDecryptedSize(encryptedDataWUD.length - (Integer.SIZE>>3));
if(data2 == null)
data2 = new byte [dataLength];
c.crypt.decryptData(data2, encryptedDataWUD, (Integer.SIZE>>3), encryptedDataWUD.length - (Integer.SIZE>>3));
}
protected abstract Cache getCache();
protected void setInt(int pos, int val) {
if(getCache() != null)
getCache().notifyRowUpdated((CachableRow) this);
Util.intToByteArray2(val, data2, pos);
}
protected void setByte(int pos, byte val) {
if(getCache() != null)
getCache().notifyRowUpdated((CachableRow) this);
data2[pos] = val;
}
protected void setFloat(int pos, float val) {
if(getCache() != null)
getCache().notifyRowUpdated((CachableRow) this);
Util.floatToByteArray2(val, data2, pos);
}
protected void setLong(int pos, long val) {
if(getCache() != null)
getCache().notifyRowUpdated((CachableRow) this);
Util.longToByteArray2(val, data2, pos);
}
protected void setString(int pos, String input, int maxLength) {
byte [] in = input.getBytes();
if(in.length >= maxLength)
throw new IllegalStateException("Can't set string, too long, max is "+maxLength
+", got "+input.length());
if(maxLength > 255)
{
throw new IllegalStateException("max length can only be up to 255");
}
data2[pos] = (byte) in.length;
System.arraycopy(in, 0, data2, pos+1, in.length);
}
public String getString(Column column)
{
int length = data2[column.pos];
return new String(data2, column.pos+1, length);
}
protected void setDouble(int pos, double val) {
if(getCache() != null)
getCache().notifyRowUpdated((CachableRow) this);
Util.doubleToByteArray2(val, data2, pos);
}
public boolean isSameAs(EncryptedRow ap) {
if(ap.id != id)
return false;
for(int i = 0; i < ap.data2.length; i++)
{
if(data2[i] != ap.data2[i])
return false;
}
return true;
}
public String toString()
{
StringBuffer sb = new StringBuffer(this.getClass().getSimpleName()+"(id="+id+")");
return sb.toString();
}
}