package net.i2p.util; import java.io.IOException; import java.io.OutputStream; import java.util.zip.CRC32; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; /** * GZIP implementation per * <a href="http://www.faqs.org/rfcs/rfc1952.html">RFC 1952</a>, reusing * java's standard CRC32 and Deflater implementations. The main difference * is that this implementation allows its state to be reset to initial * values, and hence reused, while the standard GZIPOutputStream writes the * GZIP header to the stream on instantiation, rather than on first write. * */ public class ResettableGZIPOutputStream extends DeflaterOutputStream { /** has the header been written out yet? */ private boolean _headerWritten; /** how much data is in the uncompressed stream? */ private long _writtenSize; private final CRC32 _crc32; private static final boolean DEBUG = false; public ResettableGZIPOutputStream(OutputStream o) { super(o, new Deflater(9, true)); _crc32 = new CRC32(); } /** * Reinitialze everything so we can write a brand new gzip output stream * again. */ public void reset() { if (DEBUG) System.out.println("Resetting (writtenSize=" + _writtenSize + ")"); def.reset(); _crc32.reset(); _writtenSize = 0; _headerWritten = false; } private static final byte[] HEADER = new byte[] { (byte)0x1F, (byte)0x8b, // magic bytes 0x08, // compression format == DEFLATE 0x00, // flags (NOT using CRC16, filename, etc) 0x00, 0x00, 0x00, 0x00, // no modification time available (don't leak this!) 0x02, // maximum compression (byte)0xFF // unknown creator OS (!!!) }; /** * obviously not threadsafe, but its a stream, thats standard */ private void ensureHeaderIsWritten() throws IOException { if (_headerWritten) return; if (DEBUG) System.out.println("Writing header"); out.write(HEADER); _headerWritten = true; } private void writeFooter() throws IOException { // damn RFC writing their bytes backwards... long crcVal = _crc32.getValue(); out.write((int)(crcVal & 0xFF)); out.write((int)((crcVal >>> 8) & 0xFF)); out.write((int)((crcVal >>> 16) & 0xFF)); out.write((int)((crcVal >>> 24) & 0xFF)); long sizeVal = _writtenSize; // % (1 << 31) // *redundant* out.write((int)(sizeVal & 0xFF)); out.write((int)((sizeVal >>> 8) & 0xFF)); out.write((int)((sizeVal >>> 16) & 0xFF)); out.write((int)((sizeVal >>> 24) & 0xFF)); out.flush(); if (DEBUG) { System.out.println("Footer written: crcVal=" + crcVal + " sizeVal=" + sizeVal + " written=" + _writtenSize); System.out.println("size hex: " + Long.toHexString(sizeVal)); System.out.print( "size2 hex:" + Long.toHexString((int)(sizeVal & 0xFF))); System.out.print( Long.toHexString((int)((sizeVal >>> 8) & 0xFF))); System.out.print( Long.toHexString((int)((sizeVal >>> 16) & 0xFF))); System.out.print( Long.toHexString((int)((sizeVal >>> 24) & 0xFF))); System.out.println(); } } @Override public void close() throws IOException { finish(); super.close(); } @Override public void finish() throws IOException { ensureHeaderIsWritten(); super.finish(); writeFooter(); } @Override public void write(int b) throws IOException { ensureHeaderIsWritten(); _crc32.update(b); _writtenSize++; super.write(b); } @Override public void write(byte buf[]) throws IOException { write(buf, 0, buf.length); } @Override public void write(byte buf[], int off, int len) throws IOException { ensureHeaderIsWritten(); _crc32.update(buf, off, len); _writtenSize += len; super.write(buf, off, len); } /****** public static void main(String args[]) { for (int i = 0; i < 2; i++) test(); } private static void test() { byte b[] = "hi, how are you today?".getBytes(); try { ByteArrayOutputStream baos = new ByteArrayOutputStream(64); ResettableGZIPOutputStream o = new ResettableGZIPOutputStream(baos); o.write(b); o.finish(); o.flush(); byte compressed[] = baos.toByteArray(); ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); SnoopGZIPOutputStream gzo = new SnoopGZIPOutputStream(baos2); gzo.write(b); gzo.finish(); gzo.flush(); long value = gzo.getCRC().getValue(); byte compressed2[] = baos2.toByteArray(); System.out.println("CRC32 values: Resettable = " + o._crc32.getValue() + " GZIP = " + value); System.out.print("Resettable compressed data: "); for (int i = 0; i < compressed.length; i++) System.out.print(Integer.toHexString(compressed[i] & 0xFF) + " "); System.out.println(); System.out.print(" GZIP compressed data: "); for (int i = 0; i < compressed2.length; i++) System.out.print(Integer.toHexString(compressed2[i] & 0xFF) + " "); System.out.println(); GZIPInputStream in = new GZIPInputStream(new ByteArrayInputStream(compressed)); byte rv[] = new byte[128]; int read = in.read(rv); if (!DataHelper.eq(rv, 0, b, 0, b.length)) throw new RuntimeException("foo, read=" + read); else System.out.println("match, w00t"); } catch (Exception e) { e.printStackTrace(); } } // just for testing/verification, expose the CRC32 values private static final class SnoopGZIPOutputStream extends GZIPOutputStream { public SnoopGZIPOutputStream(OutputStream o) throws IOException { super(o); } public CRC32 getCRC() { return crc; } } ******/ }