Server POW should work now

This commit is contained in:
Christian Basler 2015-12-21 15:31:48 +01:00
parent b0828ec1e5
commit 41f4571bf6
26 changed files with 804 additions and 226 deletions

View File

@ -29,6 +29,7 @@ dependencies {
compile 'ch.dissem.jabit:jabit-domain:0.2.1-SNAPSHOT'
compile 'ch.dissem.jabit:jabit-networking:0.2.1-SNAPSHOT'
compile 'ch.dissem.jabit:jabit-security-spongy:0.2.1-SNAPSHOT'
compile 'ch.dissem.jabit:jabit-extensions:0.2.1-SNAPSHOT'
compile 'org.slf4j:slf4j-android:1.7.12'

View File

@ -8,6 +8,7 @@ CREATE TABLE Message (
sent INTEGER,
received INTEGER,
status VARCHAR(20) NOT NULL,
initial_hash BINARY(64) UNIQUE,
FOREIGN KEY (sender) REFERENCES Address (address),
FOREIGN KEY (recipient) REFERENCES Address (address)

View File

@ -0,0 +1,7 @@
-- This is done in V1.2, as SQLite doesn't support ADD CONSTRAINT and a proper migration
-- wasn't really necessary yet.
--
-- This file is here to reduce confusion regarding to the original migration files.
--ALTER TABLE Message ADD COLUMN initial_hash BINARY(64);
--ALTER TABLE Message ADD CONSTRAINT initial_hash_unique UNIQUE(initial_hash);

View File

@ -0,0 +1,7 @@
CREATE TABLE POW (
initial_hash BINARY(64) PRIMARY KEY,
data BLOB NOT NULL,
version BIGINT NOT NULL,
nonce_trials_per_byte BIGINT NOT NULL,
extra_bytes BIGINT NOT NULL
);

View File

@ -50,6 +50,8 @@ import ch.dissem.apps.abit.listener.ListSelectionListener;
import ch.dissem.apps.abit.service.BitmessageService;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.synchronization.Authenticator;
import ch.dissem.apps.abit.synchronization.SyncAdapter;
import ch.dissem.apps.abit.util.Preferences;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.Label;
@ -85,7 +87,6 @@ public class MainActivity extends AppCompatActivity
public static final String ACTION_SHOW_INBOX = "ch.dissem.abit.ShowInbox";
private static final Logger LOG = LoggerFactory.getLogger(MainActivity.class);
private static final long SYNC_FREQUENCY = 15 * 60; // seconds
private static final int ADD_IDENTITY = 1;
/**
@ -134,7 +135,8 @@ public class MainActivity extends AppCompatActivity
setSupportActionBar(toolbar);
MessageListFragment listFragment = new MessageListFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.item_list, listFragment).commit();
getSupportFragmentManager().beginTransaction().replace(R.id.item_list, listFragment)
.commit();
if (findViewById(R.id.message_detail_container) != null) {
// The detail container view will be present only in the
@ -157,21 +159,10 @@ public class MainActivity extends AppCompatActivity
onItemSelected(getIntent().getSerializableExtra(EXTRA_SHOW_MESSAGE));
}
createSyncAccount();
}
private void createSyncAccount() {
// Create account, if it's missing. (Either first run, or user has deleted account.)
Account account = new Account(Authenticator.ACCOUNT_NAME, Authenticator.ACCOUNT_TYPE);
if (AccountManager.get(this).addAccountExplicitly(account, null, null)) {
// Inform the system that this account supports sync
ContentResolver.setIsSyncable(account, AUTHORITY, 1);
// Inform the system that this account is eligible for auto sync when the network is up
ContentResolver.setSyncAutomatically(account, AUTHORITY, true);
// Recommend a schedule for automatic synchronization. The system may modify this based
// on other scheduled syncs and network utilization.
ContentResolver.addPeriodicSync(account, AUTHORITY, new Bundle(), SYNC_FREQUENCY);
if (Preferences.useTrustedNode(this)) {
SyncAdapter.startSync(this);
} else {
SyncAdapter.stopSync(this);
}
}
@ -220,10 +211,12 @@ public class MainActivity extends AppCompatActivity
.withProfiles(profiles)
.withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() {
@Override
public boolean onProfileChanged(View view, IProfile profile, boolean currentProfile) {
public boolean onProfileChanged(View view, IProfile profile, boolean
currentProfile) {
if (profile.getIdentifier() == ADD_IDENTITY) {
try {
Message message = Message.obtain(null, BitmessageService.MSG_CREATE_IDENTITY);
Message message = Message.obtain(null, BitmessageService
.MSG_CREATE_IDENTITY);
message.replyTo = messenger;
service.send(message);
} catch (RemoteException e) {
@ -240,14 +233,15 @@ public class MainActivity extends AppCompatActivity
}
})
.build();
if (profiles.size() > 0) {
if (profiles.size() > 2) { // There's always the add and manage identity items
accountHeader.setActiveProfile(profiles.get(0), true);
}
incomingHandler.updateAccountHeader(accountHeader);
ArrayList<IDrawerItem> drawerItems = new ArrayList<>();
for (Label label : labels) {
PrimaryDrawerItem item = new PrimaryDrawerItem().withName(label.toString()).withTag(label);
PrimaryDrawerItem item = new PrimaryDrawerItem().withName(label.toString()).withTag
(label);
if (label.getType() == null) {
item.withIcon(CommunityMaterial.Icon.cmd_label);
} else {
@ -301,17 +295,21 @@ public class MainActivity extends AppCompatActivity
.withChecked(BitmessageService.isRunning())
.withOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(IDrawerItem drawerItem, CompoundButton buttonView, boolean isChecked) {
public void onCheckedChanged(IDrawerItem drawerItem,
CompoundButton buttonView,
boolean isChecked) {
if (messenger != null) {
if (isChecked) {
try {
service.send(Message.obtain(null, MSG_START_NODE));
service.send(Message.obtain(null,
MSG_START_NODE));
} catch (RemoteException e) {
LOG.error(e.getMessage(), e);
}
} else {
try {
service.send(Message.obtain(null, MSG_STOP_NODE));
service.send(Message.obtain(null,
MSG_STOP_NODE));
} catch (RemoteException e) {
LOG.error(e.getMessage(), e);
}
@ -322,7 +320,8 @@ public class MainActivity extends AppCompatActivity
)
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
@Override
public boolean onItemClick(AdapterView<?> adapterView, View view, int i, long l, IDrawerItem item) {
public boolean onItemClick(AdapterView<?> adapterView, View view, int i, long
l, IDrawerItem item) {
if (item.getTag() instanceof Label) {
selectedLabel = (Label) item.getTag();
showSelectedLabel();
@ -331,15 +330,18 @@ public class MainActivity extends AppCompatActivity
Nameable<?> ni = (Nameable<?>) item;
switch (ni.getNameRes()) {
case R.string.contacts_and_subscriptions:
if (!(getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof SubscriptionListFragment)) {
if (!(getSupportFragmentManager().findFragmentById(R.id
.item_list) instanceof SubscriptionListFragment)) {
changeList(new SubscriptionListFragment());
} else {
((SubscriptionListFragment) getSupportFragmentManager()
.findFragmentById(R.id.item_list)).updateList();
}
break;
case R.string.settings:
startActivity(new Intent(MainActivity.this, SettingsActivity.class));
startActivity(new Intent(MainActivity.this, SettingsActivity
.class));
break;
case R.string.archive:
selectedLabel = null;
@ -357,7 +359,8 @@ public class MainActivity extends AppCompatActivity
}
private void showSelectedLabel() {
if (getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof MessageListFragment) {
if (getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof
MessageListFragment) {
((MessageListFragment) getSupportFragmentManager()
.findFragmentById(R.id.item_list)).updateList(selectedLabel);
} else {
@ -385,7 +388,8 @@ public class MainActivity extends AppCompatActivity
else if (item instanceof BitmessageAddress)
fragment = new SubscriptionDetailFragment();
else
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but was "
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " +
"was "
+ item.getClass().getSimpleName());
fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction()
@ -400,7 +404,8 @@ public class MainActivity extends AppCompatActivity
else if (item instanceof BitmessageAddress)
detailIntent = new Intent(this, SubscriptionDetailActivity.class);
else
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but was "
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " +
"was "
+ item.getClass().getSimpleName());
detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item);
@ -421,7 +426,8 @@ public class MainActivity extends AppCompatActivity
@Override
protected void onStart() {
super.onStart();
bindService(new Intent(this, BitmessageService.class), connection, Context.BIND_AUTO_CREATE);
bindService(new Intent(this, BitmessageService.class), connection, Context
.BIND_AUTO_CREATE);
}
@Override
@ -463,8 +469,10 @@ public class MainActivity extends AppCompatActivity
.withEmail(identity.getAddress())
.withTag(identity);
if (accountHeader.getProfiles() != null) {
//we know that there are 2 setting elements. set the new profile above them ;)
accountHeader.addProfile(newProfile, accountHeader.getProfiles().size() - 2);
//we know that there are 2 setting elements. set the new profile
// above them ;)
accountHeader.addProfile(newProfile, accountHeader.getProfiles().size
() - 2);
} else {
accountHeader.addProfiles(newProfile);
}

View File

@ -43,7 +43,6 @@ import ch.dissem.bitmessage.entity.BitmessageAddress;
import static ch.dissem.apps.abit.service.BitmessageService.DATA_FIELD_ADDRESS;
import static ch.dissem.apps.abit.service.BitmessageService.MSG_ADD_CONTACT;
import static ch.dissem.apps.abit.service.BitmessageService.MSG_SUBSCRIBE;
import static ch.dissem.apps.abit.service.BitmessageService.MSG_SUBSCRIBE_AND_ADD_CONTACT;
public class OpenBitmessageLinkActivity extends AppCompatActivity {
private static final Logger LOG = LoggerFactory.getLogger(OpenBitmessageLinkActivity.class);
@ -106,7 +105,7 @@ public class OpenBitmessageLinkActivity extends AppCompatActivity {
final int what;
if (subscribe.isChecked())
what = MSG_SUBSCRIBE_AND_ADD_CONTACT;
what = MSG_SUBSCRIBE;
else
what = MSG_ADD_CONTACT;
@ -155,7 +154,8 @@ public class OpenBitmessageLinkActivity extends AppCompatActivity {
@Override
protected void onStart() {
super.onStart();
bindService(new Intent(this, BitmessageService.class), connection, Context.BIND_AUTO_CREATE);
bindService(new Intent(this, BitmessageService.class), connection, Context
.BIND_AUTO_CREATE);
}
@Override

View File

@ -5,7 +5,7 @@ import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
/**
* Created by chris on 14.07.15.
* @author Christian Basler
*/
public class SettingsActivity extends AppCompatActivity {
@Override

View File

@ -1,13 +1,22 @@
package ch.dissem.apps.abit;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import ch.dissem.apps.abit.synchronization.SyncAdapter;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE;
/**
* Created by chris on 14.07.15.
* @author Christian Basler
*/
public class SettingsFragment extends PreferenceFragment {
public class SettingsFragment
extends PreferenceFragment
implements SharedPreferences.OnSharedPreferenceChangeListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -15,4 +24,32 @@ public class SettingsFragment extends PreferenceFragment {
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences);
}
@Override
public void onAttach(Context ctx) {
super.onAttach(ctx);
PreferenceManager.getDefaultSharedPreferences(ctx)
.registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
switch (key) {
case PREFERENCE_TRUSTED_NODE:
String node = sharedPreferences.getString(PREFERENCE_TRUSTED_NODE, null);
if (node != null) {
SyncAdapter.startSync(getActivity());
} else {
SyncAdapter.stopSync(getActivity());
}
break;
case PREFERENCE_SERVER_POW:
if (sharedPreferences.getBoolean(PREFERENCE_SERVER_POW, false)) {
SyncAdapter.startPowSync(getActivity());
} else {
SyncAdapter.stopPowSync(getActivity());
}
break;
}
}
}

View File

@ -16,6 +16,7 @@
package ch.dissem.apps.abit;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
@ -25,6 +26,7 @@ import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import ch.dissem.apps.abit.listener.ActionBarListener;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.valueobject.Label;
@ -100,12 +102,18 @@ public class SubscriptionListFragment extends AbstractItemListFragment<Bitmessag
});
}
@Override
public void onAttach(Context ctx) {
super.onAttach(ctx);
if (ctx instanceof ActionBarListener){
((ActionBarListener) ctx).updateTitle(getString(R.string.contacts_and_subscriptions));
}
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_contact_list, container, false);
return rootView;
return inflater.inflate(R.layout.fragment_contact_list, container, false);
}
@Override

