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-domain:0.2.1-SNAPSHOT'
compile 'ch.dissem.jabit:jabit-networking: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-security-spongy:0.2.1-SNAPSHOT'
compile 'ch.dissem.jabit:jabit-extensions:0.2.1-SNAPSHOT'
compile 'org.slf4j:slf4j-android:1.7.12' compile 'org.slf4j:slf4j-android:1.7.12'

View File

@ -8,6 +8,7 @@ CREATE TABLE Message (
sent INTEGER, sent INTEGER,
received INTEGER, received INTEGER,
status VARCHAR(20) NOT NULL, status VARCHAR(20) NOT NULL,
initial_hash BINARY(64) UNIQUE,
FOREIGN KEY (sender) REFERENCES Address (address), FOREIGN KEY (sender) REFERENCES Address (address),
FOREIGN KEY (recipient) 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.BitmessageService;
import ch.dissem.apps.abit.service.Singleton; import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.synchronization.Authenticator; 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.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.Label; 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"; public static final String ACTION_SHOW_INBOX = "ch.dissem.abit.ShowInbox";
private static final Logger LOG = LoggerFactory.getLogger(MainActivity.class); 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; private static final int ADD_IDENTITY = 1;
/** /**
@ -134,7 +135,8 @@ public class MainActivity extends AppCompatActivity
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
MessageListFragment listFragment = new MessageListFragment(); 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) { if (findViewById(R.id.message_detail_container) != null) {
// The detail container view will be present only in the // 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)); onItemSelected(getIntent().getSerializableExtra(EXTRA_SHOW_MESSAGE));
} }
createSyncAccount(); if (Preferences.useTrustedNode(this)) {
} SyncAdapter.startSync(this);
} else {
private void createSyncAccount() { SyncAdapter.stopSync(this);
// 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);
} }
} }
@ -220,10 +211,12 @@ public class MainActivity extends AppCompatActivity
.withProfiles(profiles) .withProfiles(profiles)
.withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() { .withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() {
@Override @Override
public boolean onProfileChanged(View view, IProfile profile, boolean currentProfile) { public boolean onProfileChanged(View view, IProfile profile, boolean
currentProfile) {
if (profile.getIdentifier() == ADD_IDENTITY) { if (profile.getIdentifier() == ADD_IDENTITY) {
try { try {
Message message = Message.obtain(null, BitmessageService.MSG_CREATE_IDENTITY); Message message = Message.obtain(null, BitmessageService
.MSG_CREATE_IDENTITY);
message.replyTo = messenger; message.replyTo = messenger;
service.send(message); service.send(message);
} catch (RemoteException e) { } catch (RemoteException e) {
@ -240,14 +233,15 @@ public class MainActivity extends AppCompatActivity
} }
}) })
.build(); .build();
if (profiles.size() > 0) { if (profiles.size() > 2) { // There's always the add and manage identity items
accountHeader.setActiveProfile(profiles.get(0), true); accountHeader.setActiveProfile(profiles.get(0), true);
} }
incomingHandler.updateAccountHeader(accountHeader); incomingHandler.updateAccountHeader(accountHeader);
ArrayList<IDrawerItem> drawerItems = new ArrayList<>(); ArrayList<IDrawerItem> drawerItems = new ArrayList<>();
for (Label label : labels) { 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) { if (label.getType() == null) {
item.withIcon(CommunityMaterial.Icon.cmd_label); item.withIcon(CommunityMaterial.Icon.cmd_label);
} else { } else {
@ -301,17 +295,21 @@ public class MainActivity extends AppCompatActivity
.withChecked(BitmessageService.isRunning()) .withChecked(BitmessageService.isRunning())
.withOnCheckedChangeListener(new OnCheckedChangeListener() { .withOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override @Override
public void onCheckedChanged(IDrawerItem drawerItem, CompoundButton buttonView, boolean isChecked) { public void onCheckedChanged(IDrawerItem drawerItem,
CompoundButton buttonView,
boolean isChecked) {
if (messenger != null) { if (messenger != null) {
if (isChecked) { if (isChecked) {
try { try {
service.send(Message.obtain(null, MSG_START_NODE)); service.send(Message.obtain(null,
MSG_START_NODE));
} catch (RemoteException e) { } catch (RemoteException e) {
LOG.error(e.getMessage(), e); LOG.error(e.getMessage(), e);
} }
} else { } else {
try { try {
service.send(Message.obtain(null, MSG_STOP_NODE)); service.send(Message.obtain(null,
MSG_STOP_NODE));
} catch (RemoteException e) { } catch (RemoteException e) {
LOG.error(e.getMessage(), e); LOG.error(e.getMessage(), e);
} }
@ -322,7 +320,8 @@ public class MainActivity extends AppCompatActivity
) )
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() { .withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
@Override @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) { if (item.getTag() instanceof Label) {
selectedLabel = (Label) item.getTag(); selectedLabel = (Label) item.getTag();
showSelectedLabel(); showSelectedLabel();
@ -331,15 +330,18 @@ public class MainActivity extends AppCompatActivity
Nameable<?> ni = (Nameable<?>) item; Nameable<?> ni = (Nameable<?>) item;
switch (ni.getNameRes()) { switch (ni.getNameRes()) {
case R.string.contacts_and_subscriptions: 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()); changeList(new SubscriptionListFragment());
} else { } else {
((SubscriptionListFragment) getSupportFragmentManager() ((SubscriptionListFragment) getSupportFragmentManager()
.findFragmentById(R.id.item_list)).updateList(); .findFragmentById(R.id.item_list)).updateList();
} }
break; break;
case R.string.settings: case R.string.settings:
startActivity(new Intent(MainActivity.this, SettingsActivity.class)); startActivity(new Intent(MainActivity.this, SettingsActivity
.class));
break; break;
case R.string.archive: case R.string.archive:
selectedLabel = null; selectedLabel = null;
@ -357,7 +359,8 @@ public class MainActivity extends AppCompatActivity
} }
private void showSelectedLabel() { private void showSelectedLabel() {
if (getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof MessageListFragment) { if (getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof
MessageListFragment) {
((MessageListFragment) getSupportFragmentManager() ((MessageListFragment) getSupportFragmentManager()
.findFragmentById(R.id.item_list)).updateList(selectedLabel); .findFragmentById(R.id.item_list)).updateList(selectedLabel);
} else { } else {
@ -385,7 +388,8 @@ public class MainActivity extends AppCompatActivity
else if (item instanceof BitmessageAddress) else if (item instanceof BitmessageAddress)
fragment = new SubscriptionDetailFragment(); fragment = new SubscriptionDetailFragment();
else else
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but was " throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " +
"was "
+ item.getClass().getSimpleName()); + item.getClass().getSimpleName());
fragment.setArguments(arguments); fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
@ -400,7 +404,8 @@ public class MainActivity extends AppCompatActivity
else if (item instanceof BitmessageAddress) else if (item instanceof BitmessageAddress)
detailIntent = new Intent(this, SubscriptionDetailActivity.class); detailIntent = new Intent(this, SubscriptionDetailActivity.class);
else else
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but was " throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " +
"was "
+ item.getClass().getSimpleName()); + item.getClass().getSimpleName());
detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item); detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item);
@ -421,7 +426,8 @@ public class MainActivity extends AppCompatActivity
@Override @Override
protected void onStart() { protected void onStart() {
super.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 @Override
@ -463,8 +469,10 @@ public class MainActivity extends AppCompatActivity
.withEmail(identity.getAddress()) .withEmail(identity.getAddress())
.withTag(identity); .withTag(identity);
if (accountHeader.getProfiles() != null) { if (accountHeader.getProfiles() != null) {
//we know that there are 2 setting elements. set the new profile above them ;) //we know that there are 2 setting elements. set the new profile
accountHeader.addProfile(newProfile, accountHeader.getProfiles().size() - 2); // above them ;)
accountHeader.addProfile(newProfile, accountHeader.getProfiles().size
() - 2);
} else { } else {
accountHeader.addProfiles(newProfile); 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.DATA_FIELD_ADDRESS;
import static ch.dissem.apps.abit.service.BitmessageService.MSG_ADD_CONTACT; 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;
import static ch.dissem.apps.abit.service.BitmessageService.MSG_SUBSCRIBE_AND_ADD_CONTACT;
public class OpenBitmessageLinkActivity extends AppCompatActivity { public class OpenBitmessageLinkActivity extends AppCompatActivity {
private static final Logger LOG = LoggerFactory.getLogger(OpenBitmessageLinkActivity.class); private static final Logger LOG = LoggerFactory.getLogger(OpenBitmessageLinkActivity.class);
@ -106,7 +105,7 @@ public class OpenBitmessageLinkActivity extends AppCompatActivity {
final int what; final int what;
if (subscribe.isChecked()) if (subscribe.isChecked())
what = MSG_SUBSCRIBE_AND_ADD_CONTACT; what = MSG_SUBSCRIBE;
else else
what = MSG_ADD_CONTACT; what = MSG_ADD_CONTACT;
@ -155,7 +154,8 @@ public class OpenBitmessageLinkActivity extends AppCompatActivity {
@Override @Override
protected void onStart() { protected void onStart() {
super.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 @Override

View File

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

View File

@ -1,13 +1,22 @@
package ch.dissem.apps.abit; package ch.dissem.apps.abit;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment; 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 @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -15,4 +24,32 @@ public class SettingsFragment extends PreferenceFragment {
// Load the preferences from an XML resource // Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences); 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; package ch.dissem.apps.abit;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -25,6 +26,7 @@ import android.widget.ArrayAdapter;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import ch.dissem.apps.abit.listener.ActionBarListener;
import ch.dissem.apps.abit.service.Singleton; import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.valueobject.Label; 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 @Nullable
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_contact_list, container, false); return inflater.inflate(R.layout.fragment_contact_list, container, false);
return rootView;
} }
@Override @Override

View File

@ -28,57 +28,7 @@ import static ch.dissem.bitmessage.utils.UnixTime.DAY;
* @author Christian Basler * @author Christian Basler
*/ */
public class AndroidSecurity extends SpongySecurity { public class AndroidSecurity extends SpongySecurity {
private final SharedPreferences preferences; public AndroidSecurity() {
public AndroidSecurity(Context ctx) {
PRNGFixes.apply(); 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.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.payload.Pubkey; import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.V3Pubkey; 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.factory.Factory;
import ch.dissem.bitmessage.ports.AddressRepository; import ch.dissem.bitmessage.ports.AddressRepository;
import ch.dissem.bitmessage.utils.Encode; import ch.dissem.bitmessage.utils.Encode;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -118,27 +120,30 @@ public class AndroidAddressRepository implements AddressRepository {
COLUMN_SUBSCRIBED COLUMN_SUBSCRIBED
}; };
try {
SQLiteDatabase db = sql.getReadableDatabase(); SQLiteDatabase db = sql.getReadableDatabase();
Cursor c = db.query( Cursor c = db.query(
TABLE_NAME, projection, TABLE_NAME, projection,
where, where,
null, null, null, null null, null, null, null
); );
try {
c.moveToFirst(); c.moveToFirst();
while (!c.isAfterLast()) { while (!c.isAfterLast()) {
BitmessageAddress address; BitmessageAddress address;
byte[] privateKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PRIVATE_KEY)); byte[] privateKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PRIVATE_KEY));
if (privateKeyBytes != null) { if (privateKeyBytes != null) {
PrivateKey privateKey = PrivateKey.read(new ByteArrayInputStream(privateKeyBytes)); PrivateKey privateKey = PrivateKey.read(new ByteArrayInputStream
(privateKeyBytes));
address = new BitmessageAddress(privateKey); address = new BitmessageAddress(privateKey);
} else { } else {
address = new BitmessageAddress(c.getString(c.getColumnIndex(COLUMN_ADDRESS))); address = new BitmessageAddress(c.getString(c.getColumnIndex(COLUMN_ADDRESS)));
byte[] publicKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PUBLIC_KEY)); byte[] publicKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PUBLIC_KEY));
if (publicKeyBytes != null) { if (publicKeyBytes != null) {
Pubkey pubkey = Factory.readPubkey(address.getVersion(), address.getStream(), Pubkey pubkey = Factory.readPubkey(address.getVersion(), address
new ByteArrayInputStream(publicKeyBytes), publicKeyBytes.length, false); .getStream(),
new ByteArrayInputStream(publicKeyBytes), publicKeyBytes.length,
false);
if (address.getVersion() == 4 && pubkey instanceof V3Pubkey) { if (address.getVersion() == 4 && pubkey instanceof V3Pubkey) {
pubkey = new V4Pubkey((V3Pubkey) pubkey); pubkey = new V4Pubkey((V3Pubkey) pubkey);
} }
@ -153,8 +158,9 @@ public class AndroidAddressRepository implements AddressRepository {
} }
} catch (IOException e) { } catch (IOException e) {
LOG.error(e.getMessage(), e); LOG.error(e.getMessage(), e);
} finally {
c.close();
} }
return result; return result;
} }
@ -173,9 +179,14 @@ public class AndroidAddressRepository implements AddressRepository {
private boolean exists(BitmessageAddress address) { private boolean exists(BitmessageAddress address) {
SQLiteDatabase db = sql.getReadableDatabase(); SQLiteDatabase db = sql.getReadableDatabase();
Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM Address WHERE address='" + address.getAddress() + "'", null); Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM Address WHERE address='" + address
.getAddress() + "'", null);
try {
cursor.moveToFirst(); cursor.moveToFirst();
return cursor.getInt(0) > 0; return cursor.getInt(0) > 0;
} finally {
cursor.close();
}
} }
private void update(BitmessageAddress address) throws IOException { 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_PRIVATE_KEY, Encode.bytes(address.getPrivateKey()));
values.put(COLUMN_SUBSCRIBED, address.isSubscribed()); 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) { if (update < 0) {
LOG.error("Could not update address " + address); LOG.error("Could not update address " + address);
} }

