Use an explicit-allow model for tagging client versions in metrics
This commit is contained in:
parent
c315b34395
commit
adf6c751ee
|
@ -22,47 +22,10 @@ public class UserAgentTagUtil {
|
||||||
|
|
||||||
public static final String PLATFORM_TAG = "platform";
|
public static final String PLATFORM_TAG = "platform";
|
||||||
public static final String VERSION_TAG = "clientVersion";
|
public static final String VERSION_TAG = "clientVersion";
|
||||||
static final List<Tag> OVERFLOW_TAGS = List.of(Tag.of(PLATFORM_TAG, "overflow"), Tag.of(VERSION_TAG, "overflow"));
|
|
||||||
static final List<Tag> UNRECOGNIZED_TAGS = List.of(Tag.of(PLATFORM_TAG, "unrecognized"),
|
|
||||||
Tag.of(VERSION_TAG, "unrecognized"));
|
|
||||||
|
|
||||||
private static final Map<ClientPlatform, Semver> 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<Pair<ClientPlatform, Semver>> SEEN_VERSIONS = new HashSet<>();
|
|
||||||
|
|
||||||
private UserAgentTagUtil() {
|
private UserAgentTagUtil() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Tag> getUserAgentTags(final String userAgentString) {
|
|
||||||
try {
|
|
||||||
final UserAgent userAgent = UserAgentUtil.parseUserAgentString(userAgentString);
|
|
||||||
final List<Tag> 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) {
|
public static Tag getPlatformTag(final String userAgentString) {
|
||||||
String platform;
|
String platform;
|
||||||
|
|
||||||
|
@ -75,12 +38,16 @@ public class UserAgentTagUtil {
|
||||||
return Tag.of(PLATFORM_TAG, platform);
|
return Tag.of(PLATFORM_TAG, platform);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean allowVersion(final ClientPlatform platform, final Semver version) {
|
public static Optional<Tag> getClientVersionTag(final String userAgentString, final Map<ClientPlatform, Set<Semver>> taggedVersions) {
|
||||||
final Pair<ClientPlatform, Semver> platformAndVersion = new Pair<>(platform, version);
|
try {
|
||||||
|
final UserAgent userAgent = UserAgentUtil.parseUserAgentString(userAgentString);
|
||||||
|
|
||||||
synchronized (SEEN_VERSIONS) {
|
if (taggedVersions.getOrDefault(userAgent.getPlatform(), Collections.emptySet()).contains(userAgent.getVersion())) {
|
||||||
return SEEN_VERSIONS.contains(platformAndVersion) || (SEEN_VERSIONS.size() < MAX_VERSIONS && SEEN_VERSIONS.add(
|
return Optional.of(Tag.of(VERSION_TAG, userAgent.getVersion().toString()));
|
||||||
platformAndVersion));
|
}
|
||||||
|
} catch (final UnrecognizedUserAgentException ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,76 +6,30 @@
|
||||||
package org.whispersystems.textsecuregcm.metrics;
|
package org.whispersystems.textsecuregcm.metrics;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
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 io.micrometer.core.instrument.Tag;
|
||||||
import java.util.HashSet;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.Arguments;
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
|
||||||
|
|
||||||
class UserAgentTagUtilTest {
|
class UserAgentTagUtilTest {
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource
|
@MethodSource
|
||||||
public void testGetUserAgentTags(final String userAgent, final List<Tag> expectedTags) {
|
void getPlatformTag(final String userAgent, final Tag expectedTag) {
|
||||||
assertEquals(new HashSet<>(expectedTags),
|
|
||||||
new HashSet<>(UserAgentTagUtil.getUserAgentTags(userAgent)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<Tag> 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<Arguments> 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<Tag> 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) {
|
|
||||||
assertEquals(expectedTag, UserAgentTagUtil.getPlatformTag(userAgent));
|
assertEquals(expectedTag, UserAgentTagUtil.getPlatformTag(userAgent));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Stream<Arguments> argumentsForTestGetPlatformTag() {
|
private static Stream<Arguments> getPlatformTag() {
|
||||||
return Stream.of(
|
return Stream.of(
|
||||||
Arguments.of("This is obviously not a reasonable User-Agent string.",
|
Arguments.of("This is obviously not a reasonable User-Agent string.", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "unrecognized")),
|
||||||
Tag.of(UserAgentTagUtil.PLATFORM_TAG, "unrecognized")),
|
|
||||||
Arguments.of(null, 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-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")),
|
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/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-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.22.45-foo-0", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "desktop")),
|
||||||
Arguments.of("Signal-Desktop/1.34.5-beta.1-fakeclientemporium",
|
Arguments.of("Signal-Desktop/1.34.5-beta.1-fakeclientemporium", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "desktop")),
|
||||||
Tag.of(UserAgentTagUtil.PLATFORM_TAG, "desktop")),
|
|
||||||
Arguments.of("Signal-Desktop/1.32.0-beta.3", 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<ClientPlatform, Set<Semver>> taggedVersions, final Optional<Tag> expectedTag) {
|
||||||
|
assertEquals(expectedTag, UserAgentTagUtil.getClientVersionTag(userAgent, taggedVersions));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> 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())
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue