diff-delta.con commit Merge branch 'jc/diff' into next (cf1e6d1)
   1/*
   2 * diff-delta.c: generate a delta between two buffers
   3 *
   4 *  Many parts of this file have been lifted from LibXDiff version 0.10.
   5 *  http://www.xmailserver.org/xdiff-lib.html
   6 *
   7 *  LibXDiff was written by Davide Libenzi <davidel@xmailserver.org>
   8 *  Copyright (C) 2003  Davide Libenzi
   9 *
  10 *  Many mods for GIT usage by Nicolas Pitre <nico@cam.org>, (C) 2005.
  11 *
  12 *  This file is free software; you can redistribute it and/or
  13 *  modify it under the terms of the GNU Lesser General Public
  14 *  License as published by the Free Software Foundation; either
  15 *  version 2.1 of the License, or (at your option) any later version.
  16 *
  17 *  Use of this within git automatically means that the LGPL
  18 *  licensing gets turned into GPLv2 within this project.
  19 */
  20
  21#include <stdlib.h>
  22#include <string.h>
  23#include <zlib.h>
  24#include "delta.h"
  25
  26
  27/* block size: min = 16, max = 64k, power of 2 */
  28#define BLK_SIZE 16
  29
  30#define MIN(a, b) ((a) < (b) ? (a) : (b))
  31
  32#define GR_PRIME 0x9e370001
  33#define HASH(v, shift) (((unsigned int)(v) * GR_PRIME) >> (shift))
  34
  35struct index {
  36        const unsigned char *ptr;
  37        unsigned int val;
  38        struct index *next;
  39};
  40
  41static struct index ** delta_index(const unsigned char *buf,
  42                                   unsigned long bufsize,
  43                                   unsigned long trg_bufsize,
  44                                   unsigned int *hash_shift)
  45{
  46        unsigned int i, hsize, hshift, hlimit, entries, *hash_count;
  47        const unsigned char *data;
  48        struct index *entry, **hash;
  49        void *mem;
  50
  51        /* determine index hash size */
  52        entries = bufsize  / BLK_SIZE;
  53        hsize = entries / 4;
  54        for (i = 4; (1 << i) < hsize && i < 31; i++);
  55        hsize = 1 << i;
  56        hshift = 32 - i;
  57        *hash_shift = hshift;
  58
  59        /* allocate lookup index */
  60        mem = malloc(hsize * sizeof(*hash) + entries * sizeof(*entry));
  61        if (!mem)
  62                return NULL;
  63        hash = mem;
  64        entry = mem + hsize * sizeof(*hash);
  65        memset(hash, 0, hsize * sizeof(*hash));
  66
  67        /* allocate an array to count hash entries */
  68        hash_count = calloc(hsize, sizeof(*hash_count));
  69        if (!hash_count) {
  70                free(hash);
  71                return NULL;
  72        }
  73
  74        /* then populate the index */
  75        data = buf + entries * BLK_SIZE - BLK_SIZE;
  76        while (data >= buf) {
  77                unsigned int val = adler32(0, data, BLK_SIZE);
  78                i = HASH(val, hshift);
  79                entry->ptr = data;
  80                entry->val = val;
  81                entry->next = hash[i];
  82                hash[i] = entry++;
  83                hash_count[i]++;
  84                data -= BLK_SIZE;
  85        }
  86
  87        /*
  88         * Determine a limit on the number of entries in the same hash
  89         * bucket.  This guard us against patological data sets causing
  90         * really bad hash distribution with most entries in the same hash
  91         * bucket that would bring us to O(m*n) computing costs (m and n
  92         * corresponding to reference and target buffer sizes).
  93         *
  94         * The more the target buffer is large, the more it is important to
  95         * have small entry lists for each hash buckets.  With such a limit
  96         * the cost is bounded to something more like O(m+n).
  97         */
  98        hlimit = (1 << 26) / trg_bufsize;
  99        if (hlimit < 4*BLK_SIZE)
 100                hlimit = 4*BLK_SIZE;
 101
 102        /*
 103         * Now make sure none of the hash buckets has more entries than
 104         * we're willing to test.  Otherwise we cull the entry list
 105         * uniformly to still preserve a good repartition across
 106         * the reference buffer.
 107         */
 108        for (i = 0; i < hsize; i++) {
 109                if (hash_count[i] < hlimit)
 110                        continue;
 111                entry = hash[i];
 112                do {
 113                        struct index *keep = entry;
 114                        int skip = hash_count[i] / hlimit / 2;
 115                        do {
 116                                entry = entry->next;
 117                        } while(--skip && entry);
 118                        keep->next = entry;
 119                } while(entry);
 120        }
 121        free(hash_count);
 122
 123        return hash;
 124}
 125
 126/* provide the size of the copy opcode given the block offset and size */
 127#define COPYOP_SIZE(o, s) \
 128    (!!(o & 0xff) + !!(o & 0xff00) + !!(o & 0xff0000) + !!(o & 0xff000000) + \
 129     !!(s & 0xff) + !!(s & 0xff00) + 1)
 130
 131/* the maximum size for any opcode */
 132#define MAX_OP_SIZE COPYOP_SIZE(0xffffffff, 0xffffffff)
 133
 134void *diff_delta(void *from_buf, unsigned long from_size,
 135                 void *to_buf, unsigned long to_size,
 136                 unsigned long *delta_size,
 137                 unsigned long max_size)
 138{
 139        unsigned int i, outpos, outsize, inscnt, hash_shift;
 140        const unsigned char *ref_data, *ref_top, *data, *top;
 141        unsigned char *out;
 142        struct index *entry, **hash;
 143
 144        if (!from_size || !to_size)
 145                return NULL;
 146        hash = delta_index(from_buf, from_size, to_size, &hash_shift);
 147        if (!hash)
 148                return NULL;
 149
 150        outpos = 0;
 151        outsize = 8192;
 152        if (max_size && outsize >= max_size)
 153                outsize = max_size + MAX_OP_SIZE + 1;
 154        out = malloc(outsize);
 155        if (!out) {
 156                free(hash);
 157                return NULL;
 158        }
 159
 160        ref_data = from_buf;
 161        ref_top = from_buf + from_size;
 162        data = to_buf;
 163        top = to_buf + to_size;
 164
 165        /* store reference buffer size */
 166        out[outpos++] = from_size;
 167        from_size >>= 7;
 168        while (from_size) {
 169                out[outpos - 1] |= 0x80;
 170                out[outpos++] = from_size;
 171                from_size >>= 7;
 172        }
 173
 174        /* store target buffer size */
 175        out[outpos++] = to_size;
 176        to_size >>= 7;
 177        while (to_size) {
 178                out[outpos - 1] |= 0x80;
 179                out[outpos++] = to_size;
 180                to_size >>= 7;
 181        }
 182
 183        inscnt = 0;
 184
 185        while (data < top) {
 186                unsigned int moff = 0, msize = 0;
 187                if (data + BLK_SIZE <= top) {
 188                        unsigned int val = adler32(0, data, BLK_SIZE);
 189                        i = HASH(val, hash_shift);
 190                        for (entry = hash[i]; entry; entry = entry->next) {
 191                                const unsigned char *ref = entry->ptr;
 192                                const unsigned char *src = data;
 193                                unsigned int ref_size = ref_top - ref;
 194                                if (entry->val != val)
 195                                        continue;
 196                                if (ref_size > top - src)
 197                                        ref_size = top - src;
 198                                if (ref_size > 0x10000)
 199                                        ref_size = 0x10000;
 200                                if (ref_size <= msize)
 201                                        break;
 202                                while (ref_size-- && *src++ == *ref)
 203                                        ref++;
 204                                if (msize < ref - entry->ptr) {
 205                                        /* this is our best match so far */
 206                                        msize = ref - entry->ptr;
 207                                        moff = entry->ptr - ref_data;
 208                                }
 209                        }
 210                }
 211
 212                if (!msize || msize < COPYOP_SIZE(moff, msize)) {
 213                        if (!inscnt)
 214                                outpos++;
 215                        out[outpos++] = *data++;
 216                        inscnt++;
 217                        if (inscnt == 0x7f) {
 218                                out[outpos - inscnt - 1] = inscnt;
 219                                inscnt = 0;
 220                        }
 221                } else {
 222                        unsigned char *op;
 223
 224                        if (inscnt) {
 225                                out[outpos - inscnt - 1] = inscnt;
 226                                inscnt = 0;
 227                        }
 228
 229                        data += msize;
 230                        op = out + outpos++;
 231                        i = 0x80;
 232
 233                        if (moff & 0xff) { out[outpos++] = moff; i |= 0x01; }
 234                        moff >>= 8;
 235                        if (moff & 0xff) { out[outpos++] = moff; i |= 0x02; }
 236                        moff >>= 8;
 237                        if (moff & 0xff) { out[outpos++] = moff; i |= 0x04; }
 238                        moff >>= 8;
 239                        if (moff & 0xff) { out[outpos++] = moff; i |= 0x08; }
 240
 241                        if (msize & 0xff) { out[outpos++] = msize; i |= 0x10; }
 242                        msize >>= 8;
 243                        if (msize & 0xff) { out[outpos++] = msize; i |= 0x20; }
 244
 245                        *op = i;
 246                }
 247
 248                if (outpos >= outsize - MAX_OP_SIZE) {
 249                        void *tmp = out;
 250                        outsize = outsize * 3 / 2;
 251                        if (max_size && outsize >= max_size)
 252                                outsize = max_size + MAX_OP_SIZE + 1;
 253                        if (max_size && outpos > max_size)
 254                                out = NULL;
 255                        else
 256                                out = realloc(out, outsize);
 257                        if (!out) {
 258                                free(tmp);
 259                                free(hash);
 260                                return NULL;
 261                        }
 262                }
 263        }
 264
 265        if (inscnt)
 266                out[outpos - inscnt - 1] = inscnt;
 267
 268        free(hash);
 269        *delta_size = outpos;
 270        return out;
 271}