JDK-4529325 : Inflater/DeflaterInputStream failure when compressed data size near buffer size
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util
  • Affected Version: 1.4.0
  • Priority: P3
  • Status: Closed
  • Resolution: Not an Issue
  • OS: windows_nt
  • CPU: x86
  • Submitted: 2001-11-19
  • Updated: 2001-11-27
  • Resolved: 2001-11-27
Description

Name: nt126004			Date: 11/19/2001


java version "1.4.0-beta3"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0-beta3-b84)
Java HotSpot(TM) Client VM (build 1.4.0-beta3-b84, mixed mode)

We have run into problems trying to implement compression across sockets
between a Java client and a C++ server. To isolate the problems we have created
a simple Java client and server. The client reads from standard input a sends
data to the server line by line. The server merely echoes the client's input.

Client.java:
================================================================================
import java.io.*;
import java.net.Socket;
import java.util.zip.*;

public class Client {
	public static void main(String[] args) {
		boolean nowrap = true;
		if (args.length == 1 && args[0].equals("-h"))
			nowrap = false;
		try {
			Client client = new Client(nowrap);
			client.test();
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}

	private Socket socket;
	private Inflater inflater;
	private InflaterInputStream iIn;
	private DataInputStream in;
	private Deflater deflater;
	private DeflaterOutputStream dOut;
	private DataOutputStream out;

	public Client(boolean nowrap) throws IOException {
		this.socket = new Socket("127.0.0.1", 3000);
		this.inflater = new Inflater(nowrap);
		this.iIn = new InflaterInputStream(socket.getInputStream(),
inflater);
		this.in = new DataInputStream(iIn);
		this.deflater =  new Deflater(Deflater.DEFAULT_COMPRESSION,
nowrap);
		this.dOut = new DeflaterOutputStream(new BufferedOutputStream
(socket.getOutputStream()), deflater);
		this.out = new DataOutputStream(dOut);
	}

	public void test() throws IOException {
		BufferedReader reader = new BufferedReader(new InputStreamReader
(System.in));
		String line;
		while ((line = reader.readLine()) != null) {
			System.out.print("Writing... ");
			out.writeUTF(line);
			dOut.finish();
			out.flush();
			deflater.reset();
			System.out.println(line);
			System.out.print("Reading... ");
			String result = in.readUTF();
			inflater.reset();
			System.out.println(result);
		}
		socket.close();
		System.out.println("Disconnected.");
	}
}
================================================================================

Server.java
================================================================================
import java.io.*;
import java.net.*;
import java.util.zip.*;

public class Server {
	public static void main(String[] args) {
		boolean nowrap = true;
		if (args.length == 1 && args[0].equals("-h"))
			nowrap = false;
		try {
			Server server = new Server(nowrap);
			server.test();
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}

	private ServerSocket serverSocket;
	private Socket socket;
	private Inflater inflater;
	private InflaterInputStream iIn;
	private DataInputStream in;
	private Deflater deflater;
	private DeflaterOutputStream dOut;
	private DataOutputStream out;

	public Server(boolean nowrap) throws IOException {
		System.out.print("Waiting for connection... ");
		this.serverSocket = new ServerSocket(3000);
		this.socket = serverSocket.accept();
		System.out.println("Done.");
		this.inflater = new Inflater(nowrap);
		this.iIn = new InflaterInputStream(socket.getInputStream(),
inflater);
		this.in = new DataInputStream(iIn);
		this.deflater =  new Deflater(Deflater.DEFAULT_COMPRESSION,
nowrap);
		this.dOut = new DeflaterOutputStream(new BufferedOutputStream
(socket.getOutputStream()), deflater);
		this.out = new DataOutputStream(dOut);
	}

	public void test() throws IOException {
		while (true) {
			try {
				System.out.print("Reading... ");
				String result = in.readUTF();
				inflater.reset();
				System.out.println(result);
				System.out.print("Writing... ");
				out.writeUTF(result);
				dOut.finish();
				out.flush();
				deflater.reset();
				System.out.println(result);
			} catch (EOFException ex) {
				break;
			}
		}
		socket.close();
		System.out.println("Disconnected.");
	}
}
================================================================================

The client and server work fine for most input, however certain input can cause
either the client or the server to fail consistently with an exception similar
to the following:

java.util.zip.ZipException: unknown compression method
        at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:139)
        at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:104)
        at java.io.DataInputStream.readUnsignedShort(DataInputStream.java:290)
        at java.io.DataInputStream.readUTF(DataInputStream.java:519)
        at java.io.DataInputStream.readUTF(DataInputStream.java:496)
        at Server.test(Server.java:44)
        at Server.main(Server.java:12)

