Add a repository for client release information
This commit is contained in:
parent
60cc0c482e
commit
10689843b0
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import com.vdurmont.semver4j.Semver;
|
||||
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
|
||||
import java.time.Instant;
|
||||
|
||||
public record ClientRelease(ClientPlatform platform, Semver version, Instant release, Instant expiration) {
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import com.vdurmont.semver4j.Semver;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.util.function.Tuple2;
|
||||
import reactor.util.function.Tuples;
|
||||
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
|
||||
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
|
||||
import software.amazon.awssdk.services.dynamodb.model.ScanRequest;
|
||||
import javax.annotation.Nullable;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
public class ClientReleases {
|
||||
|
||||
private final DynamoDbAsyncClient dynamoDbAsyncClient;
|
||||
private final String tableName;
|
||||
|
||||
public static final String ATTR_PLATFORM = "P";
|
||||
public static final String ATTR_VERSION = "V";
|
||||
public static final String ATTR_RELEASE_TIMESTAMP = "T";
|
||||
public static final String ATTR_EXPIRATION = "E";
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ClientReleases.class);
|
||||
|
||||
public ClientReleases(final DynamoDbAsyncClient dynamoDbAsyncClient, final String tableName) {
|
||||
this.dynamoDbAsyncClient = dynamoDbAsyncClient;
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
public Map<ClientPlatform, Map<Semver, ClientRelease>> getClientReleases() {
|
||||
return Collections.unmodifiableMap(
|
||||
Flux.from(dynamoDbAsyncClient.scanPaginator(ScanRequest.builder()
|
||||
.tableName(tableName)
|
||||
.build())
|
||||
.items())
|
||||
.mapNotNull(ClientReleases::releaseFromItem)
|
||||
.groupBy(ClientRelease::platform)
|
||||
.flatMap(groupedFlux -> groupedFlux.collectMap(ClientRelease::version)
|
||||
.map(releasesByVersion -> Tuples.of(groupedFlux.key(), releasesByVersion)))
|
||||
.collectMap(Tuple2::getT1, Tuple2::getT2)
|
||||
.blockOptional()
|
||||
.orElseGet(Collections::emptyMap));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static ClientRelease releaseFromItem(final Map<String, AttributeValue> item) {
|
||||
try {
|
||||
final ClientPlatform platform = ClientPlatform.valueOf(item.get(ATTR_PLATFORM).s());
|
||||
final Semver version = new Semver(item.get(ATTR_VERSION).s());
|
||||
final Instant release = Instant.ofEpochSecond(Long.parseLong(item.get(ATTR_RELEASE_TIMESTAMP).n()));
|
||||
final Instant expiration = Instant.ofEpochSecond(Long.parseLong(item.get(ATTR_EXPIRATION).n()));
|
||||
|
||||
return new ClientRelease(platform, version, release, expiration);
|
||||
} catch (final Exception e) {
|
||||
logger.warn("Failed to parse client release item", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import com.vdurmont.semver4j.Semver;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
|
||||
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
|
||||
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
|
||||
|
||||
class ClientReleasesTest {
|
||||
|
||||
private ClientReleases clientReleases;
|
||||
|
||||
@RegisterExtension
|
||||
static final DynamoDbExtension DYNAMO_DB_EXTENSION =
|
||||
new DynamoDbExtension(DynamoDbExtensionSchema.Tables.CLIENT_RELEASES);
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
clientReleases = new ClientReleases(DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(),
|
||||
DynamoDbExtensionSchema.Tables.CLIENT_RELEASES.tableName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClientReleases() {
|
||||
final Instant releaseTimestamp = Instant.now().truncatedTo(ChronoUnit.SECONDS);
|
||||
final Instant expiration = releaseTimestamp.plusSeconds(60);
|
||||
|
||||
storeClientRelease("IOS", "1.2.3", releaseTimestamp, expiration);
|
||||
storeClientRelease("IOS", "not-a-valid-version", releaseTimestamp, expiration);
|
||||
storeClientRelease("ANDROID", "4.5.6", releaseTimestamp, expiration);
|
||||
storeClientRelease("UNRECOGNIZED_PLATFORM", "7.8.9", releaseTimestamp, expiration);
|
||||
|
||||
final Map<ClientPlatform, Map<Semver, ClientRelease>> expectedVersions = Map.of(
|
||||
ClientPlatform.IOS, Map.of(new Semver("1.2.3"), new ClientRelease(ClientPlatform.IOS, new Semver("1.2.3"), releaseTimestamp, expiration)),
|
||||
ClientPlatform.ANDROID, Map.of(new Semver("4.5.6"), new ClientRelease(ClientPlatform.ANDROID, new Semver("4.5.6"), releaseTimestamp, expiration)));
|
||||
|
||||
assertEquals(expectedVersions, clientReleases.getClientReleases());
|
||||
}
|
||||
|
||||
private void storeClientRelease(final String platform, final String version, final Instant release, final Instant expiration) {
|
||||
DYNAMO_DB_EXTENSION.getDynamoDbClient().putItem(PutItemRequest.builder()
|
||||
.tableName(DynamoDbExtensionSchema.Tables.CLIENT_RELEASES.tableName())
|
||||
.item(Map.of(
|
||||
ClientReleases.ATTR_PLATFORM, AttributeValue.builder().s(platform).build(),
|
||||
ClientReleases.ATTR_VERSION, AttributeValue.builder().s(version).build(),
|
||||
ClientReleases.ATTR_RELEASE_TIMESTAMP,
|
||||
AttributeValue.builder().n(String.valueOf(release.getEpochSecond())).build(),
|
||||
ClientReleases.ATTR_EXPIRATION,
|
||||
AttributeValue.builder().n(String.valueOf(expiration.getEpochSecond())).build()))
|
||||
.build());
|
||||
}
|
||||
}
|
|
@ -31,12 +31,12 @@ import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
|
|||
public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback {
|
||||
|
||||
public interface TableSchema {
|
||||
public String tableName();
|
||||
public String hashKeyName();
|
||||
public String rangeKeyName();
|
||||
public List<AttributeDefinition> attributeDefinitions();
|
||||
public List<GlobalSecondaryIndex> globalSecondaryIndexes();
|
||||
public List<LocalSecondaryIndex> localSecondaryIndexes();
|
||||
String tableName();
|
||||
String hashKeyName();
|
||||
String rangeKeyName();
|
||||
List<AttributeDefinition> attributeDefinitions();
|
||||
List<GlobalSecondaryIndex> globalSecondaryIndexes();
|
||||
List<LocalSecondaryIndex> localSecondaryIndexes();
|
||||
}
|
||||
|
||||
record RawSchema(
|
||||
|
|
|
@ -47,6 +47,21 @@ public final class DynamoDbExtensionSchema {
|
|||
),
|
||||
List.of()),
|
||||
|
||||
CLIENT_RELEASES("client_releases_test",
|
||||
ClientReleases.ATTR_PLATFORM,
|
||||
ClientReleases.ATTR_VERSION,
|
||||
List.of(
|
||||
AttributeDefinition.builder()
|
||||
.attributeName(ClientReleases.ATTR_PLATFORM)
|
||||
.attributeType(ScalarAttributeType.S)
|
||||
.build(),
|
||||
AttributeDefinition.builder()
|
||||
.attributeName(ClientReleases.ATTR_VERSION)
|
||||
.attributeType(ScalarAttributeType.S)
|
||||
.build()),
|
||||
List.of(),
|
||||
List.of()),
|
||||
|
||||
DELETED_ACCOUNTS("deleted_accounts_test",
|
||||
DeletedAccounts.KEY_ACCOUNT_E164,
|
||||
null,
|
||||
|
|
Loading…
Reference in New Issue