View File

@ -28,57 +28,7 @@ import static ch.dissem.bitmessage.utils.UnixTime.DAY;
* @author Christian Basler
*/
public class AndroidSecurity extends SpongySecurity {
private final SharedPreferences preferences;
public AndroidSecurity(Context ctx) {
public AndroidSecurity() {
PRNGFixes.apply();
preferences = PreferenceManager.getDefaultSharedPreferences(ctx);
}
@Override
public void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes, ProofOfWorkEngine.Callback callback) {
if (preferences.getBoolean(PREFERENCE_SERVER_POW, false)) {
object.setNonce(new byte[8]);
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
object.write(out);
sendAsBroadcast(getContext().getAddressRepo().getIdentities().get(0), out.toByteArray());
if (object.getPayload() instanceof PlaintextHolder) {
Plaintext plaintext = ((PlaintextHolder) object.getPayload()).getPlaintext();
plaintext.setInventoryVector(object.getInventoryVector());
plaintext.setStatus(SENT);
plaintext.removeLabel(Label.Type.OUTBOX);
plaintext.addLabels(getContext().getMessageRepository().getLabels(Label.Type.SENT));
getContext().getMessageRepository().save(plaintext);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
super.doProofOfWork(object, nonceTrialsPerByte, extraBytes, callback);
}
}
private void sendAsBroadcast(BitmessageAddress identity, byte[] data) throws IOException {
Plaintext msg = new Plaintext.Builder(BROADCAST)
.from(identity)
.message(data)
.build();
Broadcast payload = Factory.getBroadcast(identity, msg);
long expires = UnixTime.now(+2 * DAY);
final ObjectMessage object = new ObjectMessage.Builder()
.stream(identity.getStream())
.expiresTime(expires)
.payload(payload)
.build();
object.sign(identity.getPrivateKey());
payload.encrypt();
object.setNonce(new byte[8]);
getContext().getInventory().storeObject(object);
getContext().getNetworkHandler().offer(object.getInventoryVector());
// TODO: offer to the trusted node only?
// at least make sure it is offered to the trusted node!
}
}

View File

@ -0,0 +1,52 @@
package ch.dissem.apps.abit.adapter;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import java.util.Arrays;
import ch.dissem.apps.abit.util.Preferences;
import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW;
/**
* Switches between two {@link ProofOfWorkEngine}s depending on the configuration.
*
* @author Christian Basler
*/
public class SwitchingProofOfWorkEngine implements ProofOfWorkEngine, InternalContext.ContextHolder {
private final Context ctx;
private final String preference;
private final ProofOfWorkEngine option;
private final ProofOfWorkEngine fallback;
public SwitchingProofOfWorkEngine(Context ctx, String preference,
ProofOfWorkEngine option, ProofOfWorkEngine fallback) {
this.ctx = ctx;
this.preference = preference;
this.option = option;
this.fallback = fallback;
}
@Override
public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx);
if (preferences.getBoolean(preference, false)) {
option.calculateNonce(initialHash, target, callback);
} else {
fallback.calculateNonce(initialHash, target, callback);
}
}
@Override
public void setContext(InternalContext context) {
for (ProofOfWorkEngine e : Arrays.asList(option, fallback)) {
if (e instanceof InternalContext.ContextHolder) {
((InternalContext.ContextHolder) e).setContext(context);
}
}
}
}

