diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/metrics/UserAgentTagUtil.java b/service/src/main/java/org/whispersystems/textsecuregcm/metrics/UserAgentTagUtil.java index 8811d2dce..1ba6b0046 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/metrics/UserAgentTagUtil.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/metrics/UserAgentTagUtil.java @@ -22,47 +22,10 @@ public class UserAgentTagUtil { public static final String PLATFORM_TAG = "platform"; public static final String VERSION_TAG = "clientVersion"; - static final List OVERFLOW_TAGS = List.of(Tag.of(PLATFORM_TAG, "overflow"), Tag.of(VERSION_TAG, "overflow")); - static final List UNRECOGNIZED_TAGS = List.of(Tag.of(PLATFORM_TAG, "unrecognized"), - Tag.of(VERSION_TAG, "unrecognized")); - - private static final Map MINIMUM_VERSION_BY_PLATFORM = new EnumMap<>(ClientPlatform.class); - - static { - MINIMUM_VERSION_BY_PLATFORM.put(ClientPlatform.ANDROID, new Semver("4.0.0")); - MINIMUM_VERSION_BY_PLATFORM.put(ClientPlatform.DESKTOP, new Semver("1.0.0")); - MINIMUM_VERSION_BY_PLATFORM.put(ClientPlatform.IOS, new Semver("3.0.0")); - } - - static final int MAX_VERSIONS = 1_000; - private static final Set> SEEN_VERSIONS = new HashSet<>(); private UserAgentTagUtil() { } - public static List getUserAgentTags(final String userAgentString) { - try { - final UserAgent userAgent = UserAgentUtil.parseUserAgentString(userAgentString); - final List tags; - - if (userAgent.getVersion().isStable() && userAgent.getVersion() - .isGreaterThanOrEqualTo(MINIMUM_VERSION_BY_PLATFORM.get(userAgent.getPlatform()))) { - if (allowVersion(userAgent.getPlatform(), userAgent.getVersion())) { - tags = List.of(Tag.of(PLATFORM_TAG, userAgent.getPlatform().name().toLowerCase()), - Tag.of(VERSION_TAG, userAgent.getVersion().toString())); - } else { - tags = OVERFLOW_TAGS; - } - } else { - tags = UNRECOGNIZED_TAGS; - } - - return tags; - } catch (final UnrecognizedUserAgentException e) { - return UNRECOGNIZED_TAGS; - } - } - public static Tag getPlatformTag(final String userAgentString) { String platform; @@ -75,12 +38,16 @@ public class UserAgentTagUtil { return Tag.of(PLATFORM_TAG, platform); } - private static boolean allowVersion(final ClientPlatform platform, final Semver version) { - final Pair platformAndVersion = new Pair<>(platform, version); + public static Optional getClientVersionTag(final String userAgentString, final Map> taggedVersions) { + try { + final UserAgent userAgent = UserAgentUtil.parseUserAgentString(userAgentString); - synchronized (SEEN_VERSIONS) { - return SEEN_VERSIONS.contains(platformAndVersion) || (SEEN_VERSIONS.size() < MAX_VERSIONS && SEEN_VERSIONS.add( - platformAndVersion)); + if (taggedVersions.getOrDefault(userAgent.getPlatform(), Collections.emptySet()).contains(userAgent.getVersion())) { + return Optional.of(Tag.of(VERSION_TAG, userAgent.getVersion().toString())); + } + } catch (final UnrecognizedUserAgentException ignored) { } + + return Optional.empty(); } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/metrics/UserAgentTagUtilTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/metrics/UserAgentTagUtilTest.java index e324eaeb0..de507ef8a 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/metrics/UserAgentTagUtilTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/metrics/UserAgentTagUtilTest.java @@ -6,76 +6,30 @@ package org.whispersystems.textsecuregcm.metrics; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import com.vdurmont.semver4j.Semver; import io.micrometer.core.instrument.Tag; -import java.util.HashSet; -import java.util.List; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.stream.Stream; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.whispersystems.textsecuregcm.util.ua.ClientPlatform; class UserAgentTagUtilTest { @ParameterizedTest @MethodSource - public void testGetUserAgentTags(final String userAgent, final List expectedTags) { - assertEquals(new HashSet<>(expectedTags), - new HashSet<>(UserAgentTagUtil.getUserAgentTags(userAgent))); - } - - private static List platformVersionTags(String platform, String version) { - return List.of(Tag.of(UserAgentTagUtil.PLATFORM_TAG, platform), Tag.of(UserAgentTagUtil.VERSION_TAG, version)); - } - - @SuppressWarnings("unused") - private static Stream testGetUserAgentTags() { - return Stream.of( - Arguments.of("This is obviously not a reasonable User-Agent string.", UserAgentTagUtil.UNRECOGNIZED_TAGS), - Arguments.of(null, UserAgentTagUtil.UNRECOGNIZED_TAGS), - Arguments.of("Signal-Android/4.53.7 (Android 8.1)", platformVersionTags("android", "4.53.7")), - Arguments.of("Signal-Desktop/1.2.3", platformVersionTags("desktop", "1.2.3")), - Arguments.of("Signal-iOS/3.9.0 (iPhone; iOS 12.2; Scale/3.00)", platformVersionTags("ios", "3.9.0")), - Arguments.of("Signal-Android/1.2.3 (Android 8.1)", UserAgentTagUtil.UNRECOGNIZED_TAGS), - Arguments.of("Signal-Desktop/3.9.0", platformVersionTags("desktop", "3.9.0")), - Arguments.of("Signal-iOS/4.53.7 (iPhone; iOS 12.2; Scale/3.00)", platformVersionTags("ios", "4.53.7")), - Arguments.of("Signal-Android/4.68.3 (Android 9)", platformVersionTags("android", "4.68.3")), - Arguments.of("Signal-Android/1.2.3 (Android 4.3)", UserAgentTagUtil.UNRECOGNIZED_TAGS), - Arguments.of("Signal-Android/4.68.3.0-bobsbootlegclient", UserAgentTagUtil.UNRECOGNIZED_TAGS), - Arguments.of("Signal-Desktop/1.22.45-foo-0", UserAgentTagUtil.UNRECOGNIZED_TAGS), - Arguments.of("Signal-Desktop/1.34.5-beta.1-fakeclientemporium", UserAgentTagUtil.UNRECOGNIZED_TAGS), - Arguments.of("Signal-Desktop/1.32.0-beta.3", UserAgentTagUtil.UNRECOGNIZED_TAGS) - ); - } - - @Test - void testGetUserAgentTagsFlooded() { - for (int i = 0; i < UserAgentTagUtil.MAX_VERSIONS; i++) { - UserAgentTagUtil.getUserAgentTags(String.format("Signal-Android/4.0.%d (Android 8.1)", i)); - } - - assertEquals(UserAgentTagUtil.OVERFLOW_TAGS, - UserAgentTagUtil.getUserAgentTags("Signal-Android/4.1.0 (Android 8.1)")); - - final List tags = UserAgentTagUtil.getUserAgentTags("Signal-Android/4.0.0 (Android 8.1)"); - - assertEquals(2, tags.size()); - assertTrue(tags.contains(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android"))); - assertTrue(tags.contains(Tag.of(UserAgentTagUtil.VERSION_TAG, "4.0.0"))); - } - - @ParameterizedTest - @MethodSource("argumentsForTestGetPlatformTag") - public void testGetPlatformTag(final String userAgent, final Tag expectedTag) { + void getPlatformTag(final String userAgent, final Tag expectedTag) { assertEquals(expectedTag, UserAgentTagUtil.getPlatformTag(userAgent)); } - private static Stream argumentsForTestGetPlatformTag() { + private static Stream getPlatformTag() { return Stream.of( - Arguments.of("This is obviously not a reasonable User-Agent string.", - Tag.of(UserAgentTagUtil.PLATFORM_TAG, "unrecognized")), + Arguments.of("This is obviously not a reasonable User-Agent string.", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "unrecognized")), Arguments.of(null, Tag.of(UserAgentTagUtil.PLATFORM_TAG, "unrecognized")), Arguments.of("Signal-Android/4.53.7 (Android 8.1)", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android")), Arguments.of("Signal-Desktop/1.2.3", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "desktop")), @@ -87,9 +41,35 @@ class UserAgentTagUtilTest { Arguments.of("Signal-Android/1.2.3 (Android 4.3)", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android")), Arguments.of("Signal-Android/4.68.3.0-bobsbootlegclient", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android")), Arguments.of("Signal-Desktop/1.22.45-foo-0", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "desktop")), - Arguments.of("Signal-Desktop/1.34.5-beta.1-fakeclientemporium", - Tag.of(UserAgentTagUtil.PLATFORM_TAG, "desktop")), + Arguments.of("Signal-Desktop/1.34.5-beta.1-fakeclientemporium", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "desktop")), Arguments.of("Signal-Desktop/1.32.0-beta.3", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "desktop")) ); } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + @ParameterizedTest + @MethodSource + void getClientVersionTag(final String userAgent, final Map> taggedVersions, final Optional expectedTag) { + assertEquals(expectedTag, UserAgentTagUtil.getClientVersionTag(userAgent, taggedVersions)); + } + + private static Stream getClientVersionTag() { + return Stream.of( + Arguments.of("Signal-Android/1.2.3 (Android 9)", + Map.of(ClientPlatform.ANDROID, Set.of(new Semver("1.2.3"))), + Optional.of(Tag.of(UserAgentTagUtil.VERSION_TAG, "1.2.3"))), + + Arguments.of("Signal-Android/1.2.3 (Android 9)", + Collections.emptyMap(), + Optional.empty()), + + Arguments.of("Signal-Android/1.2.3.0-bobsbootlegclient", + Map.of(ClientPlatform.ANDROID, Set.of(new Semver("1.2.3"))), + Optional.empty()), + + Arguments.of("Signal-Desktop/1.2.3", + Map.of(ClientPlatform.ANDROID, Set.of(new Semver("1.2.3"))), + Optional.empty()) + ); + } }