diff --git a/java/google/registry/rde/Ghostryde.java b/java/google/registry/rde/Ghostryde.java index 1dc4d9d86..2d0167f84 100644 --- a/java/google/registry/rde/Ghostryde.java +++ b/java/google/registry/rde/Ghostryde.java @@ -16,9 +16,10 @@ package google.registry.rde; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static google.registry.rde.RydeCompression.openCompressor; +import static google.registry.rde.RydeCompression.openDecompressor; import static java.nio.charset.StandardCharsets.US_ASCII; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.bouncycastle.bcpg.CompressionAlgorithmTags.ZLIB; import static org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags.AES_128; import static org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME; import static org.bouncycastle.openpgp.PGPLiteralData.BINARY; @@ -40,8 +41,6 @@ import java.util.stream.Collectors; import javax.annotation.CheckReturnValue; import javax.annotation.Nullable; import javax.annotation.WillNotClose; -import org.bouncycastle.openpgp.PGPCompressedData; -import org.bouncycastle.openpgp.PGPCompressedDataGenerator; import org.bouncycastle.openpgp.PGPEncryptedDataGenerator; import org.bouncycastle.openpgp.PGPEncryptedDataList; import org.bouncycastle.openpgp.PGPException; @@ -126,15 +125,6 @@ public final class Ghostryde { /** Size of the buffer used by the intermediate streams. */ static final int BUFFER_SIZE = 64 * 1024; - /** - * Compression algorithm to use when creating ghostryde files. - * - *

We're going to use ZLIB since it's better than ZIP. - * - * @see org.bouncycastle.bcpg.CompressionAlgorithmTags - */ - static final int COMPRESSION_ALGORITHM = ZLIB; - /** * Symmetric encryption cipher to use when creating ghostryde files. * @@ -322,26 +312,6 @@ public final class Ghostryde { return random; } - /** - * Opens a new compressor (Writing Step 2/3) - * - *

This is the second step in creating a ghostryde file. After this method, you'll want to call - * {@link #openPgpFileOutputStream}. - * - *

TODO(b/110465985): merge with the RyDE version. - * - * @param os is the value returned by {@link #openEncryptor(OutputStream, PGPPublicKey)}. - * @throws IOException - * @throws PGPException - */ - @CheckReturnValue - private static ImprovedOutputStream openCompressor(@WillNotClose OutputStream os) - throws IOException, PGPException { - PGPCompressedDataGenerator kompressor = new PGPCompressedDataGenerator(COMPRESSION_ALGORITHM); - return new ImprovedOutputStream( - "GhostrydeCompressor", kompressor.open(os, new byte[BUFFER_SIZE])); - } - /** * Opens an {@link OutputStream} to which the actual data should be written (Writing Step 3/3) * @@ -423,25 +393,6 @@ public final class Ghostryde { }; } - /** - * Opens a new decompressor (Reading Step 2/3) - * - *

This is the second step in reading a ghostryde file. After this method, you'll want to call - * {@link #openPgpFileInputStream}. - * - *

TODO(b/110465985): merge with the RyDE version. - * - * @param input is the value returned by {@link #openDecryptor}. - * @throws IOException - * @throws PGPException - */ - @CheckReturnValue - private static ImprovedInputStream openDecompressor(@WillNotClose InputStream input) - throws IOException, PGPException { - PGPCompressedData compressed = PgpUtils.readSinglePgpObject(input, PGPCompressedData.class); - return new ImprovedInputStream("GhostrydeDecompressor", compressed.getDataStream()); - } - /** * Opens a new decoder for reading the original contents (Reading Step 3/3) * diff --git a/java/google/registry/rde/RydeCompression.java b/java/google/registry/rde/RydeCompression.java new file mode 100644 index 000000000..3c6008bc1 --- /dev/null +++ b/java/google/registry/rde/RydeCompression.java @@ -0,0 +1,80 @@ +// Copyright 2018 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package google.registry.rde; + +import static org.bouncycastle.bcpg.CompressionAlgorithmTags.ZIP; + +import google.registry.util.ImprovedInputStream; +import google.registry.util.ImprovedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import javax.annotation.CheckReturnValue; +import javax.annotation.WillNotClose; +import org.bouncycastle.openpgp.PGPCompressedData; +import org.bouncycastle.openpgp.PGPCompressedDataGenerator; +import org.bouncycastle.openpgp.PGPException; + +/** + * OpenPGP compression service that wraps an {@link OutputStream}. + * + *

This uses the ZIP compression algorithm per the ICANN escrow specification. + */ +final class RydeCompression { + + private static final int BUFFER_SIZE = 64 * 1024; + + /** + * Compression algorithm to use when creating RyDE files. + * + *

We're going to use ZIP because that's what the spec mandates. + * + * @see org.bouncycastle.bcpg.CompressionAlgorithmTags + */ + private static final int COMPRESSION_ALGORITHM = ZIP; + + /** + * Creates an OutputStream that compresses the data. + * + *

TODO(b/110465964): document where the input comes from / output goes to. Something like + * documenting that os is the result of openEncryptor and the result goes into openFileEncoder. + */ + @CheckReturnValue + static ImprovedOutputStream openCompressor(@WillNotClose OutputStream os) { + try { + return new ImprovedOutputStream( + "RydeCompressor", + new PGPCompressedDataGenerator(COMPRESSION_ALGORITHM).open(os, new byte[BUFFER_SIZE])); + } catch (IOException | PGPException e) { + throw new RuntimeException(e); + } + } + + /** + * Creates an InputStream that decompresses the data. + * + *

TODO(b/110465964): document where the input comes from / output goes to. Something like + * documenting that input is the result of openDecryptor and the result goes into openFileDecoder. + */ + @CheckReturnValue + static ImprovedInputStream openDecompressor(@WillNotClose InputStream input) { + try { + PGPCompressedData compressed = PgpUtils.readSinglePgpObject(input, PGPCompressedData.class); + return new ImprovedInputStream("RydeDecompressor", compressed.getDataStream()); + } catch (PGPException e) { + throw new RuntimeException(e); + } + } +} diff --git a/java/google/registry/rde/RydeEncoder.java b/java/google/registry/rde/RydeEncoder.java index 11e48154e..d43c77b09 100644 --- a/java/google/registry/rde/RydeEncoder.java +++ b/java/google/registry/rde/RydeEncoder.java @@ -15,6 +15,7 @@ package google.registry.rde; import static com.google.common.base.Preconditions.checkNotNull; +import static google.registry.rde.RydeCompression.openCompressor; import com.google.common.collect.ImmutableList; import com.google.common.io.Closer; @@ -69,7 +70,7 @@ public final class RydeEncoder extends FilterOutputStream { this.sigOutput = sigOutput; signer = closer.register(new RydePgpSigningOutputStream(checkNotNull(rydeOutput), signingKey)); encryptLayer = closer.register(new RydePgpEncryptionOutputStream(signer, receiverKeys)); - kompressor = closer.register(new RydePgpCompressionOutputStream(encryptLayer)); + kompressor = closer.register(openCompressor(encryptLayer)); fileLayer = closer.register(new RydePgpFileOutputStream(kompressor, modified, filenamePrefix + ".tar")); tarLayer = diff --git a/java/google/registry/rde/RydePgpCompressionOutputStream.java b/java/google/registry/rde/RydePgpCompressionOutputStream.java deleted file mode 100644 index e0422b07d..000000000 --- a/java/google/registry/rde/RydePgpCompressionOutputStream.java +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2017 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package google.registry.rde; - -import static org.bouncycastle.bcpg.CompressionAlgorithmTags.ZIP; - -import google.registry.util.ImprovedOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import javax.annotation.WillNotClose; -import org.bouncycastle.openpgp.PGPCompressedDataGenerator; -import org.bouncycastle.openpgp.PGPException; - -/** - * OpenPGP compression service that wraps an {@link OutputStream}. - * - *

This uses the ZIP compression algorithm per the ICANN escrow specification. - */ -public class RydePgpCompressionOutputStream extends ImprovedOutputStream { - - private static final int BUFFER_SIZE = 64 * 1024; - - /** - * Creates a new instance that compresses data. - * - * @param os is the upstream {@link OutputStream} which is not closed by this object - * @throws RuntimeException to rethrow {@link PGPException} and {@link IOException} - */ - public RydePgpCompressionOutputStream( - @WillNotClose OutputStream os) { - super("RydePgpCompressionOutputStream", createDelegate(BUFFER_SIZE, os)); - } - - private static OutputStream createDelegate(int bufferSize, OutputStream os) { - try { - return new PGPCompressedDataGenerator(ZIP).open(os, new byte[bufferSize]); - } catch (IOException | PGPException e) { - throw new RuntimeException(e); - } - } -} diff --git a/javatests/google/registry/rde/RydeCompressionTest.java b/javatests/google/registry/rde/RydeCompressionTest.java new file mode 100644 index 000000000..d08891cb2 --- /dev/null +++ b/javatests/google/registry/rde/RydeCompressionTest.java @@ -0,0 +1,48 @@ +// Copyright 2018 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package google.registry.rde; + +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.io.ByteStreams; +import google.registry.testing.ShardableTestCase; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class RydeCompressionTest extends ShardableTestCase { + + @Test + public void testCompression_decompression() throws Exception { + byte[] expected = "Testing 1, 2, 3".getBytes(UTF_8); + + ByteArrayOutputStream output = new ByteArrayOutputStream(); + try (OutputStream compressor = RydeCompression.openCompressor(output)) { + compressor.write(expected); + } + byte[] compressed = output.toByteArray(); + + ByteArrayInputStream input = new ByteArrayInputStream(compressed); + try (InputStream decompressor = RydeCompression.openDecompressor(input)) { + assertThat(ByteStreams.toByteArray(decompressor)).isEqualTo(expected); + } + } +}