View File

@ -0,0 +1,81 @@
package ch.dissem.apps.abit.pow;
import android.content.Context;
import android.support.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.synchronization.SyncAdapter;
import ch.dissem.apps.abit.util.Preferences;
import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.extensions.CryptoCustomMessage;
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest;
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
import static ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.CALCULATE;
import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* @author Christian Basler
*/
public class ServerPowEngine implements ProofOfWorkEngine, InternalContext
.ContextHolder {
private static final Logger LOG = LoggerFactory.getLogger(ServerPowEngine.class);
private final Context ctx;
private InternalContext context;
private final ExecutorService pool;
public ServerPowEngine(Context ctx) {
this.ctx = ctx;
pool = Executors.newCachedThreadPool(new ThreadFactory() {
@Override
public Thread newThread(@NonNull Runnable r) {
Thread thread = Executors.defaultThreadFactory().newThread(r);
thread.setPriority(Thread.MIN_PRIORITY);
return thread;
}
});
}
@Override
public void calculateNonce(final byte[] initialHash, final byte[] target, Callback callback) {
pool.execute(new Runnable() {
@Override
public void run() {
BitmessageAddress identity = Singleton.getIdentity(ctx);
if (identity == null) throw new RuntimeException("No Identity for calculating POW");
ProofOfWorkRequest request = new ProofOfWorkRequest(identity, initialHash,
CALCULATE, target);
SyncAdapter.startPowSync(ctx);
try {
CryptoCustomMessage<ProofOfWorkRequest> cryptoMsg = new CryptoCustomMessage<>
(request);
cryptoMsg.signAndEncrypt(
identity,
security().createPublicKey(identity.getPublicDecryptionKey())
);
context.getNetworkHandler().send(
Preferences.getTrustedNode(ctx), Preferences.getTrustedNodePort(ctx),
cryptoMsg);
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
}
});
}
@Override
public void setContext(InternalContext context) {
this.context = context;
}
}

View File

@ -19,6 +19,7 @@ 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;
@ -27,6 +28,7 @@ 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;
@ -118,27 +120,30 @@ public class AndroidAddressRepository implements AddressRepository {
COLUMN_SUBSCRIBED
};
SQLiteDatabase db = sql.getReadableDatabase();
Cursor c = db.query(
TABLE_NAME, projection,
where,
null, null, null, null
);
try {
SQLiteDatabase db = sql.getReadableDatabase();
Cursor c = db.query(
TABLE_NAME, projection,
where,
null, null, null, null
);
c.moveToFirst();
while (!c.isAfterLast()) {
BitmessageAddress address;
byte[] privateKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PRIVATE_KEY));
if (privateKeyBytes != null) {
PrivateKey privateKey = PrivateKey.read(new ByteArrayInputStream(privateKeyBytes));
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);
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);
}
@ -153,8 +158,9 @@ public class AndroidAddressRepository implements AddressRepository {
}
} catch (IOException e) {
LOG.error(e.getMessage(), e);
} finally {
c.close();
}
return result;
}
@ -173,9 +179,14 @@ public class AndroidAddressRepository implements AddressRepository {
private boolean exists(BitmessageAddress address) {
SQLiteDatabase db = sql.getReadableDatabase();
Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM Address WHERE address='" + address.getAddress() + "'", null);
cursor.moveToFirst();
return cursor.getInt(0) > 0;
Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM Address WHERE address='" + address
.getAddress() + "'", null);
try {
cursor.moveToFirst();
return cursor.getInt(0) > 0;
} finally {
cursor.close();
}
}
private void update(BitmessageAddress address) throws IOException {
@ -194,7 +205,8 @@ public class AndroidAddressRepository implements AddressRepository {
values.put(COLUMN_PRIVATE_KEY, Encode.bytes(address.getPrivateKey()));
values.put(COLUMN_SUBSCRIBED, address.isSubscribed());
int update = db.update(TABLE_NAME, values, "address = '" + address.getAddress() + "'", null);
int update = db.update(TABLE_NAME, values, "address = '" + address.getAddress() +
"'", null);
if (update < 0) {
LOG.error("Could not update address " + address);
}

View File

@ -74,15 +74,20 @@ public class AndroidInventory implements Inventory {
SQLiteDatabase db = sql.getReadableDatabase();
Cursor c = db.query(
TABLE_NAME, projection,
(includeExpired ? "" : "expires > " + now() + " AND ") + "stream IN (" + join(streams) + ")",
(includeExpired ? "" : "expires > " + now() + " AND ") + "stream IN (" + join
(streams) + ")",
null, null, null, null
);
c.moveToFirst();
List<InventoryVector> result = new LinkedList<>();
while (!c.isAfterLast()) {
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_HASH));
result.add(new InventoryVector(blob));
c.moveToNext();
try {
c.moveToFirst();
while (!c.isAfterLast()) {
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_HASH));
result.add(new InventoryVector(blob));
c.moveToNext();
}
} finally {
c.close();
}
return result;
}
@ -108,15 +113,19 @@ public class AndroidInventory implements Inventory {
"hash = X'" + vector + "'",
null, null, null, null
);
c.moveToFirst();
if (c.isAfterLast()) {
LOG.info("Object requested that we don't have. IV: " + vector);
return null;
}
try {
c.moveToFirst();
if (c.isAfterLast()) {
LOG.info("Object requested that we don't have. IV: " + vector);
return null;
}
int version = c.getInt(c.getColumnIndex(COLUMN_VERSION));
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
return Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob.length);
int version = c.getInt(c.getColumnIndex(COLUMN_VERSION));
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
return Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob.length);
} finally {
c.close();
}
}
@Override
@ -144,13 +153,18 @@ public class AndroidInventory implements Inventory {
where.toString(),
null, null, null, null
);
c.moveToFirst();
List<ObjectMessage> result = new LinkedList<>();
while (!c.isAfterLast()) {
int objectVersion = c.getInt(c.getColumnIndex(COLUMN_VERSION));
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
result.add(Factory.getObjectMessage(objectVersion, new ByteArrayInputStream(blob), blob.length));
c.moveToNext();
try {
c.moveToFirst();
while (!c.isAfterLast()) {
int objectVersion = c.getInt(c.getColumnIndex(COLUMN_VERSION));
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
result.add(Factory.getObjectMessage(objectVersion, new ByteArrayInputStream(blob),
blob.length));
c.moveToNext();
}
} finally {
c.close();
}
return result;
}
@ -187,7 +201,11 @@ public class AndroidInventory implements Inventory {
"hash = X'" + object.getInventoryVector() + "'",
null, null, null, null
);
return c.getCount() > 0;
try {
return c.getCount() > 0;
} finally {
c.close();
}
}
@Override

