mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
Check whether system clock is reasonable at startup.
This commit is contained in:
@@ -22,6 +22,7 @@ public interface LifecycleManager {
|
||||
*/
|
||||
enum StartResult {
|
||||
ALREADY_RUNNING,
|
||||
CLOCK_ERROR,
|
||||
DB_ERROR,
|
||||
DATA_TOO_OLD_ERROR,
|
||||
DATA_TOO_NEW_ERROR,
|
||||
@@ -29,6 +30,26 @@ public interface LifecycleManager {
|
||||
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.
|
||||
* 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.event.LifecycleEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.util.List;
|
||||
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.STOPPING;
|
||||
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_OLD_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 EventBus eventBus;
|
||||
private final Clock clock;
|
||||
private final List<Service> services;
|
||||
private final List<OpenDatabaseHook> openDatabaseHooks;
|
||||
private final List<ExecutorService> executors;
|
||||
@@ -63,9 +66,11 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
private volatile LifecycleState state = STARTING;
|
||||
|
||||
@Inject
|
||||
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus) {
|
||||
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
|
||||
Clock clock) {
|
||||
this.db = db;
|
||||
this.eventBus = eventBus;
|
||||
this.clock = clock;
|
||||
services = new CopyOnWriteArrayList<>();
|
||||
openDatabaseHooks = new CopyOnWriteArrayList<>();
|
||||
executors = new CopyOnWriteArrayList<>();
|
||||
@@ -99,6 +104,13 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
LOG.info("Already starting or stopping");
|
||||
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 {
|
||||
LOG.info("Opening database");
|
||||
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.lifecycle.LifecycleManager.OpenDatabaseHook;
|
||||
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.DbExpectations;
|
||||
import org.junit.Before;
|
||||
@@ -14,6 +15,9 @@ import org.junit.Test;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
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.test.TestUtils.getSecretKey;
|
||||
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 EventBus eventBus = context.mock(EventBus.class);
|
||||
private final Clock clock = context.mock(Clock.class);
|
||||
|
||||
private final SecretKey dbKey = getSecretKey();
|
||||
|
||||
@@ -29,16 +34,19 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
lifecycleManager = new LifecycleManagerImpl(db, eventBus);
|
||||
lifecycleManager = new LifecycleManagerImpl(db, eventBus, clock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenDatabaseHooksAreCalledAtStartup() throws Exception {
|
||||
long now = System.currentTimeMillis();
|
||||
Transaction txn = new Transaction(null, false);
|
||||
AtomicBoolean called = new AtomicBoolean(false);
|
||||
OpenDatabaseHook hook = transaction -> called.set(true);
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(db).open(dbKey, lifecycleManager);
|
||||
will(returnValue(false));
|
||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||
@@ -51,4 +59,26 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
||||
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
|
||||
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
|
||||
String errorMsg;
|
||||
int errorRes;
|
||||
switch (result) {
|
||||
case CLOCK_ERROR:
|
||||
errorRes = R.string.startup_failed_clock_error;
|
||||
break;
|
||||
case DATA_TOO_OLD_ERROR:
|
||||
errorMsg =
|
||||
getString(R.string.startup_failed_data_too_old_error);
|
||||
errorRes = R.string.startup_failed_data_too_old_error;
|
||||
break;
|
||||
case DATA_TOO_NEW_ERROR:
|
||||
errorMsg =
|
||||
getString(R.string.startup_failed_data_too_new_error);
|
||||
errorRes = R.string.startup_failed_data_too_new_error;
|
||||
break;
|
||||
case DB_ERROR:
|
||||
errorMsg = getString(R.string.startup_failed_db_error);
|
||||
errorRes = R.string.startup_failed_db_error;
|
||||
break;
|
||||
case SERVICE_ERROR:
|
||||
errorMsg = getString(R.string.startup_failed_service_error);
|
||||
errorRes = R.string.startup_failed_service_error;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
showInitialFragment(ErrorFragment.newInstance(errorMsg));
|
||||
showInitialFragment(ErrorFragment.newInstance(getString(errorRes)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
<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_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_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>
|
||||
|
||||
Reference in New Issue
Block a user