diff-delta.con commit diff-delta: cull collided hash bucket more aggressively. (c436eb8)
   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 "delta.h"
  24
  25
  26struct index {
  27        const unsigned char *ptr;
  28        struct index *next;
  29};
  30
  31static struct index ** delta_index(const unsigned char *buf,
  32                                   unsigned long bufsize,
  33                                   unsigned long trg_bufsize,
  34                                   unsigned int *hash_shift)
  35{
  36        unsigned long hsize;
  37        unsigned int i, hshift, hlimit, *hash_count;
  38        const unsigned char *data;
  39        struct index *entry, **hash;
  40        void *mem;
  41
  42        /* determine index hash size */
  43        hsize = bufsize / 4;
  44        for (i = 8; (1 << i) < hsize && i < 24; i += 2);
  45        hsize = 1 << i;
  46        hshift = (i - 8) / 2;
  47        *hash_shift = hshift;
  48
  49        /* allocate lookup index */
  50        mem = malloc(hsize * sizeof(*hash) + bufsize * sizeof(*entry));
  51        if (!mem)
  52                return NULL;
  53        hash = mem;
  54        entry = mem + hsize * sizeof(*hash);
  55        memset(hash, 0, hsize * sizeof(*hash));
  56
  57        /* allocate an array to count hash entries */
  58        hash_count = calloc(hsize, sizeof(*hash_count));
  59        if (!hash_count) {
  60                free(hash);
  61                return NULL;
  62        }
  63
  64        /* then populate the index */
  65        data = buf + bufsize - 2;
  66        while (data > buf) {
  67                entry->ptr = --data;
  68                i = data[0] ^ ((data[1] ^ (data[2] << hshift)) << hshift);
  69                entry->next = hash[i];
  70                hash[i] = entry++;
  71                hash_count[i]++;
  72        }
  73
  74        /*
  75         * Determine a limit on the number of entries in the same hash
  76         * bucket.  This guard us against patological data sets causing
  77         * really bad hash distribution with most entries in the same hash
  78         * bucket that would bring us to O(m*n) computing costs (m and n
  79         * corresponding to reference and target buffer sizes).
  80         *
  81         * The more the target buffer is large, the more it is important to
  82         * have small entry lists for each hash buckets.  With such a limit
  83         * the cost is bounded to something more like O(m+n).
  84         */
  85        hlimit = (1 << 26) / trg_bufsize;
  86        if (hlimit < 16)
  87                hlimit = 16;
  88
  89        /*
  90         * Now make sure none of the hash buckets has more entries than
  91         * we're willing to test.  Otherwise we cull the entry list to
  92         * limit identical three byte prefixes to still preserve a good
  93         * repartition across the reference buffer.
  94         */
  95        for (i = 0; i < hsize; i++) {
  96                struct index **list, *bucket, *remaining;
  97                int cnt;
  98                if (hash_count[i] < hlimit)
  99                        continue;
 100
 101                bucket = NULL;
 102                list = &bucket;
 103                remaining = hash[i];
 104                cnt = 0;
 105                while (cnt < hlimit && remaining) {
 106                        struct index *this = remaining, *that;
 107                        remaining = remaining->next;
 108                        for (that = bucket; that; that = that->next) {
 109                                if (!memcmp(that->ptr, this->ptr, 3))
 110                                        break;
 111                        }
 112                        if (that)
 113                                continue; /* discard */
 114                        cnt++;
 115                        *list = this;
 116                        list = &(this->next);
 117                        this->next = NULL;
 118                }
 119                hash[i] = bucket;
 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 + 3 <= top) {
 188                        i = data[0] ^ ((data[1] ^ (data[2] << hash_shift)) << hash_shift);
 189                        for (entry = hash[i]; entry; entry = entry->next) {
 190                                const unsigned char *ref = entry->ptr;
 191                                const unsigned char *src = data;
 192                                unsigned int ref_size = ref_top - ref;
 193                                if (ref_size > top - src)
 194                                        ref_size = top - src;
 195                                if (ref_size > 0x10000)
 196                                        ref_size = 0x10000;
 197                                if (ref_size <= msize)
 198                                        break;
 199                                if (*ref != *src)
 200                                        continue;
 201                                while (ref_size-- && *++src == *++ref);
 202                                if (msize < ref - entry->ptr) {
 203                                        /* this is our best match so far */
 204                                        msize = ref - entry->ptr;
 205                                        moff = entry->ptr - ref_data;
 206                                }
 207                        }
 208                }
 209
 210                if (!msize || msize < COPYOP_SIZE(moff, msize)) {
 211                        if (!inscnt)
 212                                outpos++;
 213                        out[outpos++] = *data++;
 214                        inscnt++;
 215                        if (inscnt == 0x7f) {
 216                                out[outpos - inscnt - 1] = inscnt;
 217                                inscnt = 0;
 218                        }
 219                } else {
 220                        unsigned char *op;
 221
 222                        if (inscnt) {
 223                                out[outpos - inscnt - 1] = inscnt;
 224                                inscnt = 0;
 225                        }
 226
 227                        data += msize;
 228                        op = out + outpos++;
 229                        i = 0x80;
 230
 231                        if (moff & 0xff) { out[outpos++] = moff; i |= 0x01; }
 232                        moff >>= 8;
 233                        if (moff & 0xff) { out[outpos++] = moff; i |= 0x02; }
 234                        moff >>= 8;
 235                        if (moff & 0xff) { out[outpos++] = moff; i |= 0x04; }
 236                        moff >>= 8;
 237                        if (moff & 0xff) { out[outpos++] = moff; i |= 0x08; }
 238
 239                        if (msize & 0xff) { out[outpos++] = msize; i |= 0x10; }
 240                        msize >>= 8;
 241                        if (msize & 0xff) { out[outpos++] = msize; i |= 0x20; }
 242
 243                        *op = i;
 244                }
 245
 246                if (outpos >= outsize - MAX_OP_SIZE) {
 247                        void *tmp = out;
 248                        outsize = outsize * 3 / 2;
 249                        if (max_size && outsize >= max_size)
 250                                outsize = max_size + MAX_OP_SIZE + 1;
 251                        if (max_size && outpos > max_size)
 252                                out = NULL;
 253                        else
 254                                out = realloc(out, outsize);
 255                        if (!out) {
 256                                free(tmp);
 257                                free(hash);
 258                                return NULL;
 259                        }
 260                }
 261        }
 262
 263        if (inscnt)
 264                out[outpos - inscnt - 1] = inscnt;
 265
 266        free(hash);
 267        *delta_size = outpos;
 268        return out;
 269}