View File

@ -30,6 +30,7 @@ import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.ports.MessageRepository;
import ch.dissem.bitmessage.utils.Encode;
import ch.dissem.bitmessage.utils.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -58,6 +59,7 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
private static final String COLUMN_SENT = "sent";
private static final String COLUMN_RECEIVED = "received";
private static final String COLUMN_STATUS = "status";
private static final String COLUMN_INITIAL_HASH = "initial_hash";
private static final String JOIN_TABLE_NAME = "Message_Label";
private static final String JT_COLUMN_MESSAGE = "message_id";
@ -112,10 +114,14 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
null, null, null,
LBL_COLUMN_ORDER
);
c.moveToFirst();
while (!c.isAfterLast()) {
result.add(getLabel(c));
c.moveToNext();
try {
c.moveToFirst();
while (!c.isAfterLast()) {
result.add(getLabel(c));
c.moveToNext();
}
} finally {
c.close();
}
return result;
}
@ -124,27 +130,31 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
String typeName = c.getString(c.getColumnIndex(LBL_COLUMN_TYPE));
Label.Type type = typeName == null ? null : Label.Type.valueOf(typeName);
String text;
switch (type) {
case INBOX:
text = ctx.getString(R.string.inbox);
break;
case DRAFT:
text = ctx.getString(R.string.draft);
break;
case SENT:
text = ctx.getString(R.string.sent);
break;
case UNREAD:
text = ctx.getString(R.string.unread);
break;
case TRASH:
text = ctx.getString(R.string.trash);
break;
case BROADCAST:
text = ctx.getString(R.string.broadcasts);
break;
default:
text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL));
if (type == null) {
text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL));
} else {
switch (type) {
case INBOX:
text = ctx.getString(R.string.inbox);
break;
case DRAFT:
text = ctx.getString(R.string.draft);
break;
case SENT:
text = ctx.getString(R.string.sent);
break;
case UNREAD:
text = ctx.getString(R.string.unread);
break;
case TRASH:
text = ctx.getString(R.string.trash);
break;
case BROADCAST:
text = ctx.getString(R.string.broadcasts);
break;
default:
text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL));
}
}
Label label = new Label(
text,
@ -158,7 +168,8 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
public int countUnread(Label label) {
String where;
if (label != null) {
where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ") AND ";
where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId()
+ ") AND ";
} else {
where = "";
}
@ -169,13 +180,32 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
"SELECT id FROM Label WHERE type = '" + Label.Type.UNREAD.name() + "'))",
null, null, null, null
);
return c.getColumnCount();
try {
return c.getColumnCount();
} finally {
c.close();
}
}
@Override
public Plaintext getMessage(byte[] initialHash) {
List<Plaintext> results = find("initial_hash=X'" + Strings.hex(initialHash) + "'");
switch (results.size()) {
case 0:
return null;
case 1:
return results.get(0);
default:
throw new RuntimeException("This shouldn't happen, found " + results.size() +
" messages, one or none was expected");
}
}
@Override
public List<Plaintext> findMessages(Label label) {
if (label != null) {
return find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ")");
return find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label
.getId() + ")");
} else {
return find("id NOT IN (SELECT message_id FROM Message_Label)");
}
@ -183,7 +213,8 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
@Override
public List<Plaintext> findMessages(Plaintext.Status status, BitmessageAddress recipient) {
return find("status='" + status.name() + "' AND recipient='" + recipient.getAddress() + "'");
return find("status='" + status.name() + "' AND recipient='" + recipient.getAddress() +
"'");
}
@Override
@ -213,34 +244,41 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
COLUMN_STATUS
};
SQLiteDatabase db = sql.getReadableDatabase();
Cursor c = db.query(
TABLE_NAME, projection,
where,
null, null, null,
COLUMN_RECEIVED + " DESC"
);
try {
SQLiteDatabase db = sql.getReadableDatabase();
Cursor c = db.query(
TABLE_NAME, projection,
where,
null, null, null,
COLUMN_RECEIVED + " DESC"
);
c.moveToFirst();
while (!c.isAfterLast()) {
byte[] iv = c.getBlob(c.getColumnIndex(COLUMN_IV));
byte[] data = c.getBlob(c.getColumnIndex(COLUMN_DATA));
Plaintext.Type type = Plaintext.Type.valueOf(c.getString(c.getColumnIndex(COLUMN_TYPE)));
Plaintext.Builder builder = Plaintext.readWithoutSignature(type, new ByteArrayInputStream(data));
Plaintext.Type type = Plaintext.Type.valueOf(c.getString(c.getColumnIndex
(COLUMN_TYPE)));
Plaintext.Builder builder = Plaintext.readWithoutSignature(type, new
ByteArrayInputStream(data));
long id = c.getLong(c.getColumnIndex(COLUMN_ID));
builder.id(id);
builder.IV(new InventoryVector(iv));
builder.from(bmc.getAddressRepo().getAddress(c.getString(c.getColumnIndex(COLUMN_SENDER))));
builder.to(bmc.getAddressRepo().getAddress(c.getString(c.getColumnIndex(COLUMN_RECIPIENT))));
builder.from(bmc.getAddressRepository().getAddress(c.getString(c.getColumnIndex
(COLUMN_SENDER))));
builder.to(bmc.getAddressRepository().getAddress(c.getString(c.getColumnIndex
(COLUMN_RECIPIENT))));
builder.sent(c.getLong(c.getColumnIndex(COLUMN_SENT)));
builder.received(c.getLong(c.getColumnIndex(COLUMN_RECEIVED)));
builder.status(Plaintext.Status.valueOf(c.getString(c.getColumnIndex(COLUMN_STATUS))));
builder.status(Plaintext.Status.valueOf(c.getString(c.getColumnIndex
(COLUMN_STATUS))));
builder.labels(findLabels(id));
result.add(builder.build());
c.moveToNext();
}
} catch (IOException e) {
LOG.error(e.getMessage(), e);
} finally {
c.close();
}
return result;
}
@ -257,12 +295,13 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
// save from address if necessary
if (message.getId() == null) {
BitmessageAddress savedAddress = bmc.getAddressRepo().getAddress(message.getFrom().getAddress());
BitmessageAddress savedAddress = bmc.getAddressRepository().getAddress(message
.getFrom().getAddress());
if (savedAddress == null || savedAddress.getPrivateKey() == null) {
if (savedAddress != null && savedAddress.getAlias() != null) {
message.getFrom().setAlias(savedAddress.getAlias());
}
bmc.getAddressRepo().save(message.getFrom());
bmc.getAddressRepository().save(message.getFrom());
}
}
@ -295,7 +334,8 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
private void insert(SQLiteDatabase db, Plaintext message) throws IOException {
ContentValues values = new ContentValues();
values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message.getInventoryVector().getHash());
values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message
.getInventoryVector().getHash());
values.put(COLUMN_TYPE, message.getType().name());
values.put(COLUMN_SENDER, message.getFrom().getAddress());
values.put(COLUMN_RECIPIENT, message.getTo() == null ? null : message.getTo().getAddress());
@ -303,13 +343,15 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
values.put(COLUMN_SENT, message.getSent());
values.put(COLUMN_RECEIVED, message.getReceived());
values.put(COLUMN_STATUS, message.getStatus() == null ? null : message.getStatus().name());
values.put(COLUMN_INITIAL_HASH, message.getInitialHash());
long id = db.insertOrThrow(TABLE_NAME, null, values);
message.setId(id);
}
private void update(SQLiteDatabase db, Plaintext message) throws IOException {
ContentValues values = new ContentValues();
values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message.getInventoryVector().getHash());
values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message
.getInventoryVector().getHash());
values.put(COLUMN_TYPE, message.getType().name());
values.put(COLUMN_SENDER, message.getFrom().getAddress());
values.put(COLUMN_RECIPIENT, message.getTo() == null ? null : message.getTo().getAddress());
@ -317,6 +359,7 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
values.put(COLUMN_SENT, message.getSent());
values.put(COLUMN_RECEIVED, message.getReceived());
values.put(COLUMN_STATUS, message.getStatus() == null ? null : message.getStatus().name());
values.put(COLUMN_INITIAL_HASH, message.getInitialHash());
db.update(TABLE_NAME, values, "id = " + message.getId(), null);
}

