mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-15 20:29:52 +01:00
Check whether system clock is reasonable at startup.
This commit is contained in:
@@ -22,6 +22,7 @@ public interface LifecycleManager {
|
|||||||
*/
|
*/
|
||||||
enum StartResult {
|
enum StartResult {
|
||||||
ALREADY_RUNNING,
|
ALREADY_RUNNING,
|
||||||
|
CLOCK_ERROR,
|
||||||
DB_ERROR,
|
DB_ERROR,
|
||||||
DATA_TOO_OLD_ERROR,
|
DATA_TOO_OLD_ERROR,
|
||||||
DATA_TOO_NEW_ERROR,
|
DATA_TOO_NEW_ERROR,
|
||||||
@@ -29,6 +30,26 @@ public interface LifecycleManager {
|
|||||||
SUCCESS
|
SUCCESS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimum reasonable value for the system clock, in milliseconds
|
||||||
|
* since the Unix epoch. {@link #startServices(SecretKey)} will return
|
||||||
|
* {@link StartResult#CLOCK_ERROR} if the system clock reports an earlier
|
||||||
|
* time.
|
||||||
|
* <p/>
|
||||||
|
* 1 Jan 2021, 00:00:00 UTC
|
||||||
|
*/
|
||||||
|
long MIN_REASONABLE_TIME_MS = 1_609_459_200_000L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum reasonable value for the system clock, in milliseconds
|
||||||
|
* since the Unix epoch. {@link #startServices(SecretKey)} will return
|
||||||
|
* {@link StartResult#CLOCK_ERROR} if the system clock reports a later
|
||||||
|
* time.
|
||||||
|
* <p/>
|
||||||
|
* 1 Jan 2121, 00:00:00 UTC
|
||||||
|
*/
|
||||||
|
long MAX_REASONABLE_TIME_MS = 4_765_132_800_000L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The state the lifecycle can be in.
|
* The state the lifecycle can be in.
|
||||||
* Returned by {@link #getLifecycleState()}
|
* Returned by {@link #getLifecycleState()}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import org.briarproject.bramble.api.lifecycle.Service;
|
|||||||
import org.briarproject.bramble.api.lifecycle.ServiceException;
|
import org.briarproject.bramble.api.lifecycle.ServiceException;
|
||||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
@@ -34,6 +35,7 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleS
|
|||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_NEW_ERROR;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_NEW_ERROR;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_OLD_ERROR;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_OLD_ERROR;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DB_ERROR;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DB_ERROR;
|
||||||
@@ -52,6 +54,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final EventBus eventBus;
|
private final EventBus eventBus;
|
||||||
|
private final Clock clock;
|
||||||
private final List<Service> services;
|
private final List<Service> services;
|
||||||
private final List<OpenDatabaseHook> openDatabaseHooks;
|
private final List<OpenDatabaseHook> openDatabaseHooks;
|
||||||
private final List<ExecutorService> executors;
|
private final List<ExecutorService> executors;
|
||||||
@@ -63,9 +66,11 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
private volatile LifecycleState state = STARTING;
|
private volatile LifecycleState state = STARTING;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus) {
|
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
|
||||||
|
Clock clock) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
|
this.clock = clock;
|
||||||
services = new CopyOnWriteArrayList<>();
|
services = new CopyOnWriteArrayList<>();
|
||||||
openDatabaseHooks = new CopyOnWriteArrayList<>();
|
openDatabaseHooks = new CopyOnWriteArrayList<>();
|
||||||
executors = new CopyOnWriteArrayList<>();
|
executors = new CopyOnWriteArrayList<>();
|
||||||
@@ -99,6 +104,13 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
LOG.info("Already starting or stopping");
|
LOG.info("Already starting or stopping");
|
||||||
return ALREADY_RUNNING;
|
return ALREADY_RUNNING;
|
||||||
}
|
}
|
||||||
|
long now = clock.currentTimeMillis();
|
||||||
|
if (now < MIN_REASONABLE_TIME_MS || now > MAX_REASONABLE_TIME_MS) {
|
||||||
|
if (LOG.isLoggable(WARNING)) {
|
||||||
|
LOG.warning("System clock is unreasonable: " + now);
|
||||||
|
}
|
||||||
|
return CLOCK_ERROR;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
LOG.info("Opening database");
|
LOG.info("Opening database");
|
||||||
long start = now();
|
long start = now();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.db.Transaction;
|
|||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
|
||||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.briarproject.bramble.test.DbExpectations;
|
import org.briarproject.bramble.test.DbExpectations;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@@ -14,6 +15,9 @@ import org.junit.Test;
|
|||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import static junit.framework.TestCase.assertTrue;
|
import static junit.framework.TestCase.assertTrue;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.MAX_REASONABLE_TIME_MS;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.MIN_REASONABLE_TIME_MS;
|
||||||
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@@ -22,6 +26,7 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||||
private final EventBus eventBus = context.mock(EventBus.class);
|
private final EventBus eventBus = context.mock(EventBus.class);
|
||||||
|
private final Clock clock = context.mock(Clock.class);
|
||||||
|
|
||||||
private final SecretKey dbKey = getSecretKey();
|
private final SecretKey dbKey = getSecretKey();
|
||||||
|
|
||||||
@@ -29,16 +34,19 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
lifecycleManager = new LifecycleManagerImpl(db, eventBus);
|
lifecycleManager = new LifecycleManagerImpl(db, eventBus, clock);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOpenDatabaseHooksAreCalledAtStartup() throws Exception {
|
public void testOpenDatabaseHooksAreCalledAtStartup() throws Exception {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
AtomicBoolean called = new AtomicBoolean(false);
|
AtomicBoolean called = new AtomicBoolean(false);
|
||||||
OpenDatabaseHook hook = transaction -> called.set(true);
|
OpenDatabaseHook hook = transaction -> called.set(true);
|
||||||
|
|
||||||
context.checking(new DbExpectations() {{
|
context.checking(new DbExpectations() {{
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now));
|
||||||
oneOf(db).open(dbKey, lifecycleManager);
|
oneOf(db).open(dbKey, lifecycleManager);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||||
@@ -51,4 +59,26 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
|||||||
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
|
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
|
||||||
assertTrue(called.get());
|
assertTrue(called.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStartupFailsIfClockIsUnreasonablyBehind() {
|
||||||
|
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(MIN_REASONABLE_TIME_MS - 1));
|
||||||
|
}});
|
||||||
|
|
||||||
|
assertEquals(CLOCK_ERROR, lifecycleManager.startServices(dbKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStartupFailsIfClockIsUnreasonablyAhead() {
|
||||||
|
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(MAX_REASONABLE_TIME_MS + 1));
|
||||||
|
}});
|
||||||
|
|
||||||
|
assertEquals(CLOCK_ERROR, lifecycleManager.startServices(dbKey));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,26 +51,27 @@ public class StartupFailureActivity extends BaseActivity implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
// show proper error message
|
// show proper error message
|
||||||
String errorMsg;
|
int errorRes;
|
||||||
switch (result) {
|
switch (result) {
|
||||||
|
case CLOCK_ERROR:
|
||||||
|
errorRes = R.string.startup_failed_clock_error;
|
||||||
|
break;
|
||||||
case DATA_TOO_OLD_ERROR:
|
case DATA_TOO_OLD_ERROR:
|
||||||
errorMsg =
|
errorRes = R.string.startup_failed_data_too_old_error;
|
||||||
getString(R.string.startup_failed_data_too_old_error);
|
|
||||||
break;
|
break;
|
||||||
case DATA_TOO_NEW_ERROR:
|
case DATA_TOO_NEW_ERROR:
|
||||||
errorMsg =
|
errorRes = R.string.startup_failed_data_too_new_error;
|
||||||
getString(R.string.startup_failed_data_too_new_error);
|
|
||||||
break;
|
break;
|
||||||
case DB_ERROR:
|
case DB_ERROR:
|
||||||
errorMsg = getString(R.string.startup_failed_db_error);
|
errorRes = R.string.startup_failed_db_error;
|
||||||
break;
|
break;
|
||||||
case SERVICE_ERROR:
|
case SERVICE_ERROR:
|
||||||
errorMsg = getString(R.string.startup_failed_service_error);
|
errorRes = R.string.startup_failed_service_error;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
showInitialFragment(ErrorFragment.newInstance(errorMsg));
|
showInitialFragment(ErrorFragment.newInstance(getString(errorRes)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
<string name="startup_failed_notification_title">Briar could not start</string>
|
<string name="startup_failed_notification_title">Briar could not start</string>
|
||||||
<string name="startup_failed_notification_text">Tap for more information.</string>
|
<string name="startup_failed_notification_text">Tap for more information.</string>
|
||||||
<string name="startup_failed_activity_title">Briar Startup Failure</string>
|
<string name="startup_failed_activity_title">Briar Startup Failure</string>
|
||||||
|
<string name="startup_failed_clock_error">Briar was unable to start because your device\'s clock is wrong. Please set your device\'s clock to the right time and try again.</string>
|
||||||
<string name="startup_failed_db_error">For some reason, your Briar database is corrupted beyond repair. Your account, your data and all your contacts are lost. Unfortunately, you need to reinstall Briar or set up a new account by choosing \'I have forgotten my password\' at the password prompt.</string>
|
<string name="startup_failed_db_error">For some reason, your Briar database is corrupted beyond repair. Your account, your data and all your contacts are lost. Unfortunately, you need to reinstall Briar or set up a new account by choosing \'I have forgotten my password\' at the password prompt.</string>
|
||||||
<string name="startup_failed_data_too_old_error">Your account was created with an old version of this app and cannot be opened with this version. You must either reinstall the old version or set up a new account by choosing \'I have forgotten my password\' at the password prompt.</string>
|
<string name="startup_failed_data_too_old_error">Your account was created with an old version of this app and cannot be opened with this version. You must either reinstall the old version or set up a new account by choosing \'I have forgotten my password\' at the password prompt.</string>
|
||||||
<string name="startup_failed_data_too_new_error">This version of the app is too old. Please upgrade to the latest version and try again.</string>
|
<string name="startup_failed_data_too_new_error">This version of the app is too old. Please upgrade to the latest version and try again.</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user