diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/TorExitNodeConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/TorExitNodeConfiguration.java index 75523b385..1314e45da 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/TorExitNodeConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/TorExitNodeConfiguration.java @@ -25,6 +25,9 @@ public class TorExitNodeConfiguration { @NotBlank private String objectKey; + @JsonProperty + private long maxSize = 16 * 1024 * 1024; + @JsonProperty private Duration refreshInterval = Duration.ofMinutes(5); @@ -45,6 +48,15 @@ public class TorExitNodeConfiguration { return objectKey; } + public long getMaxSize() { + return maxSize; + } + + @VisibleForTesting + public void setMaxSize(final long maxSize) { + this.maxSize = maxSize; + } + public Duration getRefreshInterval() { return refreshInterval; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/S3ObjectMonitor.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/S3ObjectMonitor.java index dcf1e77f9..1afb5f87a 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/util/S3ObjectMonitor.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/util/S3ObjectMonitor.java @@ -29,6 +29,7 @@ public class S3ObjectMonitor implements Managed { private final String s3Bucket; private final String objectKey; + private final long maxObjectSize; private final ScheduledExecutorService refreshExecutorService; private final Duration refreshInterval; @@ -46,6 +47,7 @@ public class S3ObjectMonitor implements Managed { final String s3Region, final String s3Bucket, final String objectKey, + final long maxObjectSize, final ScheduledExecutorService refreshExecutorService, final Duration refreshInterval, final Consumer changeListener) { @@ -56,6 +58,7 @@ public class S3ObjectMonitor implements Managed { .build(), s3Bucket, objectKey, + maxObjectSize, refreshExecutorService, refreshInterval, changeListener); @@ -66,6 +69,7 @@ public class S3ObjectMonitor implements Managed { final AmazonS3 s3Client, final String s3Bucket, final String objectKey, + final long maxObjectSize, final ScheduledExecutorService refreshExecutorService, final Duration refreshInterval, final Consumer changeListener) { @@ -73,6 +77,7 @@ public class S3ObjectMonitor implements Managed { this.s3Client = s3Client; this.s3Bucket = s3Bucket; this.objectKey = objectKey; + this.maxObjectSize = maxObjectSize; this.refreshExecutorService = refreshExecutorService; this.refreshInterval = refreshInterval; @@ -111,7 +116,17 @@ public class S3ObjectMonitor implements Managed { final String refreshedETag = objectMetadata.getETag(); if (!StringUtils.equals(initialETag, refreshedETag) && lastETag.compareAndSet(initialETag, refreshedETag)) { - changeListener.accept(s3Client.getObject(s3Bucket, objectKey)); + final S3Object s3Object = s3Client.getObject(s3Bucket, objectKey); + + log.info("Object at s3://{}/{} has changed; new eTag is {} and object size is {} bytes", + s3Bucket, objectKey, s3Object.getObjectMetadata().getETag(), s3Object.getObjectMetadata().getContentLength()); + + if (s3Object.getObjectMetadata().getContentLength() <= maxObjectSize) { + changeListener.accept(s3Object); + } else { + log.warn("Object at s3://{}/{} has a size of {} bytes, which exceeds the maximum allowed size of {} bytes", + s3Bucket, objectKey, s3Object.getObjectMetadata().getContentLength(), maxObjectSize); + } } } catch (final Exception e) { log.warn("Failed to refresh monitored object", e); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/TorExitNodeManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/TorExitNodeManager.java index e649164d6..d14d0dfbb 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/util/TorExitNodeManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/util/TorExitNodeManager.java @@ -49,6 +49,7 @@ public class TorExitNodeManager implements Managed { configuration.getS3Region(), configuration.getS3Bucket(), configuration.getObjectKey(), + configuration.getMaxSize(), scheduledExecutorService, configuration.getRefreshInterval(), this::handleExitListChanged); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/util/S3ObjectMonitorTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/util/S3ObjectMonitorTest.java index 60d642c4f..54e1cde00 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/util/S3ObjectMonitorTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/util/S3ObjectMonitorTest.java @@ -10,8 +10,9 @@ import java.util.UUID; import java.util.concurrent.ScheduledExecutorService; import java.util.function.Consumer; -import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -32,7 +33,9 @@ class S3ObjectMonitorTest { final S3ObjectMonitor objectMonitor = new S3ObjectMonitor( s3Client, bucket, - objectKey, mock(ScheduledExecutorService.class), + objectKey, + 16 * 1024 * 1024, + mock(ScheduledExecutorService.class), Duration.ofMinutes(1), listener); @@ -46,4 +49,37 @@ class S3ObjectMonitorTest { verify(listener).accept(s3Object); } + + @Test + void refreshOversizedObject() { + final AmazonS3 s3Client = mock(AmazonS3.class); + final ObjectMetadata metadata = mock(ObjectMetadata.class); + final S3Object s3Object = mock(S3Object.class); + + final String bucket = "s3bucket"; + final String objectKey = "greatest-smooth-jazz-hits-of-all-time.zip"; + final long maxObjectSize = 16 * 1024 * 1024; + + //noinspection unchecked + final Consumer listener = mock(Consumer.class); + + final S3ObjectMonitor objectMonitor = new S3ObjectMonitor( + s3Client, + bucket, + objectKey, + maxObjectSize, + mock(ScheduledExecutorService.class), + Duration.ofMinutes(1), + listener); + + when(metadata.getETag()).thenReturn(UUID.randomUUID().toString()); + when(metadata.getContentLength()).thenReturn(maxObjectSize + 1); + when(s3Object.getObjectMetadata()).thenReturn(metadata); + when(s3Client.getObjectMetadata(bucket, objectKey)).thenReturn(metadata); + when(s3Client.getObject(bucket, objectKey)).thenReturn(s3Object); + + objectMonitor.refresh(); + + verify(listener, never()).accept(any()); + } }