View File

@ -0,0 +1,133 @@
package ch.dissem.apps.abit.repository;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
import ch.dissem.bitmessage.utils.Encode;
import ch.dissem.bitmessage.utils.Strings;
import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* @author Christian Basler
*/
public class AndroidProofOfWorkRepository implements ProofOfWorkRepository {
private static final Logger LOG = LoggerFactory.getLogger(AndroidProofOfWorkRepository.class);
private static final String TABLE_NAME = "POW";
private static final String COLUMN_INITIAL_HASH = "initial_hash";
private static final String COLUMN_DATA = "data";
private static final String COLUMN_VERSION = "version";
private static final String COLUMN_NONCE_TRIALS_PER_BYTE = "nonce_trials_per_byte";
private static final String COLUMN_EXTRA_BYTES = "extra_bytes";
private final SqlHelper sql;
public AndroidProofOfWorkRepository(SqlHelper sql) {
this.sql = sql;
}
@Override
public Item getItem(byte[] initialHash) {
// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
COLUMN_DATA,
COLUMN_VERSION,
COLUMN_NONCE_TRIALS_PER_BYTE,
COLUMN_EXTRA_BYTES
};
SQLiteDatabase db = sql.getReadableDatabase();
Cursor c = db.query(
TABLE_NAME, projection,
"initial_hash = X'" + Strings.hex(initialHash) + "'",
null, null, null, null
);
try {
c.moveToFirst();
if (!c.isAfterLast()) {
int version = c.getInt(c.getColumnIndex(COLUMN_VERSION));
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
return new Item(
Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob
.length),
c.getLong(c.getColumnIndex(COLUMN_NONCE_TRIALS_PER_BYTE)),
c.getLong(c.getColumnIndex(COLUMN_EXTRA_BYTES))
);
}
} finally {
c.close();
}
throw new RuntimeException("Object requested that we don't have. Initial hash: " +
Strings.hex(initialHash));
}
@Override
public List<byte[]> getItems() {
// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
COLUMN_INITIAL_HASH
};
SQLiteDatabase db = sql.getReadableDatabase();
Cursor c = db.query(
TABLE_NAME, projection,
null, null, null, null, null
);
List<byte[]> result = new LinkedList<>();
try {
c.moveToFirst();
while (!c.isAfterLast()) {
byte[] initialHash = c.getBlob(c.getColumnIndex(COLUMN_INITIAL_HASH));
result.add(initialHash);
c.moveToNext();
}
} finally {
c.close();
}
return result;
}
@Override
public void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
try {
SQLiteDatabase db = sql.getWritableDatabase();
// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
values.put(COLUMN_INITIAL_HASH, security().getInitialHash(object));
values.put(COLUMN_DATA, Encode.bytes(object));
values.put(COLUMN_VERSION, object.getVersion());
values.put(COLUMN_NONCE_TRIALS_PER_BYTE, nonceTrialsPerByte);
values.put(COLUMN_EXTRA_BYTES, extraBytes);
db.insertOrThrow(TABLE_NAME, null, values);
} catch (SQLiteConstraintException e) {
LOG.trace(e.getMessage(), e);
} catch (IOException e) {
LOG.error(e.getMessage(), e);
}
}
@Override
public void removeObject(byte[] initialHash) {
SQLiteDatabase db = sql.getWritableDatabase();
db.delete(TABLE_NAME,
"initial_hash = X'" + Strings.hex(initialHash) + "'",
null);
}
}

