GP-6862 Improvements on Defaltor/Inflator stream use and other cleanup.

DmgFileReader improvements and removal of its GPL ZLIB
implementation.
This commit is contained in:
ghidra1
2026-05-24 23:25:24 -04:00
parent b205bc1428
commit b8c07ec79b
11 changed files with 318 additions and 327 deletions

View File

@@ -18,12 +18,13 @@ public final class GFileUtilityMethods {
public final static File writeTemporaryFile( InputStream inputStream, int maxBytesToWrite ) throws IOException { public final static File writeTemporaryFile( InputStream inputStream, int maxBytesToWrite ) throws IOException {
File tempOutputFile = File.createTempFile( GHIDRA_FILE_SYSTEM_PREFIX, GHIDRA_FILE_SYSTEM_SUFFIX ); File tempOutputFile = File.createTempFile( GHIDRA_FILE_SYSTEM_PREFIX, GHIDRA_FILE_SYSTEM_SUFFIX );
tempOutputFile.deleteOnExit(); tempOutputFile.deleteOnExit();
OutputStream outputStream = new FileOutputStream( tempOutputFile );
try { try (OutputStream outputStream = new FileOutputStream(tempOutputFile)) {
int nWritten = 0; int nWritten = 0;
byte [] buffer = new byte[ 8192 ]; byte[] buffer = new byte[8192];
while ( true ) { while ( true ) {
int nRead = inputStream.read( buffer ); int limit = Math.min(maxBytesToWrite - nWritten, buffer.length);
int nRead = inputStream.read(buffer, 0, limit);
if ( nRead == -1 ) { if ( nRead == -1 ) {
break; break;
} }
@@ -34,13 +35,10 @@ public final class GFileUtilityMethods {
} }
} }
} }
finally {
outputStream.close();
}
return tempOutputFile; return tempOutputFile;
} }
public final static File writeTemporaryFile( byte [] bytes, String prefix ) throws IOException { public final static File writeTemporaryFile(byte[] bytes, String prefix) throws IOException {
if ( prefix == null ) { if ( prefix == null ) {
prefix = GHIDRA_FILE_SYSTEM_PREFIX; prefix = GHIDRA_FILE_SYSTEM_PREFIX;
} }
@@ -51,13 +49,11 @@ public final class GFileUtilityMethods {
} }
File tempFile = File.createTempFile( prefix , GHIDRA_FILE_SYSTEM_SUFFIX ); File tempFile = File.createTempFile( prefix , GHIDRA_FILE_SYSTEM_SUFFIX );
tempFile.deleteOnExit(); tempFile.deleteOnExit();
OutputStream tempFileOut = new FileOutputStream( tempFile );
try { try (OutputStream tempFileOut = new FileOutputStream(tempFile)) {
tempFileOut.write( bytes ); tempFileOut.write( bytes );
} }
finally {
tempFileOut.close();
}
return tempFile; return tempFile;
} }
} }

View File

