From b8c07ec79ba284fffd0c8b09836299ff767f692e Mon Sep 17 00:00:00 2001 From: ghidra1 Date: Sun, 24 May 2026 23:25:24 -0400 Subject: [PATCH] GP-6862 Improvements on Defaltor/Inflator stream use and other cleanup. DmgFileReader improvements and removal of its GPL ZLIB implementation. --- .../dmg/ghidra/GFileUtilityMethods.java | 22 ++- .../dmg/reader/DmgFileReader.java | 128 +++++++++++++----- .../mobiledevices/dmg/server/DmgServer.java | 2 + .../dmg/java/mobiledevices/dmg/zlib/ZLIB.java | 120 ---------------- .../java/ghidra/file/formats/zlib/ZLIB.java | 110 ++++++--------- .../ghidra/file/formats/zlib/ZLIBTest.java | 53 ++++++++ .../stream/RemoteDeflaterOutputStream.java | 53 ++++++++ .../stream/RemoteInputBlockStreamHandle.java | 56 +++----- .../stream/RemoteOutputBlockStreamHandle.java | 68 ++++------ .../src/main/java/db/buffers/DataBuffer.java | 15 +- .../java/ghidra/pcode/utils/SlaFormat.java | 18 ++- 11 files changed, 318 insertions(+), 327 deletions(-) delete mode 100644 GPL/DMG/src/dmg/java/mobiledevices/dmg/zlib/ZLIB.java create mode 100644 Ghidra/Features/FileFormats/src/test/java/ghidra/file/formats/zlib/ZLIBTest.java create mode 100644 Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteDeflaterOutputStream.java diff --git a/GPL/DMG/src/dmg/java/mobiledevices/dmg/ghidra/GFileUtilityMethods.java b/GPL/DMG/src/dmg/java/mobiledevices/dmg/ghidra/GFileUtilityMethods.java index a923072401..769dfe4239 100644 --- a/GPL/DMG/src/dmg/java/mobiledevices/dmg/ghidra/GFileUtilityMethods.java +++ b/GPL/DMG/src/dmg/java/mobiledevices/dmg/ghidra/GFileUtilityMethods.java @@ -18,12 +18,13 @@ public final class GFileUtilityMethods { public final static File writeTemporaryFile( InputStream inputStream, int maxBytesToWrite ) throws IOException { File tempOutputFile = File.createTempFile( GHIDRA_FILE_SYSTEM_PREFIX, GHIDRA_FILE_SYSTEM_SUFFIX ); tempOutputFile.deleteOnExit(); - OutputStream outputStream = new FileOutputStream( tempOutputFile ); - try { + + try (OutputStream outputStream = new FileOutputStream(tempOutputFile)) { int nWritten = 0; - byte [] buffer = new byte[ 8192 ]; + byte[] buffer = new byte[8192]; 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 ) { break; } @@ -34,13 +35,10 @@ public final class GFileUtilityMethods { } } } - finally { - outputStream.close(); - } 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 ) { prefix = GHIDRA_FILE_SYSTEM_PREFIX; } @@ -51,13 +49,11 @@ public final class GFileUtilityMethods { } File tempFile = File.createTempFile( prefix , GHIDRA_FILE_SYSTEM_SUFFIX ); tempFile.deleteOnExit(); - OutputStream tempFileOut = new FileOutputStream( tempFile ); - try { + + try (OutputStream tempFileOut = new FileOutputStream(tempFile)) { tempFileOut.write( bytes ); } - finally { - tempFileOut.close(); - } + return tempFile; } } diff --git a/GPL/DMG/src/dmg/java/mobiledevices/dmg/reader/DmgFileReader.java b/GPL/DMG/src/dmg/java/mobiledevices/dmg/reader/DmgFileReader.java index 436d7ab68c..25913ac6ca 100644 --- a/GPL/DMG/src/dmg/java/mobiledevices/dmg/reader/DmgFileReader.java +++ b/GPL/DMG/src/dmg/java/mobiledevices/dmg/reader/DmgFileReader.java @@ -6,6 +6,7 @@ package mobiledevices.dmg.reader; import java.io.*; import java.util.ArrayList; import java.util.List; +import java.util.zip.InflaterInputStream; import org.catacombae.dmgextractor.encodings.encrypted.ReadableCEncryptedEncodingStream; import org.catacombae.hfsexplorer.FileSystemRecognizer; @@ -27,7 +28,6 @@ import mobiledevices.dmg.decmpfs.DecmpfsCompressionTypes; import mobiledevices.dmg.decmpfs.DecmpfsHeader; import mobiledevices.dmg.ghidra.*; import mobiledevices.dmg.hfsplus.AttributesFileParser; -import mobiledevices.dmg.zlib.ZLIB; public class DmgFileReader implements Closeable { private final static GDataConverter ledc = new GDataConverterLE(); @@ -191,24 +191,16 @@ public class DmgFileReader implements Closeable { if ( decmpfsHeader.getCompressionType() == DecmpfsCompressionTypes.CMP_Type3 ) { 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(); - - 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); + return new RestrictedInflaterInputStream(decmpfsHeader.getAttrBytes(), + (int) decmpfsHeader.getUncompressedSize()); } else if ( decmpfsHeader.getCompressionType() == DecmpfsCompressionTypes.CMP_Type4 ) { - - return decompressResourceFork( entry, resourceForkStream, (int)decmpfsHeader.getUncompressedSize() ); + return decompressResourceFork(entry, resourceForkStream, + (int) decmpfsHeader.getUncompressedSize()); } } } @@ -223,34 +215,102 @@ public class DmgFileReader implements Closeable { System.err.println( "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 ) { - input.read(); + for (int i = 0; i < 0x100; ++i) { + 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 ]; - input.read( sizeBytes ); - int size = sizeBytes[ 0 ] == 0 ? - bedc.getInt( sizeBytes ) : - ledc.getInt( sizeBytes ); + return new RestrictedInflaterInputStream(tempCompressedFile, expectedLength); + } - byte [] flagsBytes = new byte[ 4 ]; - input.read( flagsBytes ); + private class RestrictedInflaterInputStream extends InflaterInputStream { - byte [] startDistanceBytes = new byte[ 4 ]; - input.read( startDistanceBytes ); - int startDistance = ledc.getInt( startDistanceBytes ); + private File tempCompressedFile; + private int readLimit; + 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( ); - ByteArrayOutputStream uncompressedByteStream = zlib.decompress( inputStream, expectedLength ); + @Override + 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 getInfo( String path ) { diff --git a/GPL/DMG/src/dmg/java/mobiledevices/dmg/server/DmgServer.java b/GPL/DMG/src/dmg/java/mobiledevices/dmg/server/DmgServer.java index 77b62a7103..3e64e4deae 100644 --- a/GPL/DMG/src/dmg/java/mobiledevices/dmg/server/DmgServer.java +++ b/GPL/DMG/src/dmg/java/mobiledevices/dmg/server/DmgServer.java @@ -128,6 +128,8 @@ public class DmgServer { sendResponse(temporaryFile.getAbsolutePath()); + // TODO: When can we remove temporaryFile + if (expectedFileLength != temporaryFile.length()) { log("file sizes do not match!"); } diff --git a/GPL/DMG/src/dmg/java/mobiledevices/dmg/zlib/ZLIB.java b/GPL/DMG/src/dmg/java/mobiledevices/dmg/zlib/ZLIB.java deleted file mode 100644 index a993d069b4..0000000000 --- a/GPL/DMG/src/dmg/java/mobiledevices/dmg/zlib/ZLIB.java +++ /dev/null @@ -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(); - } -} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zlib/ZLIB.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zlib/ZLIB.java index ff7b6d04b0..e171e70b66 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zlib/ZLIB.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zlib/ZLIB.java @@ -15,12 +15,13 @@ */ package ghidra.file.formats.zlib; -import ghidra.app.util.bin.ByteProvider; - import java.io.*; import java.util.Arrays; import java.util.zip.*; +import ghidra.app.util.bin.ByteProvider; +import ghidra.util.Msg; + /** * * 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. * This is required by the ZLIB native library in order to support certain optimizations. * @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 * @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 ); @@ -70,37 +73,25 @@ public class ZLIB { 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() ); - } - + Inflater inflater = new Inflater(noWrap); + try { + inflater.setInput(compressedBytes); + while (!inflater.finished()) { + int nDecompressed = inflater.inflate(tempDecompressedBytes); + decompressedBOS.write(tempDecompressedBytes, 0, nDecompressed); + if (decompressedBOS.size() > decompressedSizeLimit) { + Msg.warn(this, "ZLIB decompress exceeded specified limit (" + + decompressedBOS.size() + " > " + decompressedSizeLimit + ")"); + break; + } + } + } + catch (DataFormatException e) { + throw new IOException(e.getMessage()); + } + finally { + inflater.end(); + } return decompressedBOS; } @@ -129,9 +120,8 @@ public class ZLIB { * to support the compression format used in both GZIP and PKZIP. * @param decompressedBytes the decompressed bytes * @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 ); } @@ -142,42 +132,24 @@ public class ZLIB { * @param noWrap if true then use GZIP compatible compression * @param decompressedBytes the decompressed bytes * @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(); byte [] tempBuffer = new byte[ 0x10000 ]; - int offset = 0; - - while ( offset < decompressedBytes.length ) { - - Deflater deflater = new Deflater( 0, noWrap ); - - try { - deflater.setInput( decompressedBytes, offset, decompressedBytes.length - offset ); - - deflater.finish(); - - 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(); - } - } - + Deflater deflater = new Deflater(0, noWrap); + try { + deflater.setInput(decompressedBytes); + deflater.finish(); + while (!deflater.finished()) { + int nDeflated = deflater.deflate(tempBuffer); + compressedBOS.write(tempBuffer, 0, nDeflated); + } + } + finally { + deflater.end(); + } return compressedBOS; } diff --git a/Ghidra/Features/FileFormats/src/test/java/ghidra/file/formats/zlib/ZLIBTest.java b/Ghidra/Features/FileFormats/src/test/java/ghidra/file/formats/zlib/ZLIBTest.java new file mode 100644 index 0000000000..af35a26225 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/test/java/ghidra/file/formats/zlib/ZLIBTest.java @@ -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)); + } + } +} diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteDeflaterOutputStream.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteDeflaterOutputStream.java new file mode 100644 index 0000000000..366473da2a --- /dev/null +++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteDeflaterOutputStream.java @@ -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 + } + } + } +} diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteInputBlockStreamHandle.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteInputBlockStreamHandle.java index c8e532428c..bee6a3ebe8 100644 --- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteInputBlockStreamHandle.java +++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteInputBlockStreamHandle.java @@ -18,7 +18,8 @@ package ghidra.server.stream; import java.io.*; import java.net.Socket; import java.net.SocketException; -import java.util.zip.*; +import java.util.zip.Deflater; +import java.util.zip.InflaterInputStream; import db.buffers.*; @@ -56,18 +57,16 @@ public class RemoteInputBlockStreamHandle extends RemoteBlockStreamHandle 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; + 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"); } - - 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 + 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"); } } } diff --git a/Ghidra/Framework/DB/src/main/java/db/buffers/DataBuffer.java b/Ghidra/Framework/DB/src/main/java/db/buffers/DataBuffer.java index a12b825af6..1c24959c40 100644 --- a/Ghidra/Framework/DB/src/main/java/db/buffers/DataBuffer.java +++ b/Ghidra/Framework/DB/src/main/java/db/buffers/DataBuffer.java @@ -330,17 +330,19 @@ public class DataBuffer implements Buffer, Externalizable { */ 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 { - deflate.setStrategy(Deflater.HUFFMAN_ONLY); deflate.setInput(data, 0, data.length); deflate.finish(); - + int compressedDataOffset = 0; while (!deflate.finished() && compressedDataOffset < compressedData.length) { compressedDataOffset += deflate.deflate(compressedData, compressedDataOffset, - compressedData.length - compressedDataOffset, Deflater.SYNC_FLUSH); + compressedData.length - compressedDataOffset); } if (!deflate.finished()) { @@ -416,10 +418,11 @@ public class DataBuffer implements Buffer, Externalizable { */ 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.setInput(compressedData, 0, compressedData.length); - try { + inflater.setInput(compressedData); int off = 0; while (!inflater.finished() && off < data.length) { off += inflater.inflate(data, off, data.length - off); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/utils/SlaFormat.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/utils/SlaFormat.java index 742341daa6..206695db41 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/utils/SlaFormat.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/utils/SlaFormat.java @@ -4,9 +4,9 @@ * 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. @@ -248,21 +248,19 @@ public class SlaFormat { * @throws IOException if the header is invalid or there are problems reading the file */ public static PackedDecode buildDecoder(ResourceFile sleighFile) throws IOException { - InputStream stream = sleighFile.getInputStream(); - try { + try (InputStream stream = sleighFile.getInputStream()) { if (!isSlaFormat(stream)) { throw new IOException("Missing SLA format header"); } - InflaterInputStream inflaterStream = new InflaterInputStream(stream); PackedDecode decoder = new PackedDecode(); decoder.open(MAX_FILE_SIZE, ".sla file loader"); - decoder.ingestStream(inflaterStream); + + try (InflaterInputStream inflaterStream = new InflaterInputStream(stream)) { + decoder.ingestStream(inflaterStream); + } + decoder.endIngest(); - inflaterStream.close(); return decoder; } - finally { - stream.close(); - } } }