2 // Copyright (C) 2001 Mike Krueger
\r
4 // This file was translated from java, it was part of the GNU Classpath
\r
5 // Copyright (C) 2001 Free Software Foundation, Inc.
\r
7 // This program is free software; you can redistribute it and/or
\r
8 // modify it under the terms of the GNU General Public License
\r
9 // as published by the Free Software Foundation; either version 2
\r
10 // of the License, or (at your option) any later version.
\r
12 // This program is distributed in the hope that it will be useful,
\r
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
15 // GNU General Public License for more details.
\r
17 // You should have received a copy of the GNU General Public License
\r
18 // along with this program; if not, write to the Free Software
\r
19 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
\r
21 // Linking this library statically or dynamically with other modules is
\r
22 // making a combined work based on this library. Thus, the terms and
\r
23 // conditions of the GNU General Public License cover the whole
\r
26 // As a special exception, the copyright holders of this library give you
\r
27 // permission to link this library with independent modules to produce an
\r
28 // executable, regardless of the license terms of these independent
\r
29 // modules, and to copy and distribute the resulting executable under
\r
30 // terms of your choice, provided that you also meet, for each linked
\r
31 // independent module, the terms and conditions of the license of that
\r
32 // module. An independent module is a module which is not derived from
\r
33 // or based on this library. If you modify this library, you may extend
\r
34 // this exception to your version of the library, but you are not
\r
35 // obligated to do so. If you do not wish to do so, delete this
\r
36 // exception statement from your version.
\r
40 using ICSharpCode.SharpZipLib.Checksums;
\r
42 namespace ICSharpCode.SharpZipLib.Zip.Compression
\r
45 public enum DeflateStrategy
\r
47 // The default strategy.
\r
50 // This strategy will only allow longer string repetitions. It is
\r
51 // useful for random data with a small character set.
\r
54 // This strategy will not look for string repetitions at all. It
\r
55 // only encodes with Huffman trees (which means, that more common
\r
56 // characters get a smaller encoding.
\r
60 public class DeflaterEngine : DeflaterConstants
\r
62 static int TOO_FAR = 4096;
\r
65 // private byte[] buffer;
\r
69 int matchStart, matchLen;
\r
72 int strstart, lookahead;
\r
75 DeflateStrategy strategy;
\r
76 int max_chain, max_lazy, niceLength, goodLength;
\r
79 /// The current compression function.
\r
84 /// The input data for compression.
\r
89 /// The total bytes of input read.
\r
94 /// The offset into inputBuf, where input data starts.
\r
99 /// The end offset of the input data.
\r
103 DeflaterPending pending;
\r
104 DeflaterHuffman huffman;
\r
107 /// The adler checksum
\r
111 public DeflaterEngine(DeflaterPending pending)
\r
113 this.pending = pending;
\r
114 huffman = new DeflaterHuffman(pending);
\r
115 adler = new Adler32();
\r
117 window = new byte[2 * WSIZE];
\r
118 head = new short[HASH_SIZE];
\r
119 prev = new short[WSIZE];
\r
121 /* We start at index 1, to avoid a implementation deficiency, that
\r
122 * we cannot build a repeat pattern at index 0.
\r
124 blockStart = strstart = 1;
\r
127 public void Reset()
\r
131 blockStart = strstart = 1;
\r
134 prevAvailable = false;
\r
135 matchLen = MIN_MATCH - 1;
\r
137 for (int i = 0; i < HASH_SIZE; i++) {
\r
141 for (int i = 0; i < WSIZE; i++) {
\r
146 public void ResetAdler()
\r
153 return (int)adler.Value;
\r
157 public int TotalIn {
\r
163 public DeflateStrategy Strategy {
\r
172 public void SetLevel(int lvl)
\r
174 goodLength = DeflaterConstants.GOOD_LENGTH[lvl];
\r
175 max_lazy = DeflaterConstants.MAX_LAZY[lvl];
\r
176 niceLength = DeflaterConstants.NICE_LENGTH[lvl];
\r
177 max_chain = DeflaterConstants.MAX_CHAIN[lvl];
\r
179 if (DeflaterConstants.COMPR_FUNC[lvl] != comprFunc) {
\r
180 // if (DeflaterConstants.DEBUGGING) {
\r
181 // //Console.WriteLine("Change from "+comprFunc +" to "
\r
182 // + DeflaterConstants.COMPR_FUNC[lvl]);
\r
184 switch (comprFunc) {
\r
185 case DEFLATE_STORED:
\r
186 if (strstart > blockStart) {
\r
187 huffman.FlushStoredBlock(window, blockStart,
\r
188 strstart - blockStart, false);
\r
189 blockStart = strstart;
\r
194 if (strstart > blockStart) {
\r
195 huffman.FlushBlock(window, blockStart, strstart - blockStart,
\r
197 blockStart = strstart;
\r
201 if (prevAvailable) {
\r
202 huffman.TallyLit(window[strstart-1] & 0xff);
\r
204 if (strstart > blockStart) {
\r
205 huffman.FlushBlock(window, blockStart, strstart - blockStart, false);
\r
206 blockStart = strstart;
\r
208 prevAvailable = false;
\r
209 matchLen = MIN_MATCH - 1;
\r
212 comprFunc = COMPR_FUNC[lvl];
\r
218 // if (DEBUGGING) {
\r
219 // //Console.WriteLine("updateHash: "+strstart);
\r
221 ins_h = (window[strstart] << HASH_SHIFT) ^ window[strstart + 1];
\r
224 int InsertString()
\r
227 int hash = ((ins_h << HASH_SHIFT) ^ window[strstart + (MIN_MATCH -1)]) & HASH_MASK;
\r
229 // if (DEBUGGING) {
\r
230 // if (hash != (((window[strstart] << (2*HASH_SHIFT)) ^
\r
231 // (window[strstart + 1] << HASH_SHIFT) ^
\r
232 // (window[strstart + 2])) & HASH_MASK)) {
\r
233 // throw new Exception("hash inconsistent: "+hash+"/"
\r
234 // +window[strstart]+","
\r
235 // +window[strstart+1]+","
\r
236 // +window[strstart+2]+","+HASH_SHIFT);
\r
240 prev[strstart & WMASK] = match = head[hash];
\r
241 head[hash] = (short)strstart;
\r
243 return match & 0xffff;
\r
248 Array.Copy(window, WSIZE, window, 0, WSIZE);
\r
249 matchStart -= WSIZE;
\r
251 blockStart -= WSIZE;
\r
253 /* Slide the hash table (could be avoided with 32 bit values
\r
254 * at the expense of memory usage).
\r
256 for (int i = 0; i < HASH_SIZE; ++i) {
\r
257 int m = head[i] & 0xffff;
\r
258 head[i] = (short)(m >= WSIZE ? (m - WSIZE) : 0);
\r
261 /* Slide the prev table. */
\r
262 for (int i = 0; i < WSIZE; i++) {
\r
263 int m = prev[i] & 0xffff;
\r
264 prev[i] = (short)(m >= WSIZE ? (m - WSIZE) : 0);
\r
268 public void FillWindow()
\r
270 /* If the window is almost full and there is insufficient lookahead,
\r
271 * move the upper half to the lower one to make room in the upper half.
\r
273 if (strstart >= WSIZE + MAX_DIST) {
\r
277 /* If there is not enough lookahead, but still some input left,
\r
278 * read in the input
\r
280 while (lookahead < DeflaterConstants.MIN_LOOKAHEAD && inputOff < inputEnd) {
\r
281 int more = 2 * WSIZE - lookahead - strstart;
\r
283 if (more > inputEnd - inputOff) {
\r
284 more = inputEnd - inputOff;
\r
287 System.Array.Copy(inputBuf, inputOff, window, strstart + lookahead, more);
\r
288 adler.Update(inputBuf, inputOff, more);
\r
295 if (lookahead >= MIN_MATCH) {
\r
300 bool FindLongestMatch(int curMatch)
\r
302 int chainLength = this.max_chain;
\r
303 int niceLength = this.niceLength;
\r
304 short[] prev = this.prev;
\r
305 int scan = this.strstart;
\r
307 int best_end = this.strstart + matchLen;
\r
308 int best_len = Math.Max(matchLen, MIN_MATCH - 1);
\r
310 int limit = Math.Max(strstart - MAX_DIST, 0);
\r
312 int strend = strstart + MAX_MATCH - 1;
\r
313 byte scan_end1 = window[best_end - 1];
\r
314 byte scan_end = window[best_end];
\r
316 /* Do not waste too much time if we already have a good match: */
\r
317 if (best_len >= this.goodLength) {
\r
321 /* Do not look for matches beyond the end of the input. This is necessary
\r
322 * to make deflate deterministic.
\r
324 if (niceLength > lookahead) {
\r
325 niceLength = lookahead;
\r
328 if (DeflaterConstants.DEBUGGING && strstart > 2 * WSIZE - MIN_LOOKAHEAD) {
\r
329 throw new InvalidOperationException("need lookahead");
\r
333 if (DeflaterConstants.DEBUGGING && curMatch >= strstart) {
\r
334 throw new InvalidOperationException("future match");
\r
336 if (window[curMatch + best_len] != scan_end ||
\r
337 window[curMatch + best_len - 1] != scan_end1 ||
\r
338 window[curMatch] != window[scan] ||
\r
339 window[curMatch + 1] != window[scan + 1]) {
\r
343 match = curMatch + 2;
\r
346 /* We check for insufficient lookahead only every 8th comparison;
\r
347 * the 256th check will be made at strstart+258.
\r
349 while (window[++scan] == window[++match] &&
\r
350 window[++scan] == window[++match] &&
\r
351 window[++scan] == window[++match] &&
\r
352 window[++scan] == window[++match] &&
\r
353 window[++scan] == window[++match] &&
\r
354 window[++scan] == window[++match] &&
\r
355 window[++scan] == window[++match] &&
\r
356 window[++scan] == window[++match] && scan < strend) ;
\r
358 if (scan > best_end) {
\r
359 // if (DeflaterConstants.DEBUGGING && ins_h == 0)
\r
360 // System.err.println("Found match: "+curMatch+"-"+(scan-strstart));
\r
361 matchStart = curMatch;
\r
363 best_len = scan - strstart;
\r
365 if (best_len >= niceLength) {
\r
369 scan_end1 = window[best_end - 1];
\r
370 scan_end = window[best_end];
\r
373 } while ((curMatch = (prev[curMatch & WMASK] & 0xffff)) > limit && --chainLength != 0);
\r
375 matchLen = Math.Min(best_len, lookahead);
\r
376 return matchLen >= MIN_MATCH;
\r
379 public void SetDictionary(byte[] buffer, int offset, int length)
\r
381 if (DeflaterConstants.DEBUGGING && strstart != 1) {
\r
382 throw new InvalidOperationException("strstart not 1");
\r
384 adler.Update(buffer, offset, length);
\r
385 if (length < MIN_MATCH) {
\r
388 if (length > MAX_DIST) {
\r
389 offset += length - MAX_DIST;
\r
393 System.Array.Copy(buffer, offset, window, strstart, length);
\r
397 while (--length > 0) {
\r
402 blockStart = strstart;
\r
405 bool DeflateStored(bool flush, bool finish)
\r
407 if (!flush && lookahead == 0) {
\r
411 strstart += lookahead;
\r
414 int storedLen = strstart - blockStart;
\r
416 if ((storedLen >= DeflaterConstants.MAX_BLOCK_SIZE) || /* Block is full */
\r
417 (blockStart < WSIZE && storedLen >= MAX_DIST) || /* Block may move out of window */
\r
419 bool lastBlock = finish;
\r
420 if (storedLen > DeflaterConstants.MAX_BLOCK_SIZE) {
\r
421 storedLen = DeflaterConstants.MAX_BLOCK_SIZE;
\r
425 // if (DeflaterConstants.DEBUGGING) {
\r
426 // //Console.WriteLine("storedBlock["+storedLen+","+lastBlock+"]");
\r
429 huffman.FlushStoredBlock(window, blockStart, storedLen, lastBlock);
\r
430 blockStart += storedLen;
\r
436 private bool DeflateFast(bool flush, bool finish)
\r
438 if (lookahead < MIN_LOOKAHEAD && !flush) {
\r
442 while (lookahead >= MIN_LOOKAHEAD || flush) {
\r
443 if (lookahead == 0) {
\r
444 /* We are flushing everything */
\r
445 huffman.FlushBlock(window, blockStart, strstart - blockStart, finish);
\r
446 blockStart = strstart;
\r
450 if (strstart > 2 * WSIZE - MIN_LOOKAHEAD) {
\r
451 /* slide window, as findLongestMatch need this.
\r
452 * This should only happen when flushing and the window
\r
459 if (lookahead >= MIN_MATCH &&
\r
460 (hashHead = InsertString()) != 0 &&
\r
461 strategy != DeflateStrategy.HuffmanOnly &&
\r
462 strstart - hashHead <= MAX_DIST &&
\r
463 FindLongestMatch(hashHead)) {
\r
464 /* longestMatch sets matchStart and matchLen */
\r
465 // if (DeflaterConstants.DEBUGGING) {
\r
466 // for (int i = 0 ; i < matchLen; i++) {
\r
467 // if (window[strstart+i] != window[matchStart + i]) {
\r
468 // throw new Exception();
\r
473 // -jr- Hak hak hak this stops problems with fast/low compression and index out of range
\r
474 if (huffman.TallyDist(strstart - matchStart, matchLen)) {
\r
475 bool lastBlock = finish && lookahead == 0;
\r
476 huffman.FlushBlock(window, blockStart, strstart - blockStart, lastBlock);
\r
477 blockStart = strstart;
\r
480 lookahead -= matchLen;
\r
481 if (matchLen <= max_lazy && lookahead >= MIN_MATCH) {
\r
482 while (--matchLen > 0) {
\r
488 strstart += matchLen;
\r
489 if (lookahead >= MIN_MATCH - 1) {
\r
493 matchLen = MIN_MATCH - 1;
\r
496 /* No match found */
\r
497 huffman.TallyLit(window[strstart] & 0xff);
\r
502 if (huffman.IsFull()) {
\r
503 bool lastBlock = finish && lookahead == 0;
\r
504 huffman.FlushBlock(window, blockStart, strstart - blockStart, lastBlock);
\r
505 blockStart = strstart;
\r
512 bool DeflateSlow(bool flush, bool finish)
\r
514 if (lookahead < MIN_LOOKAHEAD && !flush) {
\r
518 while (lookahead >= MIN_LOOKAHEAD || flush) {
\r
519 if (lookahead == 0) {
\r
520 if (prevAvailable) {
\r
521 huffman.TallyLit(window[strstart-1] & 0xff);
\r
523 prevAvailable = false;
\r
525 /* We are flushing everything */
\r
526 if (DeflaterConstants.DEBUGGING && !flush) {
\r
527 throw new Exception("Not flushing, but no lookahead");
\r
529 huffman.FlushBlock(window, blockStart, strstart - blockStart,
\r
531 blockStart = strstart;
\r
535 if (strstart >= 2 * WSIZE - MIN_LOOKAHEAD) {
\r
536 /* slide window, as findLongestMatch need this.
\r
537 * This should only happen when flushing and the window
\r
543 int prevMatch = matchStart;
\r
544 int prevLen = matchLen;
\r
545 if (lookahead >= MIN_MATCH) {
\r
546 int hashHead = InsertString();
\r
547 if (strategy != DeflateStrategy.HuffmanOnly && hashHead != 0 && strstart - hashHead <= MAX_DIST && FindLongestMatch(hashHead)) {
\r
548 /* longestMatch sets matchStart and matchLen */
\r
550 /* Discard match if too small and too far away */
\r
551 if (matchLen <= 5 && (strategy == DeflateStrategy.Filtered || (matchLen == MIN_MATCH && strstart - matchStart > TOO_FAR))) {
\r
552 matchLen = MIN_MATCH - 1;
\r
557 /* previous match was better */
\r
558 if (prevLen >= MIN_MATCH && matchLen <= prevLen) {
\r
559 // if (DeflaterConstants.DEBUGGING) {
\r
560 // for (int i = 0 ; i < matchLen; i++) {
\r
561 // if (window[strstart-1+i] != window[prevMatch + i])
\r
562 // throw new Exception();
\r
565 huffman.TallyDist(strstart - 1 - prevMatch, prevLen);
\r
570 if (lookahead >= MIN_MATCH) {
\r
573 } while (--prevLen > 0);
\r
576 prevAvailable = false;
\r
577 matchLen = MIN_MATCH - 1;
\r
579 if (prevAvailable) {
\r
580 huffman.TallyLit(window[strstart-1] & 0xff);
\r
582 prevAvailable = true;
\r
587 if (huffman.IsFull()) {
\r
588 int len = strstart - blockStart;
\r
589 if (prevAvailable) {
\r
592 bool lastBlock = (finish && lookahead == 0 && !prevAvailable);
\r
593 huffman.FlushBlock(window, blockStart, len, lastBlock);
\r
601 public bool Deflate(bool flush, bool finish)
\r
606 bool canFlush = flush && inputOff == inputEnd;
\r
607 // if (DeflaterConstants.DEBUGGING) {
\r
608 // //Console.WriteLine("window: ["+blockStart+","+strstart+","
\r
609 // +lookahead+"], "+comprFunc+","+canFlush);
\r
611 switch (comprFunc) {
\r
612 case DEFLATE_STORED:
\r
613 progress = DeflateStored(canFlush, finish);
\r
616 progress = DeflateFast(canFlush, finish);
\r
619 progress = DeflateSlow(canFlush, finish);
\r
622 throw new InvalidOperationException("unknown comprFunc");
\r
624 } while (pending.IsFlushed && progress); /* repeat while we have no pending output and progress was made */
\r
628 public void SetInput(byte[] buf, int off, int len)
\r
630 if (inputOff < inputEnd) {
\r
631 throw new InvalidOperationException("Old input was not completely processed");
\r
634 int end = off + len;
\r
636 /* We want to throw an ArrayIndexOutOfBoundsException early. The
\r
637 * check is very tricky: it also handles integer wrap around.
\r
639 if (0 > off || off > end || end > buf.Length) {
\r
640 throw new ArgumentOutOfRangeException();
\r
648 public bool NeedsInput()
\r
650 return inputEnd == inputOff;
\r