Skip to content

Commit 236b049

Browse files
committed
Write a set field in IssuedReceiptsManager
1 parent f68ddf6 commit 236b049

File tree

2 files changed

+47
-6
lines changed

2 files changed

+47
-6
lines changed

service/src/main/java/org/whispersystems/textsecuregcm/storage/IssuedReceiptsManager.java

+13-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
import java.security.NoSuchAlgorithmException;
1818
import java.time.Duration;
1919
import java.time.Instant;
20+
import java.util.Collections;
21+
import java.util.List;
2022
import java.util.Map;
2123
import java.util.Objects;
24+
import java.util.UUID;
2225
import java.util.concurrent.CompletableFuture;
2326
import java.util.concurrent.CompletionException;
2427
import java.util.function.Consumer;
@@ -27,6 +30,8 @@
2730
import javax.crypto.spec.SecretKeySpec;
2831
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialRequest;
2932
import org.whispersystems.textsecuregcm.subscriptions.PaymentProvider;
33+
import org.whispersystems.textsecuregcm.util.AttributeValues;
34+
import software.amazon.awssdk.core.SdkBytes;
3035
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
3136
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
3237
import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException;
@@ -39,6 +44,8 @@ public class IssuedReceiptsManager {
3944
public static final String KEY_ISSUED_RECEIPT_TAG = "B"; // B
4045
public static final String KEY_EXPIRATION = "E"; // N
4146

47+
public static final String KEY_ISSUED_RECEIPT_TAG_SET = "T"; // BS
48+
4249
private final String table;
4350
private final Duration expiration;
4451
private final DynamoDbAsyncClient dynamoDbAsyncClient;
@@ -79,20 +86,24 @@ public CompletableFuture<Void> recordIssuance(
7986
} else {
8087
key = s(processor.name() + "_" + processorItemId);
8188
}
89+
final byte[] tag = generateIssuedReceiptTag(request);
8290
UpdateItemRequest updateItemRequest = UpdateItemRequest.builder()
8391
.tableName(table)
8492
.key(Map.of(KEY_PROCESSOR_ITEM_ID, key))
8593
.conditionExpression("attribute_not_exists(#key) OR #tag = :tag")
8694
.returnValues(ReturnValue.NONE)
8795
.updateExpression("SET "
8896
+ "#tag = if_not_exists(#tag, :tag), "
89-
+ "#exp = if_not_exists(#exp, :exp)")
97+
+ "#exp = if_not_exists(#exp, :exp) "
98+
+ "ADD #tags :singletonTag")
9099
.expressionAttributeNames(Map.of(
91100
"#key", KEY_PROCESSOR_ITEM_ID,
92101
"#tag", KEY_ISSUED_RECEIPT_TAG,
102+
"#tags", KEY_ISSUED_RECEIPT_TAG_SET,
93103
"#exp", KEY_EXPIRATION))
94104
.expressionAttributeValues(Map.of(
95-
":tag", b(generateIssuedReceiptTag(request)),
105+
":tag", b(tag),
106+
":singletonTag", AttributeValue.fromBs(List.of(SdkBytes.fromByteArray(tag))),
96107
":exp", n(now.plus(expiration).getEpochSecond())))
97108
.build();
98109
return dynamoDbAsyncClient.updateItem(updateItemRequest).handle((updateItemResponse, throwable) -> {

service/src/test/java/org/whispersystems/textsecuregcm/storage/IssuedReceiptsManagerTest.java

+34-4
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,25 @@
1212
import jakarta.ws.rs.ClientErrorException;
1313
import java.time.Duration;
1414
import java.time.Instant;
15+
import java.util.List;
16+
import java.util.Map;
17+
import java.util.Set;
1518
import java.util.concurrent.CompletableFuture;
19+
import java.util.stream.Collectors;
1620
import org.assertj.core.api.Condition;
1721
import org.junit.jupiter.api.BeforeEach;
1822
import org.junit.jupiter.api.Test;
1923
import org.junit.jupiter.api.extension.RegisterExtension;
2024
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialRequest;
2125
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
2226
import org.whispersystems.textsecuregcm.subscriptions.PaymentProvider;
27+
import org.whispersystems.textsecuregcm.util.AttributeValues;
2328
import org.whispersystems.textsecuregcm.util.TestRandomUtil;
29+
import software.amazon.awssdk.core.SdkBytes;
30+
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
31+
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
32+
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
33+
import software.amazon.awssdk.services.dynamodb.model.GetItemResponse;
2434

2535
class IssuedReceiptsManagerTest {
2636

@@ -29,12 +39,10 @@ class IssuedReceiptsManagerTest {
2939
@RegisterExtension
3040
static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.ISSUED_RECEIPTS);
3141

32-
ReceiptCredentialRequest receiptCredentialRequest;
3342
IssuedReceiptsManager issuedReceiptsManager;
3443

3544
@BeforeEach
3645
void beforeEach() {
37-
receiptCredentialRequest = mock(ReceiptCredentialRequest.class);
3846
issuedReceiptsManager = new IssuedReceiptsManager(
3947
Tables.ISSUED_RECEIPTS.tableName(),
4048
Duration.ofDays(90),
@@ -45,12 +53,18 @@ void beforeEach() {
4553
@Test
4654
void testRecordIssuance() {
4755
Instant now = Instant.ofEpochSecond(NOW_EPOCH_SECONDS);
48-
byte[] request1 = TestRandomUtil.nextBytes(20);
49-
when(receiptCredentialRequest.serialize()).thenReturn(request1);
56+
final ReceiptCredentialRequest receiptCredentialRequest = randomReceiptCredentialRequest();
5057
CompletableFuture<Void> future = issuedReceiptsManager.recordIssuance("item-1", PaymentProvider.STRIPE,
5158
receiptCredentialRequest, now);
5259
assertThat(future).succeedsWithin(Duration.ofSeconds(3));
5360

61+
final Map<String, AttributeValue> item = getItem("item-1").item();
62+
final Set<byte[]> tagSet = item.get(IssuedReceiptsManager.KEY_ISSUED_RECEIPT_TAG_SET).bs()
63+
.stream()
64+
.map(SdkBytes::asByteArray)
65+
.collect(Collectors.toSet());
66+
assertThat(tagSet).containsExactly(item.get(IssuedReceiptsManager.KEY_ISSUED_RECEIPT_TAG).b().asByteArray());
67+
5468
// same request should succeed
5569
future = issuedReceiptsManager.recordIssuance("item-1", PaymentProvider.STRIPE, receiptCredentialRequest,
5670
now);
@@ -74,4 +88,20 @@ void testRecordIssuance() {
7488
now);
7589
assertThat(future).succeedsWithin(Duration.ofSeconds(3));
7690
}
91+
92+
93+
private GetItemResponse getItem(final String itemId) {
94+
final DynamoDbClient client = DYNAMO_DB_EXTENSION.getDynamoDbClient();
95+
return client.getItem(GetItemRequest.builder()
96+
.tableName(Tables.ISSUED_RECEIPTS.tableName())
97+
.key(Map.of(IssuedReceiptsManager.KEY_PROCESSOR_ITEM_ID, AttributeValues.s(itemId)))
98+
.build());
99+
}
100+
101+
private static ReceiptCredentialRequest randomReceiptCredentialRequest() {
102+
final ReceiptCredentialRequest request = mock(ReceiptCredentialRequest.class);
103+
final byte[] bytes = TestRandomUtil.nextBytes(20);
104+
when(request.serialize()).thenReturn(bytes);
105+
return request;
106+
}
77107
}

0 commit comments

Comments
 (0)