@@ -6,6 +6,7 @@ package mobiledevices.dmg.reader;
import java.io.*; import java.io.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.zip.InflaterInputStream;
import org.catacombae.dmgextractor.encodings.encrypted.ReadableCEncryptedEncodingStream; import org.catacombae.dmgextractor.encodings.encrypted.ReadableCEncryptedEncodingStream;
import org.catacombae.hfsexplorer.FileSystemRecognizer; import org.catacombae.hfsexplorer.FileSystemRecognizer;
@@ -27,7 +28,6 @@ import mobiledevices.dmg.decmpfs.DecmpfsCompressionTypes;
import mobiledevices.dmg.decmpfs.DecmpfsHeader; import mobiledevices.dmg.decmpfs.DecmpfsHeader;
import mobiledevices.dmg.ghidra.*; import mobiledevices.dmg.ghidra.*;
import mobiledevices.dmg.hfsplus.AttributesFileParser; import mobiledevices.dmg.hfsplus.AttributesFileParser;
import mobiledevices.dmg.zlib.ZLIB;
public class DmgFileReader implements Closeable { public class DmgFileReader implements Closeable {
private final static GDataConverter ledc = new GDataConverterLE(); private final static GDataConverter ledc = new GDataConverterLE();
@@ -191,24 +191,16 @@ public class DmgFileReader implements Closeable {
if ( decmpfsHeader.getCompressionType() == DecmpfsCompressionTypes.CMP_Type3 ) { if ( decmpfsHeader.getCompressionType() == DecmpfsCompressionTypes.CMP_Type3 ) {
if ( decmpfsHeader.getAttrBytes()[ 0 ] == -1 ) { if ( decmpfsHeader.getAttrBytes()[ 0 ] == -1 ) {
return new ByteArrayInputStream( decmpfsHeader.getAttrBytes(), 1, decmpfsHeader.getAttrBytes().length - 1 ); return new ByteArrayInputStream(decmpfsHeader.getAttrBytes(), 1,
decmpfsHeader.getAttrBytes().length - 1);
} }
ZLIB zlib = new ZLIB(); return new RestrictedInflaterInputStream(decmpfsHeader.getAttrBytes(),
(int) decmpfsHeader.getUncompressedSize());
InputStream inputStream =
new ByteArrayInputStream(decmpfsHeader.getAttrBytes());
ByteArrayOutputStream uncompressedBytes =
zlib.decompress(inputStream, (int) decmpfsHeader.getUncompressedSize());
File tempDecompressedFile = GFileUtilityMethods.writeTemporaryFile(
uncompressedBytes.toByteArray(), entry.getName());
return new FileInputStream(tempDecompressedFile);
} }
else if ( decmpfsHeader.getCompressionType() == DecmpfsCompressionTypes.CMP_Type4 ) { else if ( decmpfsHeader.getCompressionType() == DecmpfsCompressionTypes.CMP_Type4 ) {
return decompressResourceFork(entry, resourceForkStream,
return decompressResourceFork( entry, resourceForkStream, (int)decmpfsHeader.getUncompressedSize() ); (int) decmpfsHeader.getUncompressedSize());
} }
} }
} }
@@ -223,34 +215,102 @@ public class DmgFileReader implements Closeable {
System.err.println( System.err.println(
"dmg resource fork for " + entry.getName() + ": " + tempFile.getAbsolutePath()); "dmg resource fork for " + entry.getName() + ": " + tempFile.getAbsolutePath());
InputStream input = new FileInputStream( tempFile ); // Copy compressed portion of tempFile to tempCompressedFile
File tempCompressedFile;
try (InputStream input = new FileInputStream(tempFile)) {
for ( int i = 0 ; i < 0x100 ; ++i ) { for (int i = 0; i < 0x100; ++i) {
input.read(); input.read();
}
byte[] sizeBytes = new byte[4];
input.read(sizeBytes);
int size = sizeBytes[0] == 0 ? bedc.getInt(sizeBytes) : ledc.getInt(sizeBytes);
byte[] flagsBytes = new byte[4];
input.read(flagsBytes);
byte[] startDistanceBytes = new byte[4];
input.read(startDistanceBytes);
int startDistance = ledc.getInt(startDistanceBytes);
input.skip(startDistance - 8);//skip to the start of the zlib compressed file
tempCompressedFile =
GFileUtilityMethods.writeTemporaryFile(input, size - startDistance);
}
finally {
tempFile.delete();
} }
byte [] sizeBytes = new byte[ 4 ]; return new RestrictedInflaterInputStream(tempCompressedFile, expectedLength);
input.read( sizeBytes ); }
int size = sizeBytes[ 0 ] == 0 ?
bedc.getInt( sizeBytes ) :
ledc.getInt( sizeBytes );
byte [] flagsBytes = new byte[ 4 ]; private class RestrictedInflaterInputStream extends InflaterInputStream {
input.read( flagsBytes );
byte [] startDistanceBytes = new byte[ 4 ]; private File tempCompressedFile;
input.read( startDistanceBytes ); private int readLimit;
int startDistance = ledc.getInt( startDistanceBytes ); private int readCount = 0;
input.skip( startDistance - 8 );//skip to the start of the zlib compressed file /**
* Creates a new Inflater input stream with a default decompressor and buffer size.
* NOTE: The default Inflater instance will be ended when this stream closes.
* @param tempCompressedFile the temporary file containing compressed data. File will be
* removed when this stream is closed.
* @param readLimit maximum data read count. Exceeding this limit will result in an
* IOException.
* @throws FileNotFoundException if tempCompressedFile does not exist
*/
RestrictedInflaterInputStream(File tempCompressedFile, int readLimit)
throws FileNotFoundException {
super(new FileInputStream(tempCompressedFile));
this.tempCompressedFile = tempCompressedFile;
this.readLimit = readLimit;
}
File tempCompressedFile = GFileUtilityMethods.writeTemporaryFile( input, size - startDistance ); /**
InputStream inputStream = new FileInputStream( tempCompressedFile ); * Creates a new Inflater input stream with a default decompressor and buffer size.
* NOTE: The default Inflater instance will be ended when this stream closes.
* @param compressedData the byte array containing compressed data.
* @param readLimit maximum data read count. Exceeding this limit will result in an
* IOException.
*/
RestrictedInflaterInputStream(byte[] compressedData, int readLimit) {
super(new ByteArrayInputStream(compressedData));
this.tempCompressedFile = null;
this.readLimit = readLimit;
}
ZLIB zlib = new ZLIB( ); @Override
ByteArrayOutputStream uncompressedByteStream = zlib.decompress( inputStream, expectedLength ); public void close() throws IOException {
try {
super.close();
}
finally {
// Cleanup temporary file input stream and remove file
in.close();
if (tempCompressedFile != null) {
tempCompressedFile.delete();
}
}
}
return new ByteArrayInputStream( uncompressedByteStream.toByteArray() ); @Override
public int read(byte[] b, int off, int length) throws IOException {
if (length == 0) {
return 0;
}
if (readCount >= readLimit) {
throw new IOException("Decompression limit exceeded: " + readLimit);
}
// Limit read length to avoid exceeding readLimit
int limit = Math.min(readLimit - readCount, length);
int count = super.read(b, off, limit);
if (count > 0) {
readCount += count;
}
return count;
}
} }
public List<String> getInfo( String path ) { public List<String> getInfo( String path ) {

View File

@@ -128,6 +128,8 @@ public class DmgServer {
sendResponse(temporaryFile.getAbsolutePath()); sendResponse(temporaryFile.getAbsolutePath());
// TODO: When can we remove temporaryFile
if (expectedFileLength != temporaryFile.length()) { if (expectedFileLength != temporaryFile.length()) {
log("file sizes do not match!"); log("file sizes do not match!");
} }

View File

@@ -1,120 +0,0 @@
/* ###
* IP: Public Domain
*/
package mobiledevices.dmg.zlib;
import java.io.*;
import java.util.zip.*;
/**
*
* TODO make this more memory efficient!!
*
*/
public class ZLIB {
public ZLIB() {
}
public ByteArrayOutputStream decompress( InputStream compressedIn, int expectedDecompressedLength ) throws IOException {
return decompress( compressedIn, expectedDecompressedLength, false );
}
public ByteArrayOutputStream decompress( InputStream compressedIn, int expectedDecompressedLength, boolean noWrap ) throws IOException {
byte [] compressedBytes = convertInputStreamToByteArray( compressedIn );
ByteArrayOutputStream decompressedBOS = new ByteArrayOutputStream();
byte [] tempDecompressedBytes = new byte[ 0x10000 ];
int totalDecompressed = 0;
int offset = 0;
try {
while ( offset < compressedBytes.length && totalDecompressed < expectedDecompressedLength ) {
if ( !noWrap && compressedBytes [ offset ] != 0x78 ) {
break;
}
Inflater inflater = new Inflater( noWrap );
inflater.setInput( compressedBytes, offset, compressedBytes.length - offset );
int nDecompressed = inflater.inflate( tempDecompressedBytes );
if ( nDecompressed == 0 ) {
break;
}
totalDecompressed += nDecompressed;
decompressedBOS.write( tempDecompressedBytes, 0, nDecompressed );
offset += inflater.getTotalIn();//increment total compressed bytes consumed
}
}
catch ( DataFormatException e ) {
throw new IOException( e.getMessage() );
}
return decompressedBOS;
}
public ByteArrayOutputStream compress(byte[] decompressedBytes) {
return compress( false, decompressedBytes );
}
public ByteArrayOutputStream compress(boolean noWrap, byte[] decompressedBytes) {
ByteArrayOutputStream compressedBOS = new ByteArrayOutputStream();
byte [] tempBuffer = new byte[ 0x10000 ];
int offset = 0;
while ( offset < decompressedBytes.length ) {
Deflater deflater = new Deflater( 0, noWrap );
deflater.setInput( decompressedBytes, offset, decompressedBytes.length - offset );
deflater.finish();
if ( deflater.needsInput() ) {
System.err.println( "needs input??" );
}
int nDeflated = deflater.deflate( tempBuffer );
if ( nDeflated == 0 ) {
break;
}
compressedBOS.write( tempBuffer, 0, nDeflated );
offset += deflater.getTotalIn();
}
return compressedBOS;
}
/**
* Converts the contents of an input stream to a byte array
* @param compressedIn
* @return
* @throws IOException
*/
private byte [] convertInputStreamToByteArray( InputStream compressedIn ) throws IOException {
byte [] bytes = new byte[ 8096 ];
ByteArrayOutputStream compressedBOS = new ByteArrayOutputStream();
while ( true ) {
int nRead = compressedIn.read( bytes );
if ( nRead == -1 ) {
break;
}
compressedBOS.write( bytes, 0, nRead );
}
return compressedBOS.toByteArray();
}
}

View File

@@ -15,12 +15,13 @@
*/ */
package ghidra.file.formats.zlib; package ghidra.file.formats.zlib;
import ghidra.app.util.bin.ByteProvider;
import java.io.*; import java.io.*;
import java.util.Arrays; import java.util.Arrays;
import java.util.zip.*; import java.util.zip.*;
import ghidra.app.util.bin.ByteProvider;
import ghidra.util.Msg;
/** /**
* *
* TODO make this more memory efficient!! * TODO make this more memory efficient!!
@@ -57,12 +58,14 @@ public class ZLIB {
* Note: When using the 'noWrap' option it is also necessary to provide an extra "dummy" byte as input. * Note: When using the 'noWrap' option it is also necessary to provide an extra "dummy" byte as input.
* This is required by the ZLIB native library in order to support certain optimizations. * This is required by the ZLIB native library in order to support certain optimizations.
* @param compressedIn an input stream containing the compressed data * @param compressedIn an input stream containing the compressed data
* @param expectedDecompressedLength the expected length of the decompressed data * @param decompressedSizeLimit the maximum length of the decompressed data. Actual decompressed
* data within returned ByteArrayOutputStream may still exceed this limit.
* @param noWrap if true then support GZIP compatible compression * @param noWrap if true then support GZIP compatible compression
* @return an output stream containing the decompressed data * @return an output stream containing the decompressed data
* @throws IOException * @throws IOException if IO error occurs while reading compressedIn stream
*/ */
public ByteArrayOutputStream decompress( InputStream compressedIn, int expectedDecompressedLength, boolean noWrap ) throws IOException { public ByteArrayOutputStream decompress(InputStream compressedIn, int decompressedSizeLimit,
boolean noWrap) throws IOException {
byte [] compressedBytes = convertInputStreamToByteArray( compressedIn ); byte [] compressedBytes = convertInputStreamToByteArray( compressedIn );
@@ -70,37 +73,25 @@ public class ZLIB {
byte [] tempDecompressedBytes = new byte[ 0x10000 ]; byte [] tempDecompressedBytes = new byte[ 0x10000 ];
int totalDecompressed = 0; Inflater inflater = new Inflater(noWrap);
int offset = 0; try {
inflater.setInput(compressedBytes);
try { while (!inflater.finished()) {
while ( offset < compressedBytes.length && totalDecompressed < expectedDecompressedLength ) { int nDecompressed = inflater.inflate(tempDecompressedBytes);
decompressedBOS.write(tempDecompressedBytes, 0, nDecompressed);
if ( !noWrap && compressedBytes [ offset ] != 0x78 ) { if (decompressedBOS.size() > decompressedSizeLimit) {
break; Msg.warn(this, "ZLIB decompress exceeded specified limit (" +
} decompressedBOS.size() + " > " + decompressedSizeLimit + ")");
break;
Inflater inflater = new Inflater( noWrap ); }
}
inflater.setInput( compressedBytes, offset, compressedBytes.length - offset ); }
catch (DataFormatException e) {
int nDecompressed = inflater.inflate( tempDecompressedBytes ); throw new IOException(e.getMessage());
}
if ( nDecompressed == 0 ) { finally {
break; inflater.end();
} }
totalDecompressed += nDecompressed;
decompressedBOS.write( tempDecompressedBytes, 0, nDecompressed );
offset += inflater.getTotalIn();//increment total compressed bytes consumed
}
}
catch ( DataFormatException e ) {
throw new IOException( e.getMessage() );
}
return decompressedBOS; return decompressedBOS;
} }
@@ -129,9 +120,8 @@ public class ZLIB {
* to support the compression format used in both GZIP and PKZIP. * to support the compression format used in both GZIP and PKZIP.
* @param decompressedBytes the decompressed bytes * @param decompressedBytes the decompressed bytes
* @return an output stream containing the compressed data * @return an output stream containing the compressed data
* @throws IOException
*/ */
public ByteArrayOutputStream compress( byte [] decompressedBytes ) throws IOException { public ByteArrayOutputStream compress(byte[] decompressedBytes) {
return compress( false, decompressedBytes ); return compress( false, decompressedBytes );
} }
@@ -142,42 +132,24 @@ public class ZLIB {
* @param noWrap if true then use GZIP compatible compression * @param noWrap if true then use GZIP compatible compression
* @param decompressedBytes the decompressed bytes * @param decompressedBytes the decompressed bytes
* @return an output stream containing the compressed data * @return an output stream containing the compressed data
* @throws IOException
*/ */
public ByteArrayOutputStream compress( boolean noWrap, byte [] decompressedBytes ) throws IOException { public ByteArrayOutputStream compress(boolean noWrap, byte[] decompressedBytes) {
ByteArrayOutputStream compressedBOS = new ByteArrayOutputStream(); ByteArrayOutputStream compressedBOS = new ByteArrayOutputStream();
byte [] tempBuffer = new byte[ 0x10000 ]; byte [] tempBuffer = new byte[ 0x10000 ];
int offset = 0; Deflater deflater = new Deflater(0, noWrap);
try {
while ( offset < decompressedBytes.length ) { deflater.setInput(decompressedBytes);
deflater.finish();
Deflater deflater = new Deflater( 0, noWrap ); while (!deflater.finished()) {
int nDeflated = deflater.deflate(tempBuffer);
try { compressedBOS.write(tempBuffer, 0, nDeflated);
deflater.setInput( decompressedBytes, offset, decompressedBytes.length - offset ); }
}
deflater.finish(); finally {
deflater.end();
if ( deflater.needsInput() ) { }
System.out.println( "needs input??" );
}
int nDeflated = deflater.deflate( tempBuffer );
if ( nDeflated == 0 ) {
break;
}
compressedBOS.write( tempBuffer, 0, nDeflated );
offset += deflater.getTotalIn();
} finally {
deflater.end();
}
}
return compressedBOS; return compressedBOS;
} }

View File

@@ -0,0 +1,53 @@
/* ###
* IP: GHIDRA
*
* 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 ghidra.file.formats.zlib;
import static org.junit.Assert.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.security.SecureRandom;
import java.util.Arrays;
import org.junit.Test;
import generic.test.AbstractGTest;
public class ZLIBTest extends AbstractGTest {
@Test
public void testCompressDecompress() throws Exception {
ZLIB zlib = new ZLIB();
byte[] data = new byte[100000];
SecureRandom random = new SecureRandom();
random.nextBytes(data);
// TODO: ZLIB API should be revised to avoid senseless exposure of ByteArrayOutputStream
try (ByteArrayOutputStream compress = zlib.compress(data)) {
byte[] compressedBytes = compress.toByteArray();
new ByteArrayInputStream(compressedBytes);
ByteArrayOutputStream decompressOut =
zlib.decompress(new ByteArrayInputStream(compressedBytes), data.length);
byte[] decompressedBytes = decompressOut.toByteArray();
assertTrue("Zlib round-trip failed", Arrays.equals(data, decompressedBytes));
}
}
}

View File

@@ -0,0 +1,53 @@
/* ###
* IP: GHIDRA
*
* 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 ghidra.server.stream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
/**
* {@link RemoteDeflaterOutputStream} extends {@link DeflaterOutputStream} and
* allows a non-default {@link Deflater} to "end" properly when this output stream
* is closed.
*/
class RemoteDeflaterOutputStream extends DeflaterOutputStream {
private boolean closed = false;
/**
* Construct a deflater output stream using a specified compression level.
* @param out output stream providing the uncompressed data
* @param deflaterLevel {@link Deflater} compression level
*/
RemoteDeflaterOutputStream(OutputStream out, int deflaterLevel) {
super(out, new Deflater(deflaterLevel));
}
@Override
public void close() throws IOException {
if (!closed) {
try {
super.close();
}
finally {
closed = true;
def.end(); // Must end on close since we did not use default Deflater
}
}
}
}

View File

@@ -18,7 +18,8 @@ package ghidra.server.stream;
import java.io.*; import java.io.*;
import java.net.Socket; import java.net.Socket;
import java.net.SocketException; import java.net.SocketException;
import java.util.zip.*; import java.util.zip.Deflater;
import java.util.zip.InflaterInputStream;
import db.buffers.*; import db.buffers.*;
@@ -56,18 +57,16 @@ public class RemoteInputBlockStreamHandle extends RemoteBlockStreamHandle<InputB
private final Socket socket; private final Socket socket;
private final InputStream in; private final InputStream in;
private final Inflater inf;
private int blocksRemaining = getBlockCount(); private int blocksRemaining = getBlockCount();
ClientInputBlockStream(Socket socket) throws IOException { ClientInputBlockStream(Socket socket) throws IOException {
this.socket = socket; this.socket = socket;
if (compressed) { if (compressed) {
inf = new Inflater(); // Uses default Inflater with nowrap=false
in = new InflaterInputStream(socket.getInputStream(), inf); in = new InflaterInputStream(socket.getInputStream());
} else { } else {
in = socket.getInputStream(); in = socket.getInputStream();
inf = null;
} }
} }
@@ -75,9 +74,6 @@ public class RemoteInputBlockStreamHandle extends RemoteBlockStreamHandle<InputB
public void close() throws IOException { public void close() throws IOException {
in.close(); in.close();
socket.close(); socket.close();
if (inf != null) {
inf.end(); // get rid of any native memory rather than waiting for GC
}
} }
@Override @Override
@@ -142,7 +138,7 @@ public class RemoteInputBlockStreamHandle extends RemoteBlockStreamHandle<InputB
socket.setSendBufferSize(getPreferredBufferSize()); socket.setSendBufferSize(getPreferredBufferSize());
InputBlockStream inputBlockStream = (InputBlockStream) blockStream; InputBlockStream inputBlockStream = (InputBlockStream) blockStream;
try (OutputStream out = socket.getOutputStream()) { try (OutputStream out = getBlockInputStream(socket)) {
copyBlockData(inputBlockStream, out); copyBlockData(inputBlockStream, out);
@@ -159,36 +155,26 @@ public class RemoteInputBlockStreamHandle extends RemoteBlockStreamHandle<InputB
} }
} }
private OutputStream getBlockInputStream(Socket socket) throws IOException {
OutputStream out = socket.getOutputStream();
if (compressed) {
out = new RemoteDeflaterOutputStream(out, Deflater.BEST_SPEED);
}
return out;
}
private void copyBlockData(InputBlockStream inputBlockStream, OutputStream out) private void copyBlockData(InputBlockStream inputBlockStream, OutputStream out)
throws IOException { throws IOException {
Deflater def = null; int blocksRemaining = getBlockCount();
try { BufferFileBlock block;
if (compressed) { while ((block = inputBlockStream.readBlock()) != null) {
def = new Deflater(Deflater.BEST_SPEED); if (blocksRemaining == 0) {
out = new DeflaterOutputStream(out, def); throw new IOException("unexpected data in stream");
}
int blocksRemaining = getBlockCount();
BufferFileBlock block;
while ((block = inputBlockStream.readBlock()) != null) {
if (blocksRemaining == 0) {
throw new IOException("unexpected data in stream");
}
out.write(block.toBytes());
--blocksRemaining;
}
// done with compressed stream, force compressed data to flush
if (out instanceof DeflaterOutputStream) {
((DeflaterOutputStream) out).finish();
}
} finally {
if (def != null) {
def.end();
} }
out.write(block.toBytes());
--blocksRemaining;
} }
} }

View File

@@ -55,17 +55,14 @@ public class RemoteOutputBlockStreamHandle extends RemoteBlockStreamHandle<Outpu
private final Socket socket; private final Socket socket;
private final OutputStream out; private final OutputStream out;
private final Deflater def;
private int blocksRemaining = getBlockCount(); private int blocksRemaining = getBlockCount();
ClientOutputBlockStream(Socket socket) throws IOException { ClientOutputBlockStream(Socket socket) throws IOException {
this.socket = socket; this.socket = socket;
if (compressed) { if (compressed) {
def = new Deflater(Deflater.BEST_SPEED); out = new RemoteDeflaterOutputStream(socket.getOutputStream(), Deflater.BEST_SPEED);
out = new DeflaterOutputStream(socket.getOutputStream(), def);
} else { } else {
def = null;
out = socket.getOutputStream(); out = socket.getOutputStream();
} }
} }
@@ -84,9 +81,6 @@ public class RemoteOutputBlockStreamHandle extends RemoteBlockStreamHandle<Outpu
public void close() throws IOException { public void close() throws IOException {
out.close(); out.close();
socket.close(); socket.close();
if (def != null) {
def.end(); // get rid of any native memory rather than waiting for GC
}
} }
@Override @Override
@@ -130,7 +124,7 @@ public class RemoteOutputBlockStreamHandle extends RemoteBlockStreamHandle<Outpu
socket.setReceiveBufferSize(getPreferredBufferSize()); socket.setReceiveBufferSize(getPreferredBufferSize());
OutputBlockStream outputBlockStream = (OutputBlockStream) blockStream; OutputBlockStream outputBlockStream = (OutputBlockStream) blockStream;
try (InputStream in = socket.getInputStream()) { try (InputStream in = getBlockInputStream(socket)) {
copyBlockData(outputBlockStream, in); copyBlockData(outputBlockStream, in);
@@ -155,43 +149,37 @@ public class RemoteOutputBlockStreamHandle extends RemoteBlockStreamHandle<Outpu
} }
private InputStream getBlockInputStream(Socket socket) throws IOException {
InputStream in = socket.getInputStream();
if (compressed) {
in = new InflaterInputStream(in);
}
return in;
}
private void copyBlockData(OutputBlockStream outputBlockStream, InputStream in) private void copyBlockData(OutputBlockStream outputBlockStream, InputStream in)
throws IOException, EOFException { throws IOException, EOFException {
Inflater inf = null; int blocksRemaining = getBlockCount();
try {
if (compressed) { byte[] bytes = new byte[getBlockSize() + 4]; // include space for index
inf = new Inflater(); // not necessary, but doing it this way while (blocksRemaining > 0) {
// allows end to be called, which frees native memory int total = 0;
// before GC is called while (total < bytes.length) {
in = new InflaterInputStream(in, inf); int readlen = in.read(bytes, total, bytes.length - total);
} if (readlen < 0) {
throw new EOFException("unexpected end of stream");
int blocksRemaining = getBlockCount();
byte[] bytes = new byte[getBlockSize() + 4]; // include space for index
while (blocksRemaining > 0) {
int total = 0;
while (total < bytes.length) {
int readlen = in.read(bytes, total, bytes.length - total);
if (readlen < 0) {
throw new EOFException("unexpected end of stream");
}
total += readlen;
} }
total += readlen;
BufferFileBlock block = new BufferFileBlock(bytes);
outputBlockStream.writeBlock(block);
--blocksRemaining;
}
if (compressed && in.read() != -1) {
// failed to properly exhaust compressed stream
throw new IOException("expected end of compressed stream");
}
} finally {
if (inf != null) {
inf.end(); // get rid of any native memory rather than waiting for GC
} }
BufferFileBlock block = new BufferFileBlock(bytes);
outputBlockStream.writeBlock(block);
--blocksRemaining;
}
if (compressed && in.read() != -1) {
// failed to properly exhaust compressed stream
throw new IOException("expected end of compressed stream");
} }
} }
} }