With the default buffer size for InflaterInputStream and DeflaterOutputStream
(512 bytes) and ZLib headers enabled (using the -h option to both Client and
Server), the following input will trigger the exception:

in.txt
================================================================================
wfwuwwakwrktgfxivdodjbtfsgbcidftcapyzvzwbzlgprqgxiqivdqbgxiaowtfmvlxbqrrcfbmxomi
xpejsobwfiltinuzszgxjtykyepjhpiqwihqmzmeluxruqmdvbkilbjyeqsbeycsjlwxbywocwviifzi
otamzpqwbwlgzpujcjsyefatcxmqjemkkztqicosbbcuvncmfbvidxqrdznvlaxchsyzzkalsbgzsgdn
lmaxlincicnvgarnnegrixiighvoxphodbmsmmsxnnbzhoukhbxrxprbminffiltdgsqyyvetzoramjo
bxzojlgpgarqnhkvfgskmuigvzbdbmeforzwombyzaxniqilrsfwuafsviewcopyhsnmmdutmhlqrnrz
zifhbckkucantvmeeddnisasjfqwrzphzftbyjwblmrycffugqavjtgfmrpzrllxaawqslivqxwctcyb
pwgajsolssrpezegbdqyeaiirvxznndarqutudpocfryeftnuspetngclvzqlzsrdsspprencebogpri
hdmwrgqhzqhfmnbvwbaimofkryjdihyfhvppogrwtiibpjodhmlfjgzwxycqoeefracfwppxhgahxqzd
ctrqxgiyrqopncbuifgojdajsjxzcjurplwotczmypjaugqfoyjxdaltzfevdmkssxvtbdmrwmoctedl
hcaaquywapmqwtpodbjyxfogcdzkkwjouofmfsgregwphiqogqgwognwalhouvmpmgzxiohauonupvuz
k

================================================================================

(Note that this input consists of one line of 802 characters and one blank
line. The first line is the cause, but the exception is thrown when trying to
read the following line.) For other buffer sizes, other input can be found that
triggers the failure (try any large file with lots of long lines, or use
Test.java below to generate one). The exception seems to occur when the
compressed size of the line of input is a few bytes more than the
InflaterInputStream buffer.

There seems to be some sort of off-by-one error or buffer overflow happening,
but as the bulk of the inflation/deflation is in native code I am unable to
track it down. Please help.

Test.java (generates a large input file to use on the client)
================================================================================
import java.io.*;
import java.util.Random;

public class Test {
        public static void main(String[] args) {
                PrintStream out = new PrintStream(new BufferedOutputStream
(System.out));
                Random random = new Random();
                for (int i = 1; i < 2000; i++) {
                        for (int j = 1; j <= i; j++) {
                                char c = (char)('a' + random.nextInt(26));
                                out.print(c);
                        }
                        out.println();
                }
        }
}
================================================================================
(Review ID: 135062) 
======================================================================

Comments
EVALUATION Not a bug. The problem is, that user does not read the InflaterInputStream stream fully. After readUTF() is called there may be still a checksum byte not read from the underlying input stream of the InflaterOutputStream. This byte will lead to an error on the consequent iteration. Replacing in the submitter's example result = inflater.readUTF(); with String result = in.readUTF(); while (in.read() != -1) { } Remedies the situation. Remember, that the InflaterInputStream can not be considered fully read and cleaned, until read() returns -1 or readUTF returns null. ###@###.### 2001-11-26
26-11-2001