Add support for fetching remote deprecation.
This commit is contained in:
parent
c946a7a1d5
commit
2784285d47
21 changed files with 559 additions and 39 deletions
|
@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
||||||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
||||||
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob;
|
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob;
|
||||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.logging.AndroidLogger;
|
import org.thoughtcrime.securesms.logging.AndroidLogger;
|
||||||
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
|
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
@ -70,6 +71,7 @@ import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
|
||||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
||||||
import org.webrtc.voiceengine.WebRtcAudioManager;
|
import org.webrtc.voiceengine.WebRtcAudioManager;
|
||||||
|
@ -157,6 +159,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
||||||
KeyCachingService.onAppForegrounded(this);
|
KeyCachingService.onAppForegrounded(this);
|
||||||
ApplicationDependencies.getFrameRateTracker().begin();
|
ApplicationDependencies.getFrameRateTracker().begin();
|
||||||
ApplicationDependencies.getMegaphoneRepository().onAppForegrounded();
|
ApplicationDependencies.getMegaphoneRepository().onAppForegrounded();
|
||||||
|
checkBuildExpiration();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -192,6 +195,13 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
||||||
return persistentLogger;
|
return persistentLogger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void checkBuildExpiration() {
|
||||||
|
if (Util.getTimeUntilBuildExpiry() <= 0 && !SignalStore.misc().isClientDeprecated()) {
|
||||||
|
Log.w(TAG, "Build expired!");
|
||||||
|
SignalStore.misc().markDeprecated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void initializeSecurityProvider() {
|
private void initializeSecurityProvider() {
|
||||||
try {
|
try {
|
||||||
Class.forName("org.signal.aesgcmprovider.AesGcmCipher");
|
Class.forName("org.signal.aesgcmprovider.AesGcmCipher");
|
||||||
|
|
|
@ -3,8 +3,8 @@ package org.thoughtcrime.securesms.components.reminder;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.util.PlayStoreUtil;
|
import org.thoughtcrime.securesms.util.PlayStoreUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
|
||||||
|
|
||||||
public class ExpiredBuildReminder extends Reminder {
|
public class ExpiredBuildReminder extends Reminder {
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ public class ExpiredBuildReminder extends Reminder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isEligible() {
|
public static boolean isEligible() {
|
||||||
return Util.getDaysTillBuildExpiry() <= 0;
|
return SignalStore.misc().isClientDeprecated();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.util.PlayStoreUtil;
|
import org.thoughtcrime.securesms.util.PlayStoreUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class OutdatedBuildReminder extends Reminder {
|
public class OutdatedBuildReminder extends Reminder {
|
||||||
|
|
||||||
public OutdatedBuildReminder(final Context context) {
|
public OutdatedBuildReminder(final Context context) {
|
||||||
|
@ -15,7 +17,7 @@ public class OutdatedBuildReminder extends Reminder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CharSequence getPluralsText(final Context context) {
|
private static CharSequence getPluralsText(final Context context) {
|
||||||
int days = Util.getDaysTillBuildExpiry() - 1;
|
int days = getDaysUntilExpiry() - 1;
|
||||||
if (days == 0) {
|
if (days == 0) {
|
||||||
return context.getString(R.string.reminder_header_outdated_build_details_today);
|
return context.getString(R.string.reminder_header_outdated_build_details_today);
|
||||||
}
|
}
|
||||||
|
@ -28,7 +30,10 @@ public class OutdatedBuildReminder extends Reminder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isEligible() {
|
public static boolean isEligible() {
|
||||||
return Util.getDaysTillBuildExpiry() <= 10;
|
return getDaysUntilExpiry() <= 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int getDaysUntilExpiry() {
|
||||||
|
return (int) TimeUnit.MILLISECONDS.toDays(Util.getTimeUntilBuildExpiry());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,8 @@ import org.thoughtcrime.securesms.attachments.Attachment;
|
||||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
|
||||||
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -26,7 +25,7 @@ public abstract class SendJob extends BaseJob {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void onRun() throws Exception {
|
public final void onRun() throws Exception {
|
||||||
if (Util.getDaysTillBuildExpiry() <= 0) {
|
if (SignalStore.misc().isClientDeprecated()) {
|
||||||
throw new TextSecureExpiredException(String.format("TextSecure expired (build %d, now %d)",
|
throw new TextSecureExpiredException(String.format("TextSecure expired (build %d, now %d)",
|
||||||
BuildConfig.BUILD_TIMESTAMP,
|
BuildConfig.BUILD_TIMESTAMP,
|
||||||
System.currentTimeMillis()));
|
System.currentTimeMillis()));
|
||||||
|
|
|
@ -8,6 +8,7 @@ public final class MiscellaneousValues extends SignalStoreValues {
|
||||||
private static final String MESSAGE_REQUEST_ENABLE_TIME = "message_request_enable_time";
|
private static final String MESSAGE_REQUEST_ENABLE_TIME = "message_request_enable_time";
|
||||||
private static final String LAST_PROFILE_REFRESH_TIME = "misc.last_profile_refresh_time";
|
private static final String LAST_PROFILE_REFRESH_TIME = "misc.last_profile_refresh_time";
|
||||||
private static final String USERNAME_SHOW_REMINDER = "username.show.reminder";
|
private static final String USERNAME_SHOW_REMINDER = "username.show.reminder";
|
||||||
|
private static final String CLIENT_DEPRECATED = "misc.client_deprecated";
|
||||||
|
|
||||||
MiscellaneousValues(@NonNull KeyValueStore store) {
|
MiscellaneousValues(@NonNull KeyValueStore store) {
|
||||||
super(store);
|
super(store);
|
||||||
|
@ -45,4 +46,12 @@ public final class MiscellaneousValues extends SignalStoreValues {
|
||||||
public boolean shouldShowUsernameReminder() {
|
public boolean shouldShowUsernameReminder() {
|
||||||
return getBoolean(USERNAME_SHOW_REMINDER, true);
|
return getBoolean(USERNAME_SHOW_REMINDER, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isClientDeprecated() {
|
||||||
|
return getBoolean(CLIENT_DEPRECATED, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markDeprecated() {
|
||||||
|
putBoolean(CLIENT_DEPRECATED, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import com.google.android.collect.Sets;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.stickers.StickerUrl;
|
import org.thoughtcrime.securesms.stickers.StickerUrl;
|
||||||
|
import org.thoughtcrime.securesms.util.DateUtils;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.util.OptionalUtil;
|
import org.whispersystems.signalservice.api.util.OptionalUtil;
|
||||||
|
@ -203,18 +204,11 @@ public final class LinkPreviewUtil {
|
||||||
|
|
||||||
@SuppressLint("ObsoleteSdkInt")
|
@SuppressLint("ObsoleteSdkInt")
|
||||||
public long getDate() {
|
public long getDate() {
|
||||||
SimpleDateFormat format;
|
|
||||||
if (Build.VERSION.SDK_INT == 0 || Build.VERSION.SDK_INT >= 24) {
|
|
||||||
format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX", Locale.getDefault());
|
|
||||||
} else {
|
|
||||||
format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Stream.of(values.get(KEY_PUBLISHED_TIME_1),
|
return Stream.of(values.get(KEY_PUBLISHED_TIME_1),
|
||||||
values.get(KEY_PUBLISHED_TIME_2),
|
values.get(KEY_PUBLISHED_TIME_2),
|
||||||
values.get(KEY_MODIFIED_TIME_1),
|
values.get(KEY_MODIFIED_TIME_1),
|
||||||
values.get(KEY_MODIFIED_TIME_2))
|
values.get(KEY_MODIFIED_TIME_2))
|
||||||
.map(dateString -> parseDate(format, dateString))
|
.map(DateUtils::parseIso8601)
|
||||||
.filter(time -> time > 0)
|
.filter(time -> time > 0)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(0L);
|
.orElse(0L);
|
||||||
|
@ -223,19 +217,6 @@ public final class LinkPreviewUtil {
|
||||||
public @NonNull Optional<String> getDescription() {
|
public @NonNull Optional<String> getDescription() {
|
||||||
return OptionalUtil.absentIfEmpty(values.get(KEY_DESCRIPTION_URL));
|
return OptionalUtil.absentIfEmpty(values.get(KEY_DESCRIPTION_URL));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long parseDate(DateFormat dateFormat, String dateString) {
|
|
||||||
if (Util.isEmpty(dateString)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return dateFormat.parse(dateString).getTime();
|
|
||||||
} catch (ParseException e) {
|
|
||||||
Log.w(TAG, "Failed to parse date.", e);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface HtmlDecoder {
|
public interface HtmlDecoder {
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
package org.thoughtcrime.securesms.net;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import okhttp3.Interceptor;
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.Protocol;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disallows network requests when your client has been deprecated. When the client is deprecated,
|
||||||
|
* we simply fake a 499 response.
|
||||||
|
*/
|
||||||
|
public final class DeprecatedClientPreventionInterceptor implements Interceptor {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(DeprecatedClientPreventionInterceptor.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull Response intercept(@NonNull Chain chain) throws IOException {
|
||||||
|
if (SignalStore.misc().isClientDeprecated()) {
|
||||||
|
Log.w(TAG, "Preventing request because client is deprecated.");
|
||||||
|
return new Response.Builder()
|
||||||
|
.request(chain.request())
|
||||||
|
.protocol(Protocol.HTTP_1_1)
|
||||||
|
.receivedResponseAtMillis(System.currentTimeMillis())
|
||||||
|
.message("")
|
||||||
|
.body(ResponseBody.create(null, ""))
|
||||||
|
.code(499)
|
||||||
|
.build();
|
||||||
|
} else {
|
||||||
|
return chain.proceed(chain.request());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package org.thoughtcrime.securesms.net;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import okhttp3.Interceptor;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the client as remotely-deprecated when it receives a 499 response.
|
||||||
|
*/
|
||||||
|
public final class RemoteDeprecationDetectorInterceptor implements Interceptor {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(RemoteDeprecationDetectorInterceptor.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull Response intercept(@NonNull Chain chain) throws IOException {
|
||||||
|
Response response = chain.proceed(chain.request());
|
||||||
|
|
||||||
|
if (response.code() == 499 && !SignalStore.misc().isClientDeprecated()) {
|
||||||
|
Log.w(TAG, "Received 499. Client version is deprecated.");
|
||||||
|
SignalStore.misc().markDeprecated();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,6 @@ import org.thoughtcrime.securesms.BuildConfig;
|
||||||
public class StandardUserAgentInterceptor extends UserAgentInterceptor {
|
public class StandardUserAgentInterceptor extends UserAgentInterceptor {
|
||||||
|
|
||||||
public StandardUserAgentInterceptor() {
|
public StandardUserAgentInterceptor() {
|
||||||
super("Signal-Android " + BuildConfig.VERSION_NAME + " (API " + Build.VERSION.SDK_INT + ")");
|
super("Signal-Android/" + BuildConfig.VERSION_NAME + " Android/" + Build.VERSION.SDK_INT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.BuildConfig;
|
import org.thoughtcrime.securesms.BuildConfig;
|
||||||
import org.thoughtcrime.securesms.net.CustomDns;
|
import org.thoughtcrime.securesms.net.CustomDns;
|
||||||
|
import org.thoughtcrime.securesms.net.RemoteDeprecationDetectorInterceptor;
|
||||||
|
import org.thoughtcrime.securesms.net.DeprecatedClientPreventionInterceptor;
|
||||||
import org.thoughtcrime.securesms.net.SequentialDns;
|
import org.thoughtcrime.securesms.net.SequentialDns;
|
||||||
import org.thoughtcrime.securesms.net.StandardUserAgentInterceptor;
|
import org.thoughtcrime.securesms.net.StandardUserAgentInterceptor;
|
||||||
import org.thoughtcrime.securesms.util.Base64;
|
import org.thoughtcrime.securesms.util.Base64;
|
||||||
|
@ -21,6 +23,7 @@ import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl;
|
||||||
import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl;
|
import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -161,7 +164,7 @@ public class SignalServiceNetworkAccess {
|
||||||
final SignalStorageUrl omanGoogleStorage = new SignalStorageUrl("https://www.google.com.om/storage", SERVICE_REFLECTOR_HOST, trustStore, GMAIL_CONNECTION_SPEC);
|
final SignalStorageUrl omanGoogleStorage = new SignalStorageUrl("https://www.google.com.om/storage", SERVICE_REFLECTOR_HOST, trustStore, GMAIL_CONNECTION_SPEC);
|
||||||
final SignalStorageUrl qatarGoogleStorage = new SignalStorageUrl("https://www.google.com.qa/storage", SERVICE_REFLECTOR_HOST, trustStore, GMAIL_CONNECTION_SPEC);
|
final SignalStorageUrl qatarGoogleStorage = new SignalStorageUrl("https://www.google.com.qa/storage", SERVICE_REFLECTOR_HOST, trustStore, GMAIL_CONNECTION_SPEC);
|
||||||
|
|
||||||
final List<Interceptor> interceptors = Collections.singletonList(new StandardUserAgentInterceptor());
|
final List<Interceptor> interceptors = Arrays.asList(new StandardUserAgentInterceptor(), new RemoteDeprecationDetectorInterceptor(), new DeprecatedClientPreventionInterceptor());
|
||||||
final Optional<Dns> dns = Optional.of(DNS);
|
final Optional<Dns> dns = Optional.of(DNS);
|
||||||
|
|
||||||
final byte[] zkGroupServerPublicParams;
|
final byte[] zkGroupServerPublicParams;
|
||||||
|
|
|
@ -16,13 +16,18 @@
|
||||||
*/
|
*/
|
||||||
package org.thoughtcrime.securesms.util;
|
package org.thoughtcrime.securesms.util;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
import android.text.format.DateFormat;
|
import android.text.format.DateFormat;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
@ -170,4 +175,33 @@ public class DateUtils extends android.text.format.DateUtils {
|
||||||
private static String getLocalizedPattern(String template, Locale locale) {
|
private static String getLocalizedPattern(String template, Locale locale) {
|
||||||
return DateFormat.getBestDateTimePattern(locale, template);
|
return DateFormat.getBestDateTimePattern(locale, template);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* e.g. 2020-09-04T19:17:51Z
|
||||||
|
* https://www.iso.org/iso-8601-date-and-time-format.html
|
||||||
|
*
|
||||||
|
* Note: SDK_INT == 0 check needed to pass unit tests due to JVM date parser differences.
|
||||||
|
*
|
||||||
|
* @return The timestamp if able to be parsed, otherwise -1.
|
||||||
|
*/
|
||||||
|
@SuppressLint("ObsoleteSdkInt")
|
||||||
|
public static long parseIso8601(@Nullable String date) {
|
||||||
|
SimpleDateFormat format;
|
||||||
|
if (Build.VERSION.SDK_INT == 0 || Build.VERSION.SDK_INT >= 24) {
|
||||||
|
format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX", Locale.getDefault());
|
||||||
|
} else {
|
||||||
|
format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Util.isEmpty(date)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return format.parse(date).getTime();
|
||||||
|
} catch (ParseException e) {
|
||||||
|
Log.w(TAG, "Failed to parse date.", e);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,6 +64,7 @@ public final class FeatureFlags {
|
||||||
private static final String MENTIONS = "android.mentions";
|
private static final String MENTIONS = "android.mentions";
|
||||||
private static final String VERIFY_V2 = "android.verifyV2";
|
private static final String VERIFY_V2 = "android.verifyV2";
|
||||||
private static final String PHONE_NUMBER_PRIVACY_VERSION = "android.phoneNumberPrivacyVersion";
|
private static final String PHONE_NUMBER_PRIVACY_VERSION = "android.phoneNumberPrivacyVersion";
|
||||||
|
private static final String CLIENT_EXPIRATION = "android.clientExpiration";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We will only store remote values for flags in this set. If you want a flag to be controllable
|
* We will only store remote values for flags in this set. If you want a flag to be controllable
|
||||||
|
@ -82,7 +83,8 @@ public final class FeatureFlags {
|
||||||
INTERNAL_USER,
|
INTERNAL_USER,
|
||||||
USERNAMES,
|
USERNAMES,
|
||||||
MENTIONS,
|
MENTIONS,
|
||||||
VERIFY_V2
|
VERIFY_V2,
|
||||||
|
CLIENT_EXPIRATION
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -107,7 +109,8 @@ public final class FeatureFlags {
|
||||||
GROUPS_V2_CREATE_VERSION,
|
GROUPS_V2_CREATE_VERSION,
|
||||||
GROUPS_V2_JOIN_VERSION,
|
GROUPS_V2_JOIN_VERSION,
|
||||||
VERIFY_V2,
|
VERIFY_V2,
|
||||||
CDS_VERSION
|
CDS_VERSION,
|
||||||
|
CLIENT_EXPIRATION
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -280,6 +283,11 @@ public final class FeatureFlags {
|
||||||
return getBoolean(VERIFY_V2, false);
|
return getBoolean(VERIFY_V2, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The raw client expiration JSON string. */
|
||||||
|
public static String clientExpiration() {
|
||||||
|
return getString(CLIENT_EXPIRATION, null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the user can choose phone number privacy settings, and;
|
* Whether the user can choose phone number privacy settings, and;
|
||||||
* Whether to fetch and store the secondary certificate
|
* Whether to fetch and store the secondary certificate
|
||||||
|
@ -463,6 +471,20 @@ public final class FeatureFlags {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String getString(@NonNull String key, String defaultValue) {
|
||||||
|
String forced = (String) FORCED_VALUES.get(key);
|
||||||
|
if (forced != null) {
|
||||||
|
return forced;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object remote = REMOTE_VALUES.get(key);
|
||||||
|
if (remote instanceof String) {
|
||||||
|
return (String) remote;
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
private static Map<String, Object> parseStoredConfig(String stored) {
|
private static Map<String, Object> parseStoredConfig(String stored) {
|
||||||
Map<String, Object> parsed = new HashMap<>();
|
Map<String, Object> parsed = new HashMap<>();
|
||||||
|
|
||||||
|
@ -511,9 +533,6 @@ public final class FeatureFlags {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class MissingFlagRequirementError extends Error {
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final class UpdateResult {
|
static final class UpdateResult {
|
||||||
private final Map<String, Object> memory;
|
private final Map<String, Object> memory;
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
package org.thoughtcrime.securesms.util;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import com.annimon.stream.Stream;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.BuildConfig;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public final class RemoteDeprecation {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(RemoteDeprecation.class);
|
||||||
|
|
||||||
|
private RemoteDeprecation() { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The amount of time (in milliseconds) until this client version expires, or -1 if
|
||||||
|
* there's no pending expiration.
|
||||||
|
*/
|
||||||
|
public static long getTimeUntilDeprecation() {
|
||||||
|
return getTimeUntilDeprecation(FeatureFlags.clientExpiration(), System.currentTimeMillis(), BuildConfig.VERSION_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The amount of time (in milliseconds) until this client version expires, or -1 if
|
||||||
|
* there's no pending expiration.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
static long getTimeUntilDeprecation(String json, long currentTime, @NonNull String currentVersion) {
|
||||||
|
if (Util.isEmpty(json)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
SemanticVersion ourVersion = Objects.requireNonNull(SemanticVersion.parse(currentVersion));
|
||||||
|
ClientExpiration[] expirations = JsonUtils.fromJson(json, ClientExpiration[].class);
|
||||||
|
|
||||||
|
ClientExpiration expiration = Stream.of(expirations)
|
||||||
|
.filter(c -> c.getVersion() != null && c.getExpiration() != -1)
|
||||||
|
.filter(c -> c.requireVersion().compareTo(ourVersion) > 0)
|
||||||
|
.sortBy(ClientExpiration::getExpiration)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
if (expiration != null) {
|
||||||
|
return Math.max(expiration.getExpiration() - currentTime, 0);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ClientExpiration {
|
||||||
|
@JsonProperty
|
||||||
|
private final String minVersion;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private final String iso8601;
|
||||||
|
|
||||||
|
ClientExpiration(@Nullable @JsonProperty("minVersion") String minVersion,
|
||||||
|
@Nullable @JsonProperty("iso8601") String iso8601)
|
||||||
|
{
|
||||||
|
this.minVersion = minVersion;
|
||||||
|
this.iso8601 = iso8601;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable SemanticVersion getVersion() {
|
||||||
|
return SemanticVersion.parse(minVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull SemanticVersion requireVersion() {
|
||||||
|
return Objects.requireNonNull(getVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getExpiration() {
|
||||||
|
return DateUtils.parseIso8601(iso8601);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package org.thoughtcrime.securesms.util;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.annimon.stream.ComparatorCompat;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public final class SemanticVersion implements Comparable<SemanticVersion> {
|
||||||
|
|
||||||
|
private static final Pattern VERSION_PATTERN = Pattern.compile("^([0-9]+)\\.([0-9]+)\\.([0-9]+)$");
|
||||||
|
|
||||||
|
private static final Comparator<SemanticVersion> MAJOR_COMPARATOR = (s1, s2) -> Integer.compare(s1.major, s2.major);
|
||||||
|
private static final Comparator<SemanticVersion> MINOR_COMPARATOR = (s1, s2) -> Integer.compare(s1.minor, s2.minor);
|
||||||
|
private static final Comparator<SemanticVersion> PATCH_COMPARATOR = (s1, s2) -> Integer.compare(s1.patch, s2.patch);
|
||||||
|
private static final Comparator<SemanticVersion> COMPARATOR = ComparatorCompat.chain(MAJOR_COMPARATOR)
|
||||||
|
.thenComparing(MINOR_COMPARATOR)
|
||||||
|
.thenComparing(PATCH_COMPARATOR);
|
||||||
|
|
||||||
|
private final int major;
|
||||||
|
private final int minor;
|
||||||
|
private final int patch;
|
||||||
|
|
||||||
|
public SemanticVersion(int major, int minor, int patch) {
|
||||||
|
this.major = major;
|
||||||
|
this.minor = minor;
|
||||||
|
this.patch = patch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @Nullable SemanticVersion parse(@Nullable String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matcher matcher = VERSION_PATTERN.matcher(value);
|
||||||
|
if (Util.isEmpty(value) || !matcher.matches()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int major = Integer.parseInt(matcher.group(1));
|
||||||
|
int minor = Integer.parseInt(matcher.group(2));
|
||||||
|
int patch = Integer.parseInt(matcher.group(3));
|
||||||
|
|
||||||
|
return new SemanticVersion(major, minor, patch);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(SemanticVersion other) {
|
||||||
|
return COMPARATOR.compare(this, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
SemanticVersion that = (SemanticVersion) o;
|
||||||
|
return major == that.major &&
|
||||||
|
minor == that.minor &&
|
||||||
|
patch == that.patch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(major, minor, patch);
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,6 +49,7 @@ import com.google.i18n.phonenumbers.Phonenumber;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.BuildConfig;
|
import org.thoughtcrime.securesms.BuildConfig;
|
||||||
import org.thoughtcrime.securesms.components.ComposeText;
|
import org.thoughtcrime.securesms.components.ComposeText;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.mms.OutgoingLegacyMmsConnection;
|
import org.thoughtcrime.securesms.mms.OutgoingLegacyMmsConnection;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
@ -77,6 +78,8 @@ import java.util.concurrent.TimeUnit;
|
||||||
public class Util {
|
public class Util {
|
||||||
private static final String TAG = Util.class.getSimpleName();
|
private static final String TAG = Util.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final long BUILD_LIFESPAN = TimeUnit.DAYS.toMillis(90);
|
||||||
|
|
||||||
private static volatile Handler handler;
|
private static volatile Handler handler;
|
||||||
|
|
||||||
public static <T> List<T> asList(T... elements) {
|
public static <T> List<T> asList(T... elements) {
|
||||||
|
@ -458,9 +461,25 @@ public class Util {
|
||||||
return secret;
|
return secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getDaysTillBuildExpiry() {
|
/**
|
||||||
int age = (int) TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - BuildConfig.BUILD_TIMESTAMP);
|
* @return The amount of time (in ms) until this build of Signal will be considered 'expired'.
|
||||||
return 90 - age;
|
* Takes into account both the build age as well as any remote deprecation values.
|
||||||
|
*/
|
||||||
|
public static long getTimeUntilBuildExpiry() {
|
||||||
|
if (SignalStore.misc().isClientDeprecated()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
long buildAge = System.currentTimeMillis() - BuildConfig.BUILD_TIMESTAMP;
|
||||||
|
long timeUntilBuildDeprecation = BUILD_LIFESPAN - buildAge;
|
||||||
|
long timeUntilRemoteDeprecation = RemoteDeprecation.getTimeUntilDeprecation();
|
||||||
|
|
||||||
|
if (timeUntilRemoteDeprecation != -1) {
|
||||||
|
long timeUntilDeprecation = Math.min(timeUntilBuildDeprecation, timeUntilRemoteDeprecation);
|
||||||
|
return Math.max(timeUntilDeprecation, 0);
|
||||||
|
} else {
|
||||||
|
return Math.max(timeUntilBuildDeprecation, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(VERSION_CODES.LOLLIPOP)
|
@TargetApi(VERSION_CODES.LOLLIPOP)
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.thoughtcrime.securesms.testutil;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
|
||||||
|
public class EmptyLogger extends Log.Logger {
|
||||||
|
@Override
|
||||||
|
public void v(String tag, String message, Throwable t) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void d(String tag, String message, Throwable t) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void i(String tag, String message, Throwable t) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void w(String tag, String message, Throwable t) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void e(String tag, String message, Throwable t) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void wtf(String tag, String message, Throwable t) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void blockUntilAllWritesFinished() { }
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package org.thoughtcrime.securesms.util;
|
||||||
|
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.testutil.EmptyLogger;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import static junit.framework.TestCase.assertEquals;
|
||||||
|
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
|
public class RemoteExpirationTest_getTimeUntilDeprecation {
|
||||||
|
|
||||||
|
private final String json;
|
||||||
|
private final long currentDate;
|
||||||
|
private final String currentVersion;
|
||||||
|
private final long timeUntilExpiration;
|
||||||
|
|
||||||
|
@Parameterized.Parameters
|
||||||
|
public static Collection<Object[]> data() {
|
||||||
|
return Arrays.asList(new Object[][]{
|
||||||
|
// Null json, invalid
|
||||||
|
{ null, DateUtils.parseIso8601("2020-01-01T00:00:00Z"), "1.1.0", -1 },
|
||||||
|
|
||||||
|
// Empty json, no expiration
|
||||||
|
{ "[]", DateUtils.parseIso8601("2020-01-01T00:00:00Z"), "1.1.0", -1 },
|
||||||
|
|
||||||
|
// Badly formatted minVersion, no expiration
|
||||||
|
{ "[ {\"minVersion\": \"1.1\", \"iso8601\": \"2020-01-01T00:00:01Z\"} ]", DateUtils.parseIso8601("2020-01-01T00:00:00Z"), "1.1.1", -1 },
|
||||||
|
|
||||||
|
// Badly formatted date, no expiration
|
||||||
|
{ "[ {\"minVersion\": \"1.1.1\", \"iso8601\": \"20-01T00:00:01Z\"} ]", DateUtils.parseIso8601("2020-01-01T00:00:00Z"), "1.1.1", -1 },
|
||||||
|
|
||||||
|
// Missing minVersion, no expiration
|
||||||
|
{ "[ {\"iso8601\": \"20-01T00:00:01Z\"} ]", DateUtils.parseIso8601("2020-01-01T00:00:00Z"), "1.1.1", -1 },
|
||||||
|
|
||||||
|
// Missing date, no expiration
|
||||||
|
{ "[ {\"minVersion\": \"1.1.1\"} ]", DateUtils.parseIso8601("2020-01-01T00:00:00Z"), "1.1.1", -1 },
|
||||||
|
|
||||||
|
// Missing expiration and date, no expiration
|
||||||
|
{ "[ {} ]", DateUtils.parseIso8601("2020-01-01T00:00:00Z"), "1.1.1", -1 },
|
||||||
|
|
||||||
|
// Invalid inner object, no expiration
|
||||||
|
{ "[ { \"a\": 1 } ]", DateUtils.parseIso8601("2020-01-01T00:00:00Z"), "1.1.1", -1 },
|
||||||
|
|
||||||
|
// Invalid json, no expiration
|
||||||
|
{ "[ {", DateUtils.parseIso8601("2020-01-01T00:00:00Z"), "1.1.1", -1 },
|
||||||
|
|
||||||
|
// We meet the min version, no expiration
|
||||||
|
{ "[ {\"minVersion\": \"1.1.1\", \"iso8601\": \"2020-01-01T00:00:01Z\"} ]", DateUtils.parseIso8601("2020-01-01T00:00:00Z"), "1.1.1", -1 },
|
||||||
|
|
||||||
|
// We exceed the min version, no expiration
|
||||||
|
{ "[ {\"minVersion\": \"1.1.1\", \"iso8601\": \"2020-01-01T00:00:01Z\"} ]", DateUtils.parseIso8601("2020-01-01T00:00:00Z"), "1.1.2", -1 },
|
||||||
|
|
||||||
|
// We expire in 1 second
|
||||||
|
{ "[ {\"minVersion\": \"1.1.1\", \"iso8601\": \"2020-01-01T00:00:01Z\"} ]", DateUtils.parseIso8601("2020-01-01T00:00:00Z"), "1.1.0", 1000 },
|
||||||
|
|
||||||
|
// We have already expired
|
||||||
|
{ "[ {\"minVersion\": \"1.1.1\", \"iso8601\": \"2020-01-01T00:00:01Z\"} ]", DateUtils.parseIso8601("2020-01-01T00:00:02Z"), "1.1.0", 0 },
|
||||||
|
|
||||||
|
// Use the closest expiration when multiple ones are listed
|
||||||
|
{ "[ {\"minVersion\": \"1.1.2\", \"iso8601\": \"2020-02-01T00:00:00Z\"}," +
|
||||||
|
"{\"minVersion\": \"1.1.3\", \"iso8601\": \"2020-03-01T00:00:00Z\"}," +
|
||||||
|
"{\"minVersion\": \"1.1.1\", \"iso8601\": \"2020-01-01T00:00:01Z\"} ]", DateUtils.parseIso8601("2020-01-01T00:00:00Z"), "1.1.0", 1000 },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemoteExpirationTest_getTimeUntilDeprecation(String json, long currentDate, String currentVersion, long timeUntilExpiration) {
|
||||||
|
this.json = json;
|
||||||
|
this.currentDate = currentDate;
|
||||||
|
this.currentVersion = currentVersion;
|
||||||
|
this.timeUntilExpiration = timeUntilExpiration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void setup() {
|
||||||
|
Log.initialize(new EmptyLogger());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getTimeUntilExpiration() {
|
||||||
|
assertEquals(timeUntilExpiration, RemoteDeprecation.getTimeUntilDeprecation(json, currentDate, currentVersion));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package org.thoughtcrime.securesms.util;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import static junit.framework.TestCase.assertEquals;
|
||||||
|
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
|
public class SemanticVersionTest_compareTo {
|
||||||
|
|
||||||
|
private final SemanticVersion first;
|
||||||
|
private final SemanticVersion second;
|
||||||
|
private final int output;
|
||||||
|
|
||||||
|
@Parameterized.Parameters
|
||||||
|
public static Collection<Object[]> data() {
|
||||||
|
return Arrays.asList(new Object[][]{
|
||||||
|
{ new SemanticVersion(1, 0, 0), new SemanticVersion(0, 1, 0), 1 },
|
||||||
|
{ new SemanticVersion(1, 0, 0), new SemanticVersion(0, 0, 1), 1 },
|
||||||
|
{ new SemanticVersion(1, 0, 0), new SemanticVersion(0, 0, 0), 1 },
|
||||||
|
{ new SemanticVersion(0, 1, 0), new SemanticVersion(0, 0, 1), 1 },
|
||||||
|
{ new SemanticVersion(0, 1, 0), new SemanticVersion(0, 0, 0), 1 },
|
||||||
|
{ new SemanticVersion(0, 0, 1), new SemanticVersion(0, 0, 0), 1 },
|
||||||
|
{ new SemanticVersion(1, 1, 0), new SemanticVersion(1, 0, 0), 1 },
|
||||||
|
{ new SemanticVersion(1, 1, 1), new SemanticVersion(1, 1, 0), 1 },
|
||||||
|
{ new SemanticVersion(0, 0, 1), new SemanticVersion(1, 0, 0), -1 },
|
||||||
|
{ new SemanticVersion(1, 1, 1), new SemanticVersion(1, 1, 1), 0 },
|
||||||
|
{ new SemanticVersion(0, 0, 0), new SemanticVersion(0, 0, 0), 0 },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public SemanticVersionTest_compareTo(SemanticVersion first, SemanticVersion second, int output) {
|
||||||
|
this.first = first;
|
||||||
|
this.second = second;
|
||||||
|
this.output = output;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void compareTo() {
|
||||||
|
assertEquals(output, first.compareTo(second));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package org.thoughtcrime.securesms.util;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import static junit.framework.TestCase.assertEquals;
|
||||||
|
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
|
public class SemanticVersionTest_parse {
|
||||||
|
|
||||||
|
private final String input;
|
||||||
|
private final SemanticVersion output;
|
||||||
|
|
||||||
|
@Parameterized.Parameters
|
||||||
|
public static Collection<Object[]> data() {
|
||||||
|
return Arrays.asList(new Object[][]{
|
||||||
|
{ "0.0.0", new SemanticVersion(0, 0, 0)},
|
||||||
|
{ "1.2.3", new SemanticVersion(1, 2, 3)},
|
||||||
|
{ "111.222.333", new SemanticVersion(111, 222, 333)},
|
||||||
|
{ "v1.2.3", null },
|
||||||
|
{ "1.2.3x", null },
|
||||||
|
{ "peter.ben.parker", null },
|
||||||
|
{ "", null}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public SemanticVersionTest_parse(String input, SemanticVersion output) {
|
||||||
|
this.input = input;
|
||||||
|
this.output = output;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parse() {
|
||||||
|
assertEquals(output, SemanticVersion.parse(input));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
package org.whispersystems.signalservice.api.push.exceptions;
|
||||||
|
|
||||||
|
public class DeprecatedVersionException extends NonSuccessfulResponseCodeException {
|
||||||
|
}
|
|
@ -49,6 +49,7 @@ import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedE
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException;
|
import org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.ConflictException;
|
import org.whispersystems.signalservice.api.push.exceptions.ConflictException;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.ContactManifestMismatchException;
|
import org.whispersystems.signalservice.api.push.exceptions.ContactManifestMismatchException;
|
||||||
|
import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.ExpectationFailedException;
|
import org.whispersystems.signalservice.api.push.exceptions.ExpectationFailedException;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
|
import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.NoContentException;
|
import org.whispersystems.signalservice.api.push.exceptions.NoContentException;
|
||||||
|
@ -1459,6 +1460,8 @@ public class PushServiceSocket {
|
||||||
throw new LockedException(accountLockFailure.length,
|
throw new LockedException(accountLockFailure.length,
|
||||||
accountLockFailure.timeRemaining,
|
accountLockFailure.timeRemaining,
|
||||||
basicStorageCredentials);
|
basicStorageCredentials);
|
||||||
|
case 499:
|
||||||
|
throw new DeprecatedVersionException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseCode != 200 && responseCode != 204) {
|
if (responseCode != 200 && responseCode != 204) {
|
||||||
|
@ -1688,6 +1691,8 @@ public class PushServiceSocket {
|
||||||
}
|
}
|
||||||
case 429:
|
case 429:
|
||||||
throw new RateLimitException("Rate limit exceeded: " + response.code());
|
throw new RateLimitException("Rate limit exceeded: " + response.code());
|
||||||
|
case 499:
|
||||||
|
throw new DeprecatedVersionException();
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new NonSuccessfulResponseCodeException("Response: " + response);
|
throw new NonSuccessfulResponseCodeException("Response: " + response);
|
||||||
|
|
Loading…
Add table
Reference in a new issue