View File

@ -74,16 +74,21 @@ public class AndroidInventory implements Inventory {
SQLiteDatabase db = sql.getReadableDatabase(); SQLiteDatabase db = sql.getReadableDatabase();
Cursor c = db.query( Cursor c = db.query(
TABLE_NAME, projection, TABLE_NAME, projection,
(includeExpired ? "" : "expires > " + now() + " AND ") + "stream IN (" + join(streams) + ")", (includeExpired ? "" : "expires > " + now() + " AND ") + "stream IN (" + join
(streams) + ")",
null, null, null, null null, null, null, null
); );
c.moveToFirst();
List<InventoryVector> result = new LinkedList<>(); List<InventoryVector> result = new LinkedList<>();
try {
c.moveToFirst();
while (!c.isAfterLast()) { while (!c.isAfterLast()) {
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_HASH)); byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_HASH));
result.add(new InventoryVector(blob)); result.add(new InventoryVector(blob));
c.moveToNext(); c.moveToNext();
} }
} finally {
c.close();
}
return result; return result;
} }
@ -108,6 +113,7 @@ public class AndroidInventory implements Inventory {
"hash = X'" + vector + "'", "hash = X'" + vector + "'",
null, null, null, null null, null, null, null
); );
try {
c.moveToFirst(); c.moveToFirst();
if (c.isAfterLast()) { if (c.isAfterLast()) {
LOG.info("Object requested that we don't have. IV: " + vector); LOG.info("Object requested that we don't have. IV: " + vector);
@ -117,6 +123,9 @@ public class AndroidInventory implements Inventory {
int version = c.getInt(c.getColumnIndex(COLUMN_VERSION)); int version = c.getInt(c.getColumnIndex(COLUMN_VERSION));
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA)); byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
return Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob.length); return Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob.length);
} finally {
c.close();
}
} }
@Override @Override
@ -144,14 +153,19 @@ public class AndroidInventory implements Inventory {
where.toString(), where.toString(),
null, null, null, null null, null, null, null
); );
c.moveToFirst();
List<ObjectMessage> result = new LinkedList<>(); List<ObjectMessage> result = new LinkedList<>();
try {
c.moveToFirst();
while (!c.isAfterLast()) { while (!c.isAfterLast()) {
int objectVersion = c.getInt(c.getColumnIndex(COLUMN_VERSION)); int objectVersion = c.getInt(c.getColumnIndex(COLUMN_VERSION));
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA)); byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
result.add(Factory.getObjectMessage(objectVersion, new ByteArrayInputStream(blob), blob.length)); result.add(Factory.getObjectMessage(objectVersion, new ByteArrayInputStream(blob),
blob.length));
c.moveToNext(); c.moveToNext();
} }
} finally {
c.close();
}
return result; return result;
} }
@ -187,7 +201,11 @@ public class AndroidInventory implements Inventory {
"hash = X'" + object.getInventoryVector() + "'", "hash = X'" + object.getInventoryVector() + "'",
null, null, null, null null, null, null, null
); );
try {
return c.getCount() > 0; return c.getCount() > 0;
} finally {
c.close();
}
} }
@Override @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.entity.valueobject.Label;
import ch.dissem.bitmessage.ports.MessageRepository; import ch.dissem.bitmessage.ports.MessageRepository;
import ch.dissem.bitmessage.utils.Encode; import ch.dissem.bitmessage.utils.Encode;
import ch.dissem.bitmessage.utils.Strings;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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_SENT = "sent";
private static final String COLUMN_RECEIVED = "received"; private static final String COLUMN_RECEIVED = "received";
private static final String COLUMN_STATUS = "status"; 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 JOIN_TABLE_NAME = "Message_Label";
private static final String JT_COLUMN_MESSAGE = "message_id"; private static final String JT_COLUMN_MESSAGE = "message_id";
@ -112,11 +114,15 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
null, null, null, null, null, null,
LBL_COLUMN_ORDER LBL_COLUMN_ORDER
); );
try {
c.moveToFirst(); c.moveToFirst();
while (!c.isAfterLast()) { while (!c.isAfterLast()) {
result.add(getLabel(c)); result.add(getLabel(c));
c.moveToNext(); c.moveToNext();
} }
} finally {
c.close();
}
return result; return result;
} }
@ -124,6 +130,9 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
String typeName = c.getString(c.getColumnIndex(LBL_COLUMN_TYPE)); String typeName = c.getString(c.getColumnIndex(LBL_COLUMN_TYPE));
Label.Type type = typeName == null ? null : Label.Type.valueOf(typeName); Label.Type type = typeName == null ? null : Label.Type.valueOf(typeName);
String text; String text;
if (type == null) {
text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL));
} else {
switch (type) { switch (type) {
case INBOX: case INBOX:
text = ctx.getString(R.string.inbox); text = ctx.getString(R.string.inbox);
@ -146,6 +155,7 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
default: default:
text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL)); text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL));
} }
}
Label label = new Label( Label label = new Label(
text, text,
type, type,
@ -158,7 +168,8 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
public int countUnread(Label label) { public int countUnread(Label label) {
String where; String where;
if (label != null) { 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 { } else {
where = ""; where = "";
} }
@ -169,13 +180,32 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
"SELECT id FROM Label WHERE type = '" + Label.Type.UNREAD.name() + "'))", "SELECT id FROM Label WHERE type = '" + Label.Type.UNREAD.name() + "'))",
null, null, null, null null, null, null, null
); );
try {
return c.getColumnCount(); 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 @Override
public List<Plaintext> findMessages(Label label) { public List<Plaintext> findMessages(Label label) {
if (label != null) { 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 { } else {
return find("id NOT IN (SELECT message_id FROM Message_Label)"); return find("id NOT IN (SELECT message_id FROM Message_Label)");
} }
@ -183,7 +213,8 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
@Override @Override
public List<Plaintext> findMessages(Plaintext.Status status, BitmessageAddress recipient) { 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 @Override
@ -213,7 +244,6 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
COLUMN_STATUS COLUMN_STATUS
}; };
try {
SQLiteDatabase db = sql.getReadableDatabase(); SQLiteDatabase db = sql.getReadableDatabase();
Cursor c = db.query( Cursor c = db.query(
TABLE_NAME, projection, TABLE_NAME, projection,
@ -221,26 +251,34 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
null, null, null, null, null, null,
COLUMN_RECEIVED + " DESC" COLUMN_RECEIVED + " DESC"
); );
try {
c.moveToFirst(); c.moveToFirst();
while (!c.isAfterLast()) { while (!c.isAfterLast()) {
byte[] iv = c.getBlob(c.getColumnIndex(COLUMN_IV)); byte[] iv = c.getBlob(c.getColumnIndex(COLUMN_IV));
byte[] data = c.getBlob(c.getColumnIndex(COLUMN_DATA)); byte[] data = c.getBlob(c.getColumnIndex(COLUMN_DATA));
Plaintext.Type type = Plaintext.Type.valueOf(c.getString(c.getColumnIndex(COLUMN_TYPE))); Plaintext.Type type = Plaintext.Type.valueOf(c.getString(c.getColumnIndex
Plaintext.Builder builder = Plaintext.readWithoutSignature(type, new ByteArrayInputStream(data)); (COLUMN_TYPE)));
Plaintext.Builder builder = Plaintext.readWithoutSignature(type, new
ByteArrayInputStream(data));
long id = c.getLong(c.getColumnIndex(COLUMN_ID)); long id = c.getLong(c.getColumnIndex(COLUMN_ID));
builder.id(id); builder.id(id);
builder.IV(new InventoryVector(iv)); builder.IV(new InventoryVector(iv));
builder.from(bmc.getAddressRepo().getAddress(c.getString(c.getColumnIndex(COLUMN_SENDER)))); builder.from(bmc.getAddressRepository().getAddress(c.getString(c.getColumnIndex
builder.to(bmc.getAddressRepo().getAddress(c.getString(c.getColumnIndex(COLUMN_RECIPIENT)))); (COLUMN_SENDER))));
builder.to(bmc.getAddressRepository().getAddress(c.getString(c.getColumnIndex
(COLUMN_RECIPIENT))));
builder.sent(c.getLong(c.getColumnIndex(COLUMN_SENT))); builder.sent(c.getLong(c.getColumnIndex(COLUMN_SENT)));
builder.received(c.getLong(c.getColumnIndex(COLUMN_RECEIVED))); 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)); builder.labels(findLabels(id));
result.add(builder.build()); result.add(builder.build());
c.moveToNext(); c.moveToNext();
} }
} catch (IOException e) { } catch (IOException e) {
LOG.error(e.getMessage(), e); LOG.error(e.getMessage(), e);
} finally {
c.close();
} }
return result; return result;
} }
@ -257,12 +295,13 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
// save from address if necessary // save from address if necessary
if (message.getId() == null) { 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.getPrivateKey() == null) {
if (savedAddress != null && savedAddress.getAlias() != null) { if (savedAddress != null && savedAddress.getAlias() != null) {
message.getFrom().setAlias(savedAddress.getAlias()); 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 { private void insert(SQLiteDatabase db, Plaintext message) throws IOException {
ContentValues values = new ContentValues(); 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_TYPE, message.getType().name());
values.put(COLUMN_SENDER, message.getFrom().getAddress()); values.put(COLUMN_SENDER, message.getFrom().getAddress());
values.put(COLUMN_RECIPIENT, message.getTo() == null ? null : message.getTo().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_SENT, message.getSent());
values.put(COLUMN_RECEIVED, message.getReceived()); values.put(COLUMN_RECEIVED, message.getReceived());
values.put(COLUMN_STATUS, message.getStatus() == null ? null : message.getStatus().name()); 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); long id = db.insertOrThrow(TABLE_NAME, null, values);
message.setId(id); message.setId(id);
} }
private void update(SQLiteDatabase db, Plaintext message) throws IOException { private void update(SQLiteDatabase db, Plaintext message) throws IOException {
ContentValues values = new ContentValues(); 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_TYPE, message.getType().name());
values.put(COLUMN_SENDER, message.getFrom().getAddress()); values.put(COLUMN_SENDER, message.getFrom().getAddress());
values.put(COLUMN_RECIPIENT, message.getTo() == null ? null : message.getTo().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_SENT, message.getSent());
values.put(COLUMN_RECEIVED, message.getReceived()); values.put(COLUMN_RECEIVED, message.getReceived());
values.put(COLUMN_STATUS, message.getStatus() == null ? null : message.getStatus().name()); 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); 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 { public class SqlHelper extends SQLiteOpenHelper {
// If you change the database schema, you must increment the database version. // 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"; public static final String DATABASE_NAME = "jabit.db";
protected final Context ctx; protected final Context ctx;
@ -38,7 +38,7 @@ public class SqlHelper extends SQLiteOpenHelper {
@Override @Override
public void onCreate(SQLiteDatabase db) { public void onCreate(SQLiteDatabase db) {
onUpgrade(db, 0, 1); onUpgrade(db, 0, 2);
} }
@Override @Override
@ -48,6 +48,9 @@ public class SqlHelper extends SQLiteOpenHelper {
executeMigration(db, "V1.0__Create_table_inventory"); executeMigration(db, "V1.0__Create_table_inventory");
executeMigration(db, "V1.1__Create_table_address"); executeMigration(db, "V1.1__Create_table_address");
executeMigration(db, "V1.2__Create_table_message"); executeMigration(db, "V1.2__Create_table_message");
case 1:
// executeMigration(db, "V2.0__Update_table_message");
executeMigration(db, "V2.1__Create_table_POW");
default: default:
// Nothing to do. Let's assume we won't upgrade from a version that's newer than DATABASE_VERSION. // 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_CREATE_IDENTITY = 10;
public static final int MSG_SUBSCRIBE = 20; public static final int MSG_SUBSCRIBE = 20;
public static final int MSG_ADD_CONTACT = 21; 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_MESSAGE = 30;
public static final int MSG_SEND_BROADCAST = 31; public static final int MSG_SEND_BROADCAST = 31;
public static final int MSG_START_NODE = 100; public static final int MSG_START_NODE = 100;
@ -122,6 +121,13 @@ public class BitmessageService extends Service {
} }
break; 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: { case MSG_SEND_MESSAGE: {
Serializable identity = msg.getData().getSerializable(DATA_FIELD_IDENTITY); Serializable identity = msg.getData().getSerializable(DATA_FIELD_IDENTITY);
Serializable address = msg.getData().getSerializable(DATA_FIELD_ADDRESS); 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()); service.startForeground(ONGOING_NOTIFICATION_ID, notification.getNotification());
engine.calculateNonce(initialHash, target, new ProofOfWorkEngine.Callback() { engine.calculateNonce(initialHash, target, new ProofOfWorkEngine.Callback() {
@Override @Override
public void onNonceCalculated(byte[] nonce) { public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
try { try {
callback.onNonceCalculated(nonce); callback.onNonceCalculated(initialHash, nonce);
} finally { } finally {
service.stopForeground(true); service.stopForeground(true);
service.stopSelf(); service.stopSelf();

View File

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

View File

@ -2,18 +2,27 @@ package ch.dissem.apps.abit.service;
import android.content.Context; import android.content.Context;
import java.util.List;
import ch.dissem.apps.abit.adapter.AndroidSecurity; 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.listener.MessageListener;
import ch.dissem.apps.abit.pow.ServerPowEngine;
import ch.dissem.apps.abit.repository.AndroidAddressRepository; import ch.dissem.apps.abit.repository.AndroidAddressRepository;
import ch.dissem.apps.abit.repository.AndroidInventory; import ch.dissem.apps.abit.repository.AndroidInventory;
import ch.dissem.apps.abit.repository.AndroidMessageRepository; 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.repository.SqlHelper;
import ch.dissem.apps.abit.util.Constants;
import ch.dissem.bitmessage.BitmessageContext; import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.networking.DefaultNetworkHandler; import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
import ch.dissem.bitmessage.ports.AddressRepository; import ch.dissem.bitmessage.ports.AddressRepository;
import ch.dissem.bitmessage.ports.MemoryNodeRegistry; import ch.dissem.bitmessage.ports.MemoryNodeRegistry;
import ch.dissem.bitmessage.ports.MessageRepository; 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. * Provides singleton objects across the application.
@ -22,6 +31,8 @@ public class Singleton {
public static final Object lock = new Object(); public static final Object lock = new Object();
private static BitmessageContext bitmessageContext; private static BitmessageContext bitmessageContext;
private static MessageListener messageListener; private static MessageListener messageListener;
private static BitmessageAddress identity;
private static AndroidProofOfWorkRepository powRepo;
public static BitmessageContext getBitmessageContext(Context context) { public static BitmessageContext getBitmessageContext(Context context) {
if (bitmessageContext == null) { if (bitmessageContext == null) {
@ -29,15 +40,23 @@ public class Singleton {
if (bitmessageContext == null) { if (bitmessageContext == null) {
final Context ctx = context.getApplicationContext(); final Context ctx = context.getApplicationContext();
SqlHelper sqlHelper = new SqlHelper(ctx); SqlHelper sqlHelper = new SqlHelper(ctx);
powRepo = new AndroidProofOfWorkRepository(sqlHelper);
bitmessageContext = new BitmessageContext.Builder() bitmessageContext = new BitmessageContext.Builder()
.proofOfWorkEngine(new ServicePowEngine(ctx)) .proofOfWorkEngine(new SwitchingProofOfWorkEngine(
.security(new AndroidSecurity(ctx)) ctx, Constants.PREFERENCE_SERVER_POW,
new ServerPowEngine(ctx),
new ServicePowEngine(ctx)
))
.security(new AndroidSecurity())
.nodeRegistry(new MemoryNodeRegistry()) .nodeRegistry(new MemoryNodeRegistry())
.inventory(new AndroidInventory(sqlHelper)) .inventory(new AndroidInventory(sqlHelper))
.addressRepo(new AndroidAddressRepository(sqlHelper)) .addressRepo(new AndroidAddressRepository(sqlHelper))
.messageRepo(new AndroidMessageRepository(sqlHelper, ctx)) .messageRepo(new AndroidMessageRepository(sqlHelper, ctx))
.powRepo(powRepo)
.networkHandler(new DefaultNetworkHandler()) .networkHandler(new DefaultNetworkHandler())
.listener(getMessageListener(ctx)) .listener(getMessageListener(ctx))
.doNotSendPubkeyOnIdentityCreation()
.pubkeyTTL(2 * DAY)
.build(); .build();
} }
} }
@ -63,4 +82,24 @@ public class Singleton {
public static AddressRepository getAddressRepository(Context ctx) { public static AddressRepository getAddressRepository(Context ctx) {
return getBitmessageContext(ctx).addresses(); 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 * of its methods
*/ */
public class Authenticator extends AbstractAccountAuthenticator { public class Authenticator extends AbstractAccountAuthenticator {
public static final String ACCOUNT_NAME = "Bitmessage"; public static final Account ACCOUNT_SYNC = new Account("Bitmessage", "ch.dissem.bitmessage");
public static final String ACCOUNT_TYPE = "ch.dissem.bitmessage"; public static final Account ACCOUNT_POW = new Account("Proof of Work ", "ch.dissem.bitmessage");
// Simple constructor // Simple constructor
public Authenticator(Context context) { public Authenticator(Context context) {

View File

@ -1,27 +1,34 @@
package ch.dissem.apps.abit.synchronization; package ch.dissem.apps.abit.synchronization;
import android.accounts.Account; import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.AbstractThreadedSyncAdapter; import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient; import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.content.SyncResult; import android.content.SyncResult;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.net.InetAddress; import java.util.List;
import java.net.UnknownHostException;
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.service.Singleton;
import ch.dissem.apps.abit.util.Preferences;
import ch.dissem.bitmessage.BitmessageContext; 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.synchronization.Authenticator.ACCOUNT_POW;
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE; 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 * 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 { public class SyncAdapter extends AbstractThreadedSyncAdapter {
private final static Logger LOG = LoggerFactory.getLogger(SyncAdapter.class); private final static Logger LOG = LoggerFactory.getLogger(SyncAdapter.class);
private static final long SYNC_FREQUENCY = 15 * 60; // seconds
private final BitmessageContext bmc; private final BitmessageContext bmc;
/** /**
@ -41,7 +50,17 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
} }
@Override @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 the Bitmessage context acts as a full node, synchronization isn't necessary
if (bmc.isRunning()) { if (bmc.isRunning()) {
LOG.info("Synchronization skipped, Abit is acting as a full node"); LOG.info("Synchronization skipped, Abit is acting as a full node");
@ -49,40 +68,103 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
} }
LOG.info("Synchronizing Bitmessage"); 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 { try {
LOG.info("Synchronization started"); 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"); LOG.info("Synchronization finished");
} catch (UnknownHostException e) {
new ErrorNotification(getContext())
.setError(R.string.error_invalid_sync_host)
.show();
} catch (RuntimeException e) { } catch (RuntimeException e) {
LOG.error(e.getMessage(), 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 <SwitchPreference
android:defaultValue="false" android:defaultValue="false"
android:key="server_pow" android:key="server_pow"
android:dependency="@string/trusted_node" android:dependency="trusted_node"
android:title="@string/server_pow" android:title="@string/server_pow"
android:summary="@string/server_pow_summary" android:summary="@string/server_pow_summary"
/> />

View File

@ -9,7 +9,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { 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 // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files