public static byte[] Create(byte[] origin, byte[] target) { var zDelta = new Writer(); int lenOut = target.Length; int lenSrc = origin.Length; int i, lastRead = -1; zDelta.PutInt((uint)lenOut); zDelta.PutChar('\n'); // If the source is very small, it means that we have no // chance of ever doing a copy command. Just output a single // literal segment for the entire target and exit. if (lenSrc <= NHASH) { zDelta.PutInt((uint)lenOut); zDelta.PutChar(':'); zDelta.PutArray(target, 0, lenOut); zDelta.PutInt(Checksum(target)); zDelta.PutChar(';'); return(zDelta.ToArray()); } // Compute the hash table used to locate matching sections in the source. int nHash = (int)lenSrc / NHASH; int[] collide = new int[nHash]; int[] landmark = new int[nHash]; for (i = 0; i < collide.Length; i++) { collide[i] = -1; } for (i = 0; i < landmark.Length; i++) { landmark[i] = -1; } int hv; RollingHash h = new RollingHash(); for (i = 0; i < lenSrc - NHASH; i += NHASH) { h.Init(origin, i); hv = (int)(h.Value() % nHash); collide[i / NHASH] = landmark[hv]; landmark[hv] = i / NHASH; } int _base = 0; int iSrc, iBlock; int bestCnt, bestOfst = 0, bestLitsz = 0; while (_base + NHASH < lenOut) { bestOfst = 0; bestLitsz = 0; h.Init(target, _base); i = 0; // Trying to match a landmark against zOut[_base+i] bestCnt = 0; while (true) { int limit = 250; hv = (int)(h.Value() % nHash); iBlock = landmark[hv]; while (iBlock >= 0 && (limit--) > 0) { // // The hash window has identified a potential match against // landmark block iBlock. But we need to investigate further. // // Look for a region in zOut that matches zSrc. Anchor the search // at zSrc[iSrc] and zOut[_base+i]. Do not include anything prior to // zOut[_base] or after zOut[outLen] nor anything after zSrc[srcLen]. // // Set cnt equal to the length of the match and set ofst so that // zSrc[ofst] is the first element of the match. litsz is the number // of characters between zOut[_base] and the beginning of the match. // sz will be the overhead (in bytes) needed to encode the copy // command. Only generate copy command if the overhead of the // copy command is less than the amount of literal text to be copied. // int cnt, ofst, litsz; int j, k, x, y; int sz; // Beginning at iSrc, match forwards as far as we can. // j counts the number of characters that match. iSrc = iBlock * NHASH; for (j = 0, x = iSrc, y = _base + i; x < lenSrc && y < lenOut; j++, x++, y++) { if (origin[x] != target[y]) { break; } } j--; // Beginning at iSrc-1, match backwards as far as we can. // k counts the number of characters that match. for (k = 1; k < iSrc && k <= i; k++) { if (origin[iSrc - k] != target[_base + i - k]) { break; } } k--; // Compute the offset and size of the matching region. ofst = iSrc - k; cnt = j + k + 1; litsz = i - k; // Number of bytes of literal text before the copy // sz will hold the number of bytes needed to encode the "insert" // command and the copy command, not counting the "insert" text. sz = DigitCount(i - k) + DigitCount(cnt) + DigitCount(ofst) + 3; if (cnt >= sz && cnt > bestCnt) { // Remember this match only if it is the best so far and it // does not increase the file size. bestCnt = cnt; bestOfst = iSrc - k; bestLitsz = litsz; } // Check the next matching block iBlock = collide[iBlock]; } // We have a copy command that does not cause the delta to be larger // than a literal insert. So add the copy command to the delta. if (bestCnt > 0) { if (bestLitsz > 0) { // Add an insert command before the copy. zDelta.PutInt((uint)bestLitsz); zDelta.PutChar(':'); zDelta.PutArray(target, _base, _base + bestLitsz); _base += bestLitsz; } _base += bestCnt; zDelta.PutInt((uint)bestCnt); zDelta.PutChar('@'); zDelta.PutInt((uint)bestOfst); zDelta.PutChar(','); if (bestOfst + bestCnt - 1 > lastRead) { lastRead = bestOfst + bestCnt - 1; } bestCnt = 0; break; } // If we reach this point, it means no match is found so far if (_base + i + NHASH >= lenOut) { // We have reached the end and have not found any // matches. Do an "insert" for everything that does not match zDelta.PutInt((uint)(lenOut - _base)); zDelta.PutChar(':'); zDelta.PutArray(target, _base, _base + lenOut - _base); _base = lenOut; break; } // Advance the hash by one character. Keep looking for a match. h.Next(target[_base + i + NHASH]); i++; } } // Output a final "insert" record to get all the text at the end of // the file that does not match anything in the source. if (_base < lenOut) { zDelta.PutInt((uint)(lenOut - _base)); zDelta.PutChar(':'); zDelta.PutArray(target, _base, _base + lenOut - _base); } // Output the final checksum record. zDelta.PutInt(Checksum(target)); zDelta.PutChar(';'); return(zDelta.ToArray()); }
public static byte[] Apply(byte[] origin, byte[] delta) { uint limit, total = 0; uint lenSrc = (uint)origin.Length; uint lenDelta = (uint)delta.Length; Reader zDelta = new Reader(delta); limit = zDelta.GetInt(); if (zDelta.GetChar() != '\n') { throw new Exception("size integer not terminated by \'\\n\'"); } Writer zOut = new Writer(); while (zDelta.HaveBytes()) { uint cnt, ofst; cnt = zDelta.GetInt(); switch (zDelta.GetChar()) { case '@': ofst = zDelta.GetInt(); if (zDelta.HaveBytes() && zDelta.GetChar() != ',') { throw new Exception("copy command not terminated by \',\'"); } total += cnt; if (total > limit) { throw new Exception("copy exceeds output file size"); } if (ofst + cnt > lenSrc) { throw new Exception("copy extends past end of input"); } zOut.PutArray(origin, (int)ofst, (int)(ofst + cnt)); break; case ':': total += cnt; if (total > limit) { throw new Exception("insert command gives an output larger than predicted"); } if (cnt > lenDelta) { throw new Exception("insert count exceeds size of delta"); } zOut.PutArray(zDelta.a, (int)zDelta.pos, (int)(zDelta.pos + cnt)); zDelta.pos += cnt; break; case ';': byte[] output = zOut.ToArray(); // // Checksum is optional (2017-10-05) // http://fossil-scm.org/xfer/info/d3a46b2a45b92bbc // //if (cnt != Checksum (output)) // throw new Exception("bad checksum"); if (total != limit) { throw new Exception("generated size does not match predicted size"); } return(output); default: throw new Exception("unknown delta operator"); } } throw new Exception("unterminated delta"); }