Abit/app/src/main/java/ch/dissem/apps/abit/repository/AndroidAddressRepository.java

262 lines
9.5 KiB
Java

/*
* Copyright 2015 Christian Basler
*
* 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 ch.dissem.apps.abit.repository;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.V3Pubkey;
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.AddressRepository;
import ch.dissem.bitmessage.utils.Encode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
/**
* {@link AddressRepository} implementation using the Android SQL API.
*/
public class AndroidAddressRepository implements AddressRepository {
private static final Logger LOG = LoggerFactory.getLogger(AndroidAddressRepository.class);
private static final String TABLE_NAME = "Address";
private static final String COLUMN_ADDRESS = "address";
private static final String COLUMN_VERSION = "version";
private static final String COLUMN_ALIAS = "alias";
private static final String COLUMN_PUBLIC_KEY = "public_key";
private static final String COLUMN_PRIVATE_KEY = "private_key";
private static final String COLUMN_SUBSCRIBED = "subscribed";
private static final String COLUMN_CHAN = "chan";
private final SqlHelper sql;
public AndroidAddressRepository(SqlHelper sql) {
this.sql = sql;
}
@Override
public BitmessageAddress findContact(byte[] ripeOrTag) {
for (BitmessageAddress address : find("public_key is null")) {
if (address.getVersion() > 3) {
if (Arrays.equals(ripeOrTag, address.getTag())) return address;
} else {
if (Arrays.equals(ripeOrTag, address.getRipe())) return address;
}
}
return null;
}
@Override
public BitmessageAddress findIdentity(byte[] ripeOrTag) {
for (BitmessageAddress address : find("private_key is not null")) {
if (address.getVersion() > 3) {
if (Arrays.equals(ripeOrTag, address.getTag())) return address;
} else {
if (Arrays.equals(ripeOrTag, address.getRipe())) return address;
}
}
return null;
}
@Override
public List<BitmessageAddress> getIdentities() {
return find("private_key IS NOT NULL");
}
@Override
public List<BitmessageAddress> getChans() {
return find("chan = '1'");
}
@Override
public List<BitmessageAddress> getSubscriptions() {
return find("subscribed = '1'");
}
@Override
public List<BitmessageAddress> getSubscriptions(long broadcastVersion) {
if (broadcastVersion > 4) {
return find("subscribed = '1' AND version > 3");
} else {
return find("subscribed = '1' AND version <= 3");
}
}
@Override
public List<BitmessageAddress> getContacts() {
return find("private_key IS NULL OR chan = '1'");
}
private List<BitmessageAddress> find(String where) {
List<BitmessageAddress> result = new LinkedList<>();
// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
COLUMN_ADDRESS,
COLUMN_ALIAS,
COLUMN_PUBLIC_KEY,
COLUMN_PRIVATE_KEY,
COLUMN_SUBSCRIBED,
COLUMN_CHAN
};
SQLiteDatabase db = sql.getReadableDatabase();
try (Cursor c = db.query(
TABLE_NAME, projection,
where,
null, null, null, null
)) {
while (c.moveToNext()) {
BitmessageAddress address;
byte[] privateKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PRIVATE_KEY));
if (privateKeyBytes != null) {
PrivateKey privateKey = PrivateKey.read(new ByteArrayInputStream
(privateKeyBytes));
address = new BitmessageAddress(privateKey);
} else {
address = new BitmessageAddress(c.getString(c.getColumnIndex(COLUMN_ADDRESS)));
byte[] publicKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PUBLIC_KEY));
if (publicKeyBytes != null) {
Pubkey pubkey = Factory.readPubkey(address.getVersion(), address
.getStream(),
new ByteArrayInputStream(publicKeyBytes), publicKeyBytes.length,
false);
if (address.getVersion() == 4 && pubkey instanceof V3Pubkey) {
pubkey = new V4Pubkey((V3Pubkey) pubkey);
}
address.setPubkey(pubkey);
}
}
address.setAlias(c.getString(c.getColumnIndex(COLUMN_ALIAS)));
address.setChan(c.getInt(c.getColumnIndex(COLUMN_CHAN)) == 1);
address.setSubscribed(c.getInt(c.getColumnIndex(COLUMN_SUBSCRIBED)) == 1);
result.add(address);
}
} catch (IOException e) {
LOG.error(e.getMessage(), e);
}
return result;
}
@Override
public void save(BitmessageAddress address) {
if (exists(address)) {
update(address);
} else {
insert(address);
}
}
private boolean exists(BitmessageAddress address) {
SQLiteDatabase db = sql.getReadableDatabase();
try (Cursor cursor = db.rawQuery(
"SELECT COUNT(*) FROM Address WHERE address=?",
new String[]{address.getAddress()}
)) {
cursor.moveToFirst();
return cursor.getInt(0) > 0;
}
}
private void update(BitmessageAddress address) {
try {
SQLiteDatabase db = sql.getWritableDatabase();
// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
if (address.getAlias() != null) {
values.put(COLUMN_ALIAS, address.getAlias());
}
if (address.getPubkey() != null) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
address.getPubkey().writeUnencrypted(out);
values.put(COLUMN_PUBLIC_KEY, out.toByteArray());
}
if (address.getPrivateKey() != null) {
values.put(COLUMN_PRIVATE_KEY, Encode.bytes(address.getPrivateKey()));
}
if (address.isChan()) {
values.put(COLUMN_CHAN, true);
}
values.put(COLUMN_SUBSCRIBED, address.isSubscribed());
int update = db.update(TABLE_NAME, values, "address=?",
new String[]{address.getAddress()});
if (update < 0) {
LOG.error("Could not update address " + address);
}
} catch (IOException e) {
LOG.error(e.getMessage(), e);
}
}
private void insert(BitmessageAddress address) {
try {
SQLiteDatabase db = sql.getWritableDatabase();
// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
values.put(COLUMN_ADDRESS, address.getAddress());
values.put(COLUMN_VERSION, address.getVersion());
values.put(COLUMN_ALIAS, address.getAlias());
if (address.getPubkey() != null) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
address.getPubkey().writeUnencrypted(out);
values.put(COLUMN_PUBLIC_KEY, out.toByteArray());
} else {
values.put(COLUMN_PUBLIC_KEY, (byte[]) null);
}
values.put(COLUMN_PRIVATE_KEY, Encode.bytes(address.getPrivateKey()));
values.put(COLUMN_CHAN, address.isChan());
values.put(COLUMN_SUBSCRIBED, address.isSubscribed());
long insert = db.insert(TABLE_NAME, null, values);
if (insert < 0) {
LOG.error("Could not insert address " + address);
}
} catch (IOException e) {
LOG.error(e.getMessage(), e);
}
}
@Override
public void remove(BitmessageAddress address) {
SQLiteDatabase db = sql.getWritableDatabase();
db.delete(TABLE_NAME, "address = ?", new String[]{address.getAddress()});
}
@Override
public BitmessageAddress getAddress(String address) {
List<BitmessageAddress> result = find("address = '" + address + "'");
if (result.size() > 0) return result.get(0);
return null;
}
}