From 9eafa118d54c5d764c59803c55e696a162221137 Mon Sep 17 00:00:00 2001 From: Katherine Date: Wed, 21 Aug 2024 17:08:06 -0400 Subject: [PATCH] Return key transparency protobufs encoded as base64 strings --- .../KeyTransparencyController.java | 34 ++---------- .../KeyTransparencyMonitorResponse.java | 38 ++----------- .../KeyTransparencySearchResponse.java | 27 +++++---- .../KeyTransparencyServiceClient.java | 11 ++-- .../util/FullTreeHeadProtobufAdapter.java | 20 ------- .../util/MonitorProofProtobufAdapter.java | 20 ------- .../textsecuregcm/util/ProtobufAdapter.java | 46 ---------------- .../util/SearchResponseProtobufAdapter.java | 20 ------- .../KeyTransparencyControllerTest.java | 54 +++--------------- .../util/ProtobufAdapterTest.java | 55 ------------------- 10 files changed, 36 insertions(+), 289 deletions(-) delete mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/util/FullTreeHeadProtobufAdapter.java delete mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/util/MonitorProofProtobufAdapter.java delete mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/util/ProtobufAdapter.java delete mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/util/SearchResponseProtobufAdapter.java delete mode 100644 service/src/test/java/org/whispersystems/textsecuregcm/util/ProtobufAdapterTest.java diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeyTransparencyController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeyTransparencyController.java index b983005ae..4e4a3e9ee 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeyTransparencyController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeyTransparencyController.java @@ -14,9 +14,6 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import katie.MonitorKey; -import katie.MonitorProof; -import katie.MonitorResponse; -import katie.SearchResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice; @@ -93,13 +90,13 @@ public class KeyTransparencyController { requireNotAuthenticated(authenticatedAccount); try { - final CompletableFuture aciSearchKeyResponseFuture = keyTransparencyServiceClient.search( + final CompletableFuture aciSearchKeyResponseFuture = keyTransparencyServiceClient.search( getFullSearchKeyByteString(ACI_PREFIX, request.aci().toCompactByteArray()), request.lastTreeHeadSize(), request.distinguishedTreeHeadSize(), KEY_TRANSPARENCY_RPC_TIMEOUT); - final CompletableFuture e164SearchKeyResponseFuture = request.e164() + final CompletableFuture e164SearchKeyResponseFuture = request.e164() .map(e164 -> keyTransparencyServiceClient.search( getFullSearchKeyByteString(E164_PREFIX, e164.getBytes(StandardCharsets.UTF_8)), request.lastTreeHeadSize(), @@ -107,7 +104,7 @@ public class KeyTransparencyController { KEY_TRANSPARENCY_RPC_TIMEOUT)) .orElse(CompletableFuture.completedFuture(null)); - final CompletableFuture usernameHashSearchKeyResponseFuture = request.usernameHash() + final CompletableFuture usernameHashSearchKeyResponseFuture = request.usernameHash() .map(usernameHash -> keyTransparencyServiceClient.search( getFullSearchKeyByteString(USERNAME_PREFIX, request.usernameHash().get()), request.lastTreeHeadSize(), @@ -171,32 +168,11 @@ public class KeyTransparencyController { request.e164Positions().get())) ); - final MonitorResponse monitorResponse = keyTransparencyServiceClient.monitor( + return new KeyTransparencyMonitorResponse(keyTransparencyServiceClient.monitor( monitorKeys, request.lastNonDistinguishedTreeHeadSize(), request.lastDistinguishedTreeHeadSize(), - KEY_TRANSPARENCY_RPC_TIMEOUT).join(); - - MonitorProof usernameHashMonitorProof = null; - MonitorProof e164MonitorProof = null; - - // In the future we'll update KT's monitor response structure to enumerate each monitor key proof - // rather than returning everything in a list - if (monitorResponse.getContactProofsCount() == 3) { - e164MonitorProof = monitorResponse.getContactProofs(1); - usernameHashMonitorProof = monitorResponse.getContactProofs(2); - } else if (monitorResponse.getContactProofsCount() == 2) { - if (request.usernameHash().isPresent()) { - usernameHashMonitorProof = monitorResponse.getContactProofs(1); - } else if (request.e164().isPresent()) { - e164MonitorProof = monitorResponse.getContactProofs(1); - } - } - return new KeyTransparencyMonitorResponse(monitorResponse.getTreeHead(), - monitorResponse.getContactProofs(0), - Optional.ofNullable(e164MonitorProof), - Optional.ofNullable(usernameHashMonitorProof), - monitorResponse.getInclusionList().stream().map(ByteString::toByteArray).toList()); + KEY_TRANSPARENCY_RPC_TIMEOUT).join()); } catch (final CancellationException exception) { LOGGER.error("Unexpected cancellation from key transparency service", exception); throw new ServerErrorException(Response.Status.SERVICE_UNAVAILABLE, exception); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/KeyTransparencyMonitorResponse.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/KeyTransparencyMonitorResponse.java index a60447403..74ac7fbbd 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/KeyTransparencyMonitorResponse.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/KeyTransparencyMonitorResponse.java @@ -8,44 +8,14 @@ package org.whispersystems.textsecuregcm.entities; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import io.swagger.v3.oas.annotations.media.Schema; -import katie.FullTreeHead; -import katie.MonitorProof; import org.whispersystems.textsecuregcm.util.ByteArrayAdapter; -import org.whispersystems.textsecuregcm.util.FullTreeHeadProtobufAdapter; -import org.whispersystems.textsecuregcm.util.MonitorProofProtobufAdapter; import javax.validation.constraints.NotNull; -import java.util.List; -import java.util.Optional; public record KeyTransparencyMonitorResponse( @NotNull - @JsonSerialize(using = FullTreeHeadProtobufAdapter.Serializer.class) - @JsonDeserialize(using = FullTreeHeadProtobufAdapter.Deserializer.class) - @Schema(description = """ - The key transparency log's tree head along with a consistency proof and possibly an auditor-signed tree head - """) - FullTreeHead fullTreeHead, - - @NotNull - @JsonSerialize(using = MonitorProofProtobufAdapter.Serializer.class) - @JsonDeserialize(using = MonitorProofProtobufAdapter.Deserializer.class) - @Schema(description = "The monitor proof for the aci search key") - MonitorProof aciMonitorProof, - - @JsonSerialize(contentUsing = MonitorProofProtobufAdapter.Serializer.class) - @JsonDeserialize(contentUsing = MonitorProofProtobufAdapter.Deserializer.class) - @Schema(description = "The monitor proof for the e164 search key") - Optional e164MonitorProof, - - @JsonSerialize(contentUsing = MonitorProofProtobufAdapter.Serializer.class) - @JsonDeserialize(contentUsing = MonitorProofProtobufAdapter.Deserializer.class) - @Schema(description = "The monitor proof for the username hash search key") - Optional usernameHashMonitorProof, - - @NotNull - @JsonSerialize(contentUsing = ByteArrayAdapter.Serializing.class) - @JsonDeserialize(contentUsing = ByteArrayAdapter.Deserializing.class) - @Schema(description = "A list of hashes encoded in standard, unpadded base64 that prove inclusion across all monitor proofs ") - List inclusionProof + @JsonSerialize(using = ByteArrayAdapter.Serializing.class) + @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class) + @Schema(description = "The monitor response encoded in standard un-padded base64") + byte[] monitorResponse ) {} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/KeyTransparencySearchResponse.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/KeyTransparencySearchResponse.java index 700ecda23..9b3bda6d8 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/KeyTransparencySearchResponse.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/KeyTransparencySearchResponse.java @@ -8,26 +8,25 @@ package org.whispersystems.textsecuregcm.entities; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import io.swagger.v3.oas.annotations.media.Schema; -import katie.SearchResponse; -import org.whispersystems.textsecuregcm.util.SearchResponseProtobufAdapter; +import org.whispersystems.textsecuregcm.util.ByteArrayAdapter; import javax.validation.constraints.NotNull; import java.util.Optional; public record KeyTransparencySearchResponse( @NotNull - @JsonSerialize(using = SearchResponseProtobufAdapter.Serializer.class) - @JsonDeserialize(using = SearchResponseProtobufAdapter.Deserializer.class) - @Schema(description = "The search response for the aci search key") - SearchResponse aciSearchResponse, + @JsonSerialize(using = ByteArrayAdapter.Serializing.class) + @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class) + @Schema(description = "The search response for the aci search key encoded in standard un-padded base64") + byte[] aciSearchResponse, - @JsonSerialize(contentUsing = SearchResponseProtobufAdapter.Serializer.class) - @JsonDeserialize(contentUsing = SearchResponseProtobufAdapter.Deserializer.class) - @Schema(description = "The search response for the e164 search key") - Optional e164SearchResponse, + @JsonSerialize(contentUsing = ByteArrayAdapter.Serializing.class) + @JsonDeserialize(contentUsing = ByteArrayAdapter.Deserializing.class) + @Schema(description = "The search response for the e164 search key encoded in standard un-padded base64") + Optional e164SearchResponse, - @JsonSerialize(contentUsing = SearchResponseProtobufAdapter.Serializer.class) - @JsonDeserialize(contentUsing = SearchResponseProtobufAdapter.Deserializer.class) - @Schema(description = "The search response for the username hash search key") - Optional usernameHashSearchResponse + @JsonSerialize(contentUsing = ByteArrayAdapter.Serializing.class) + @JsonDeserialize(contentUsing = ByteArrayAdapter.Deserializing.class) + @Schema(description = "The search response for the username hash search key encoded in standard un-padded base64") + Optional usernameHashSearchResponse ) {} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/keytransparency/KeyTransparencyServiceClient.java b/service/src/main/java/org/whispersystems/textsecuregcm/keytransparency/KeyTransparencyServiceClient.java index b475bfa75..7f0be60ec 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/keytransparency/KeyTransparencyServiceClient.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/keytransparency/KeyTransparencyServiceClient.java @@ -1,5 +1,6 @@ package org.whispersystems.textsecuregcm.keytransparency; +import com.google.protobuf.AbstractMessageLite; import com.google.protobuf.ByteString; import io.dropwizard.lifecycle.Managed; import io.grpc.ChannelCredentials; @@ -53,7 +54,7 @@ public class KeyTransparencyServiceClient implements Managed { this.callbackExecutor = callbackExecutor; } - public CompletableFuture search( + public CompletableFuture search( final ByteString searchKey, final Optional lastTreeHeadSize, final Optional distinguishedTreeHeadSize, @@ -68,10 +69,11 @@ public class KeyTransparencyServiceClient implements Managed { searchRequestBuilder.setConsistency(consistency); return CompletableFutureUtil.toCompletableFuture(stub.withDeadline(toDeadline(timeout)) - .search(searchRequestBuilder.build()), callbackExecutor); + .search(searchRequestBuilder.build()), callbackExecutor) + .thenApply(AbstractMessageLite::toByteArray); } - public CompletableFuture monitor(final List monitorKeys, + public CompletableFuture monitor(final List monitorKeys, final Optional lastTreeHeadSize, final Optional distinguishedTreeHeadSize, final Duration timeout) { @@ -85,7 +87,8 @@ public class KeyTransparencyServiceClient implements Managed { monitorRequestBuilder.setConsistency(consistency); return CompletableFutureUtil.toCompletableFuture(stub.withDeadline(toDeadline(timeout)) - .monitor(monitorRequestBuilder.build()), callbackExecutor); + .monitor(monitorRequestBuilder.build()), callbackExecutor) + .thenApply(AbstractMessageLite::toByteArray); } private static Deadline toDeadline(final Duration timeout) { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/FullTreeHeadProtobufAdapter.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/FullTreeHeadProtobufAdapter.java deleted file mode 100644 index a48c76985..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/util/FullTreeHeadProtobufAdapter.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2024 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.util; - -import katie.FullTreeHead; - -public class FullTreeHeadProtobufAdapter { - - public static class Serializer extends ProtobufAdapter.Serializer {} - - public static class Deserializer extends ProtobufAdapter.Deserializer { - - public Deserializer() { - super(FullTreeHead::newBuilder); - } - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/MonitorProofProtobufAdapter.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/MonitorProofProtobufAdapter.java deleted file mode 100644 index 612272354..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/util/MonitorProofProtobufAdapter.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2024 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.util; - -import katie.MonitorProof; - -public class MonitorProofProtobufAdapter { - - public static class Serializer extends ProtobufAdapter.Serializer {} - - public static class Deserializer extends ProtobufAdapter.Deserializer { - - public Deserializer() { - super(MonitorProof::newBuilder); - } - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/ProtobufAdapter.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/ProtobufAdapter.java deleted file mode 100644 index 19c678a2b..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/util/ProtobufAdapter.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2024 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.util; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.google.protobuf.Message; -import com.google.protobuf.util.JsonFormat; - -import java.io.IOException; -import java.util.function.Supplier; - -public class ProtobufAdapter { - - public static class Serializer extends JsonSerializer { - - @Override - public void serialize(T message, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) - throws IOException { - jsonGenerator.writeString(JsonFormat.printer().print(message)); - } - } - - public static class Deserializer extends JsonDeserializer { - - private final Supplier builderSupplier; - - public Deserializer(Supplier builderSupplier) { - this.builderSupplier = builderSupplier; - } - - @Override - public T deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { - Message.Builder builder = builderSupplier.get(); - JsonFormat.parser().ignoringUnknownFields().merge(jsonParser.getValueAsString(), builder); - return (T) builder.build(); - } - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/SearchResponseProtobufAdapter.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/SearchResponseProtobufAdapter.java deleted file mode 100644 index 2be09aade..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/util/SearchResponseProtobufAdapter.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2024 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.util; - -import katie.SearchResponse; - -public class SearchResponseProtobufAdapter { - - public static class Serializer extends ProtobufAdapter.Serializer {} - - public static class Deserializer extends ProtobufAdapter.Deserializer { - - public Deserializer() { - super(SearchResponse::newBuilder); - } - } -} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/KeyTransparencyControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/KeyTransparencyControllerTest.java index d76b36040..422167bf3 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/KeyTransparencyControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/KeyTransparencyControllerTest.java @@ -13,10 +13,6 @@ import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; import io.dropwizard.testing.junit5.ResourceExtension; import io.grpc.Status; import io.grpc.StatusRuntimeException; -import katie.FullTreeHead; -import katie.MonitorProof; -import katie.MonitorResponse; -import katie.SearchResponse; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -46,7 +42,6 @@ import javax.ws.rs.client.Invocation; import javax.ws.rs.core.Response; import java.io.UncheckedIOException; import java.time.Duration; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -123,9 +118,8 @@ public class KeyTransparencyControllerTest { @ParameterizedTest @MethodSource void searchSuccess(final Optional e164, final Optional usernameHash, final int expectedNumClientCalls) { - final SearchResponse searchResponse = SearchResponse.newBuilder().build(); when(keyTransparencyServiceClient.search(any(), any(), any(), any())) - .thenReturn(CompletableFuture.completedFuture(searchResponse)); + .thenReturn(CompletableFuture.completedFuture(TestRandomUtil.nextBytes(16))); final Invocation.Builder request = resources.getJerseyTest() .target("/v1/key-transparency/search") @@ -235,25 +229,10 @@ public class KeyTransparencyControllerTest { } } - @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - @ParameterizedTest - @MethodSource - void monitorSuccess( - final Optional e164, - final Optional> e164Positions, - final Optional usernameHash, - final Optional> usernameHashPositions) { - final List monitorProofs = new ArrayList<>(List.of(MonitorProof.newBuilder().build())); - e164.ifPresent(ignored -> monitorProofs.add(MonitorProof.newBuilder().build())); - usernameHash.ifPresent(ignored -> monitorProofs.add(MonitorProof.newBuilder().build())); - - final MonitorResponse monitorResponse = MonitorResponse.newBuilder() - .setTreeHead(FullTreeHead.newBuilder().build()) - .addAllContactProofs(monitorProofs) - .build(); - + @Test + void monitorSuccess() { when(keyTransparencyServiceClient.monitor(any(), any(), any(), any())) - .thenReturn(CompletableFuture.completedFuture(monitorResponse)); + .thenReturn(CompletableFuture.completedFuture(TestRandomUtil.nextBytes(16))); final Invocation.Builder request = resources.getJerseyTest() .target("/v1/key-transparency/monitor") @@ -262,35 +241,20 @@ public class KeyTransparencyControllerTest { try (Response response = request.post(Entity.json( createMonitorRequestJson( ACI, List.of(3L), - usernameHash, usernameHashPositions, - e164, e164Positions, + Optional.empty(), Optional.empty(), + Optional.empty(), Optional.empty(), Optional.of(3L), Optional.of(4L))))) { assertEquals(200, response.getStatus()); final KeyTransparencyMonitorResponse keyTransparencyMonitorResponse = response.readEntity( KeyTransparencyMonitorResponse.class); - assertNotNull(keyTransparencyMonitorResponse.aciMonitorProof()); - - usernameHash.ifPresentOrElse( - ignored -> assertTrue(keyTransparencyMonitorResponse.usernameHashMonitorProof().isPresent()), - () -> assertTrue(keyTransparencyMonitorResponse.usernameHashMonitorProof().isEmpty())); - - e164.ifPresentOrElse(ignored -> assertTrue(keyTransparencyMonitorResponse.e164MonitorProof().isPresent()), - () -> assertTrue(keyTransparencyMonitorResponse.e164MonitorProof().isEmpty())); + assertNotNull(keyTransparencyMonitorResponse.monitorResponse()); verify(keyTransparencyServiceClient, times(1)).monitor( any(), eq(Optional.of(3L)), eq(Optional.of(4L)), eq(KeyTransparencyController.KEY_TRANSPARENCY_RPC_TIMEOUT)); } } - private static Stream monitorSuccess() { - return Stream.of( - Arguments.of(Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()), - Arguments.of(Optional.empty(), Optional.empty(), Optional.of(TestRandomUtil.nextBytes(20)), Optional.of(List.of(3L))), - Arguments.of(Optional.of(NUMBER), Optional.of(List.of(3L)), Optional.empty(), Optional.empty()) - ); - } - @Test void monitorAuthenticated() { final Invocation.Builder request = resources.getJerseyTest() @@ -395,10 +359,6 @@ public class KeyTransparencyControllerTest { } @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - /** - * Create an invalid monitor request by supplying an invalid combination of inputs. For example, providing - * a username hash but no corresponding list of positions. - */ private static String createMonitorRequestJson( final AciServiceIdentifier aci, final List aciPositions, diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/util/ProtobufAdapterTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/util/ProtobufAdapterTest.java deleted file mode 100644 index 5573c7023..000000000 --- a/service/src/test/java/org/whispersystems/textsecuregcm/util/ProtobufAdapterTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2024 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.util; - -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.google.protobuf.ByteString; -import katie.FullTreeHead; -import katie.TreeHead; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -public class ProtobufAdapterTest { - private record FullTreeHeadTestRecord(@JsonSerialize(using = FullTreeHeadProtobufAdapter.Serializer.class) - @JsonDeserialize(using = FullTreeHeadProtobufAdapter.Deserializer.class) - FullTreeHead fullTreeHead) { - } - - @Test - void serializeDeserialize() throws JsonProcessingException { - final TreeHead treeHead = TreeHead.newBuilder() - .setTreeSize(10) - .setTimestamp(12345) - .setSignature(ByteString.copyFrom(TestRandomUtil.nextBytes(16))) - .build(); - - final FullTreeHead fullTreeHead = FullTreeHead.newBuilder() - .setTreeHead(treeHead) - .addAllConsistency(List.of(ByteString.copyFrom(TestRandomUtil.nextBytes(20)))) - .build(); - - final FullTreeHeadTestRecord expectedTestRecord = new FullTreeHeadTestRecord(fullTreeHead); - - // Serialize to JSON - final String json = SystemMapper.jsonMapper().writeValueAsString(expectedTestRecord); - - // Deserialize back to record - assertEquals(expectedTestRecord, SystemMapper.jsonMapper().readValue(json, FullTreeHeadTestRecord.class)); - } - - @Test - void deserializeFailure() { - assertThrows(JsonParseException.class, - () -> SystemMapper.jsonMapper().readValue("this is not valid json", FullTreeHeadTestRecord.class)); - } -}