View File

@@ -330,17 +330,19 @@ public class DataBuffer implements Buffer, Externalizable {
*/ */
private static int deflateData(byte[] data, byte[] compressedData) { private static int deflateData(byte[] data, byte[] compressedData) {
Deflater deflate = new Deflater(Deflater.BEST_COMPRESSION, true); // Deflater must be consistent with inflateData nowrap option and should
// not be changed since client/server must match.
// NOTE: compression mode may be adjusted to optimize performance
Deflater deflate = new Deflater(Deflater.BEST_SPEED, true);
try { try {
deflate.setStrategy(Deflater.HUFFMAN_ONLY);
deflate.setInput(data, 0, data.length); deflate.setInput(data, 0, data.length);
deflate.finish(); deflate.finish();
int compressedDataOffset = 0; int compressedDataOffset = 0;
while (!deflate.finished() && compressedDataOffset < compressedData.length) { while (!deflate.finished() && compressedDataOffset < compressedData.length) {
compressedDataOffset += deflate.deflate(compressedData, compressedDataOffset, compressedDataOffset += deflate.deflate(compressedData, compressedDataOffset,
compressedData.length - compressedDataOffset, Deflater.SYNC_FLUSH); compressedData.length - compressedDataOffset);
} }
if (!deflate.finished()) { if (!deflate.finished()) {
@@ -416,10 +418,11 @@ public class DataBuffer implements Buffer, Externalizable {
*/ */
private static void inflateData(byte[] compressedData, byte[] data) throws IOException { private static void inflateData(byte[] compressedData, byte[] data) throws IOException {
// Inflater must be consistent with deflateData nowrap option and should
// not be changed since client/server must match.
Inflater inflater = new Inflater(true); Inflater inflater = new Inflater(true);
inflater.setInput(compressedData, 0, compressedData.length);
try { try {
inflater.setInput(compressedData);
int off = 0; int off = 0;
while (!inflater.finished() && off < data.length) { while (!inflater.finished() && off < data.length) {
off += inflater.inflate(data, off, data.length - off); off += inflater.inflate(data, off, data.length - off);

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -248,21 +248,19 @@ public class SlaFormat {
* @throws IOException if the header is invalid or there are problems reading the file * @throws IOException if the header is invalid or there are problems reading the file
*/ */
public static PackedDecode buildDecoder(ResourceFile sleighFile) throws IOException { public static PackedDecode buildDecoder(ResourceFile sleighFile) throws IOException {
InputStream stream = sleighFile.getInputStream(); try (InputStream stream = sleighFile.getInputStream()) {
try {
if (!isSlaFormat(stream)) { if (!isSlaFormat(stream)) {
throw new IOException("Missing SLA format header"); throw new IOException("Missing SLA format header");
} }
InflaterInputStream inflaterStream = new InflaterInputStream(stream);
PackedDecode decoder = new PackedDecode(); PackedDecode decoder = new PackedDecode();
decoder.open(MAX_FILE_SIZE, ".sla file loader"); decoder.open(MAX_FILE_SIZE, ".sla file loader");
decoder.ingestStream(inflaterStream);
try (InflaterInputStream inflaterStream = new InflaterInputStream(stream)) {
decoder.ingestStream(inflaterStream);
}
decoder.endIngest(); decoder.endIngest();
inflaterStream.close();
return decoder; return decoder;
} }
finally {
stream.close();
}
} }
} }