View File

@ -26,7 +26,7 @@ import ch.dissem.apps.abit.util.Assets;
*/
public class SqlHelper extends SQLiteOpenHelper {
// If you change the database schema, you must increment the database version.
public static final int DATABASE_VERSION = 1;
public static final int DATABASE_VERSION = 2;
public static final String DATABASE_NAME = "jabit.db";
protected final Context ctx;
@ -38,7 +38,7 @@ public class SqlHelper extends SQLiteOpenHelper {
@Override
public void onCreate(SQLiteDatabase db) {
onUpgrade(db, 0, 1);
onUpgrade(db, 0, 2);
}
@Override
@ -48,6 +48,9 @@ public class SqlHelper extends SQLiteOpenHelper {
executeMigration(db, "V1.0__Create_table_inventory");
executeMigration(db, "V1.1__Create_table_address");
executeMigration(db, "V1.2__Create_table_message");
case 1:
// executeMigration(db, "V2.0__Update_table_message");
executeMigration(db, "V2.1__Create_table_POW");
default:
// Nothing to do. Let's assume we won't upgrade from a version that's newer than DATABASE_VERSION.
}

View File

@ -34,7 +34,6 @@ public class BitmessageService extends Service {
public static final int MSG_CREATE_IDENTITY = 10;
public static final int MSG_SUBSCRIBE = 20;
public static final int MSG_ADD_CONTACT = 21;
public static final int MSG_SUBSCRIBE_AND_ADD_CONTACT = 23;
public static final int MSG_SEND_MESSAGE = 30;
public static final int MSG_SEND_BROADCAST = 31;
public static final int MSG_START_NODE = 100;
@ -122,6 +121,13 @@ public class BitmessageService extends Service {
}
break;
}
case MSG_ADD_CONTACT: {
Serializable data = msg.getData().getSerializable(DATA_FIELD_ADDRESS);
if (data instanceof BitmessageAddress) {
bmc.addContact((BitmessageAddress) data);
}
break;
}
case MSG_SEND_MESSAGE: {
Serializable identity = msg.getData().getSerializable(DATA_FIELD_IDENTITY);
Serializable address = msg.getData().getSerializable(DATA_FIELD_ADDRESS);

View File

@ -73,9 +73,9 @@ public class ProofOfWorkService extends Service {
service.startForeground(ONGOING_NOTIFICATION_ID, notification.getNotification());
engine.calculateNonce(initialHash, target, new ProofOfWorkEngine.Callback() {
@Override
public void onNonceCalculated(byte[] nonce) {
public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
try {
callback.onNonceCalculated(nonce);
callback.onNonceCalculated(initialHash, nonce);
} finally {
service.stopForeground(true);
service.stopSelf();

View File

@ -53,8 +53,8 @@ public class ServicePowEngine implements ProofOfWorkEngine, ProofOfWorkEngine.Ca
}
@Override
public void onNonceCalculated(byte[] bytes) {
callback.onNonceCalculated(bytes);
public void onNonceCalculated(byte[] initialHash, byte[] bytes) {
callback.onNonceCalculated(initialHash, bytes);
ctx.unbindService(connection);
}

View File

@ -2,18 +2,27 @@ package ch.dissem.apps.abit.service;
import android.content.Context;
import java.util.List;
import ch.dissem.apps.abit.adapter.AndroidSecurity;
import ch.dissem.apps.abit.adapter.SwitchingProofOfWorkEngine;
import ch.dissem.apps.abit.listener.MessageListener;
import ch.dissem.apps.abit.pow.ServerPowEngine;
import ch.dissem.apps.abit.repository.AndroidAddressRepository;
import ch.dissem.apps.abit.repository.AndroidInventory;
import ch.dissem.apps.abit.repository.AndroidMessageRepository;
import ch.dissem.apps.abit.repository.AndroidProofOfWorkRepository;
import ch.dissem.apps.abit.repository.SqlHelper;
import ch.dissem.apps.abit.util.Constants;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
import ch.dissem.bitmessage.ports.AddressRepository;
import ch.dissem.bitmessage.ports.MemoryNodeRegistry;
import ch.dissem.bitmessage.ports.MessageRepository;
import ch.dissem.bitmessage.security.sc.SpongySecurity;
import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
/**
* Provides singleton objects across the application.
@ -22,6 +31,8 @@ public class Singleton {
public static final Object lock = new Object();
private static BitmessageContext bitmessageContext;
private static MessageListener messageListener;
private static BitmessageAddress identity;
private static AndroidProofOfWorkRepository powRepo;
public static BitmessageContext getBitmessageContext(Context context) {
if (bitmessageContext == null) {
@ -29,15 +40,23 @@ public class Singleton {
if (bitmessageContext == null) {
final Context ctx = context.getApplicationContext();
SqlHelper sqlHelper = new SqlHelper(ctx);
powRepo = new AndroidProofOfWorkRepository(sqlHelper);
bitmessageContext = new BitmessageContext.Builder()
.proofOfWorkEngine(new ServicePowEngine(ctx))
.security(new AndroidSecurity(ctx))
.proofOfWorkEngine(new SwitchingProofOfWorkEngine(
ctx, Constants.PREFERENCE_SERVER_POW,
new ServerPowEngine(ctx),
new ServicePowEngine(ctx)
))
.security(new AndroidSecurity())
.nodeRegistry(new MemoryNodeRegistry())
.inventory(new AndroidInventory(sqlHelper))
.addressRepo(new AndroidAddressRepository(sqlHelper))
.messageRepo(new AndroidMessageRepository(sqlHelper, ctx))
.powRepo(powRepo)
.networkHandler(new DefaultNetworkHandler())
.listener(getMessageListener(ctx))
.doNotSendPubkeyOnIdentityCreation()
.pubkeyTTL(2 * DAY)
.build();
}
}
@ -63,4 +82,24 @@ public class Singleton {
public static AddressRepository getAddressRepository(Context ctx) {
return getBitmessageContext(ctx).addresses();
}
public static ProofOfWorkRepository getProofOfWorkRepository(Context ctx) {
if (powRepo == null) getBitmessageContext(ctx);
return powRepo;
}
public static BitmessageAddress getIdentity(Context ctx) {
if (identity == null) {
synchronized (Singleton.class) {
if (identity == null) {
List<BitmessageAddress> identities = getBitmessageContext(ctx).addresses()
.getIdentities();
if (identities.size() > 0) {
identity = identities.get(0);
}
}
}
}
return identity;
}
}

View File

@ -12,8 +12,8 @@ import android.os.Bundle;
* of its methods
*/
public class Authenticator extends AbstractAccountAuthenticator {
public static final String ACCOUNT_NAME = "Bitmessage";
public static final String ACCOUNT_TYPE = "ch.dissem.bitmessage";
public static final Account ACCOUNT_SYNC = new Account("Bitmessage", "ch.dissem.bitmessage");
public static final Account ACCOUNT_POW = new Account("Proof of Work ", "ch.dissem.bitmessage");
// Simple constructor
public Authenticator(Context context) {

View File

@ -1,27 +1,34 @@
package ch.dissem.apps.abit.synchronization;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SyncResult;
import android.os.Bundle;
import android.preference.PreferenceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
import ch.dissem.apps.abit.R;
import ch.dissem.apps.abit.notification.ErrorNotification;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.util.Preferences;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.CustomMessage;
import ch.dissem.bitmessage.extensions.CryptoCustomMessage;
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest;
import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SYNC_TIMEOUT;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE;
import static ch.dissem.apps.abit.synchronization.Authenticator.ACCOUNT_POW;
import static ch.dissem.apps.abit.synchronization.Authenticator.ACCOUNT_SYNC;
import static ch.dissem.apps.abit.synchronization.StubProvider.AUTHORITY;
import static ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.CALCULATE;
import static ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.COMPLETE;
import static ch.dissem.bitmessage.utils.Singleton.security;
/**
* Sync Adapter to synchronize with the Bitmessage network - fetches
@ -30,6 +37,8 @@ import static ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE;
public class SyncAdapter extends AbstractThreadedSyncAdapter {
private final static Logger LOG = LoggerFactory.getLogger(SyncAdapter.class);
private static final long SYNC_FREQUENCY = 15 * 60; // seconds
private final BitmessageContext bmc;
/**
@ -41,7 +50,17 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
}
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
public void onPerformSync(Account account, Bundle extras, String authority,
ContentProviderClient provider, SyncResult syncResult) {
if (account.equals(Authenticator.ACCOUNT_SYNC))
syncData();
else if (account.equals(Authenticator.ACCOUNT_POW))
syncPOW();
else
throw new RuntimeException("Unknown " + account);
}
private void syncData() {
// If the Bitmessage context acts as a full node, synchronization isn't necessary
if (bmc.isRunning()) {
LOG.info("Synchronization skipped, Abit is acting as a full node");
@ -49,40 +68,103 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
}
LOG.info("Synchronizing Bitmessage");
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getContext());
String trustedNode = preferences.getString(PREFERENCE_TRUSTED_NODE, null);
if (trustedNode == null) return;
trustedNode = trustedNode.trim();
if (trustedNode.isEmpty()) return;
int port;
if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$")) {
int index = trustedNode.lastIndexOf(':');
String portString = trustedNode.substring(index + 1);
trustedNode = trustedNode.substring(0, index);
try {
port = Integer.parseInt(portString);
} catch (NumberFormatException e) {
new ErrorNotification(getContext())
.setError(R.string.error_invalid_sync_port, portString)
.show();
return;
}
} else {
port = 8444;
}
long timeoutInSeconds = Long.parseLong(preferences.getString(PREFERENCE_SYNC_TIMEOUT, "120"));
try {
LOG.info("Synchronization started");
bmc.synchronize(InetAddress.getByName(trustedNode), port, timeoutInSeconds, true);
bmc.synchronize(
Preferences.getTrustedNode(getContext()),
Preferences.getTrustedNodePort(getContext()),
Preferences.getTimeoutInSeconds(getContext()),
true);
LOG.info("Synchronization finished");
} catch (UnknownHostException e) {
new ErrorNotification(getContext())
.setError(R.string.error_invalid_sync_host)
.show();
} catch (RuntimeException e) {
LOG.error(e.getMessage(), e);
}
}
private void syncPOW() {
// If the Bitmessage context acts as a full node, synchronization isn't necessary
LOG.info("Looking for completed POW");
try {
BitmessageAddress identity = Singleton.getIdentity(getContext());
byte[] privateKey = identity.getPrivateKey().getPrivateEncryptionKey();
byte[] signingKey = security().createPublicKey(identity.getPublicDecryptionKey());
ProofOfWorkRequest.Reader reader = new ProofOfWorkRequest.Reader(identity);
ProofOfWorkRepository powRepo = Singleton.getProofOfWorkRepository(getContext());
List<byte[]> items = powRepo.getItems();
for (byte[] initialHash : items) {
ProofOfWorkRepository.Item item = powRepo.getItem(initialHash);
byte[] target = security().getProofOfWorkTarget(item.object, item
.nonceTrialsPerByte, item.extraBytes);
CryptoCustomMessage<ProofOfWorkRequest> cryptoMsg = new CryptoCustomMessage<>(
new ProofOfWorkRequest(identity, initialHash, CALCULATE, target));
cryptoMsg.signAndEncrypt(identity, signingKey);
CustomMessage response = bmc.send(
Preferences.getTrustedNode(getContext()),
Preferences.getTrustedNodePort(getContext()),
cryptoMsg
);
if (response.isError()) {
LOG.error("Server responded with error: " + new String(response.getData(),
"UTF-8"));
} else {
ProofOfWorkRequest decryptedResponse = CryptoCustomMessage.read(
response, reader).decrypt(privateKey);
if (decryptedResponse.getRequest() == COMPLETE) {
bmc.internals().getProofOfWorkService().onNonceCalculated(
initialHash, decryptedResponse.getData());
}
}
}
if (items.size() == 0) {
stopPowSync(getContext());
}
LOG.info("Synchronization finished");
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
}
public static void startSync(Context ctx) {
// Create account, if it's missing. (Either first run, or user has deleted account.)
Account account = addAccount(ctx, ACCOUNT_SYNC);
// Recommend a schedule for automatic synchronization. The system may modify this based
// on other scheduled syncs and network utilization.
ContentResolver.addPeriodicSync(account, AUTHORITY, new Bundle(), SYNC_FREQUENCY);
}
public static void stopSync(Context ctx) {
// Create account, if it's missing. (Either first run, or user has deleted account.)
Account account = addAccount(ctx, ACCOUNT_SYNC);
ContentResolver.removePeriodicSync(account, AUTHORITY, new Bundle());
}
public static void startPowSync(Context ctx) {
// Create account, if it's missing. (Either first run, or user has deleted account.)
Account account = addAccount(ctx, ACCOUNT_POW);
// Recommend a schedule for automatic synchronization. The system may modify this based
// on other scheduled syncs and network utilization.
ContentResolver.addPeriodicSync(account, AUTHORITY, new Bundle(), SYNC_FREQUENCY);
}
public static void stopPowSync(Context ctx) {
// Create account, if it's missing. (Either first run, or user has deleted account.)
Account account = addAccount(ctx, ACCOUNT_POW);
ContentResolver.removePeriodicSync(account, AUTHORITY, new Bundle());
}
private static Account addAccount(Context ctx, Account account) {
if (AccountManager.get(ctx).addAccountExplicitly(account, null, null)) {
// Inform the system that this account supports sync
ContentResolver.setIsSyncable(account, AUTHORITY, 1);
// Inform the system that this account is eligible for auto sync when the network is up
ContentResolver.setSyncAutomatically(account, AUTHORITY, true);
}
return account;
}
}

View File

@ -0,0 +1,90 @@
package ch.dissem.apps.abit.util;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetAddress;
import java.net.UnknownHostException;
import ch.dissem.apps.abit.R;
import ch.dissem.apps.abit.notification.ErrorNotification;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SYNC_TIMEOUT;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE;
/**
* Created by chrig on 01.12.2015.
*/
public class Preferences {
private static Logger LOG = LoggerFactory.getLogger(Preferences.class);
public static boolean useTrustedNode(Context ctx) {
String trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE);
return trustedNode == null || trustedNode.trim().isEmpty();
}
/**
* Warning, this method might do a network call and therefore can't be called from
* the UI thread.
*/
public static InetAddress getTrustedNode(Context ctx) {
String trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE);
if (trustedNode == null) return null;
trustedNode = trustedNode.trim();
if (trustedNode.isEmpty()) return null;
if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$")) {
int index = trustedNode.lastIndexOf(':');
trustedNode = trustedNode.substring(0, index);
}
try {
return InetAddress.getByName(trustedNode);
} catch (UnknownHostException e) {
new ErrorNotification(ctx)
.setError(R.string.error_invalid_sync_host)
.show();
LOG.error(e.getMessage(), e);
return null;
}
}
public static int getTrustedNodePort(Context ctx) {
String trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE);
if (trustedNode == null) return 8444;
trustedNode = trustedNode.trim();
if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$")) {
int index = trustedNode.lastIndexOf(':');
String portString = trustedNode.substring(index + 1);
try {
return Integer.parseInt(portString);
} catch (NumberFormatException e) {
new ErrorNotification(ctx)
.setError(R.string.error_invalid_sync_port, portString)
.show();
}
}
return 8444;
}
public static long getTimeoutInSeconds(Context ctx) {
String preference = getPreference(ctx, PREFERENCE_SYNC_TIMEOUT);
return preference == null ? 120 : Long.parseLong(preference);
}
public static boolean isServerPOW(Context ctx) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx);
return preferences.getBoolean(PREFERENCE_SERVER_POW, false);
}
private static String getPreference(Context ctx, String name) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx);
return preferences.getString(name, null);
}
}

View File

@ -27,7 +27,7 @@
<SwitchPreference
android:defaultValue="false"
android:key="server_pow"
android:dependency="@string/trusted_node"
android:dependency="trusted_node"
android:title="@string/server_pow"
android:summary="@string/server_pow_summary"
/>

View File

@ -9,7 +9,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.5.0-beta1'
classpath 'com.android.tools.build:gradle:1.5.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files