diff-delta: fold two special tests into one plus cleanups
[git.git] / diff-delta.c
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 "delta.h"
23 #include "zlib.h"
24
25
26 /* block size: min = 16, max = 64k, power of 2 */
27 #define BLK_SIZE 16
28
29 #define MIN(a, b) ((a) < (b) ? (a) : (b))
30
31 #define GR_PRIME 0x9e370001
32 #define HASH(v, b) (((unsigned int)(v) * GR_PRIME) >> (32 - (b)))
33         
34 static unsigned int hashbits(unsigned int size)
35 {
36         unsigned int val = 1, bits = 0;
37         while (val < size && bits < 32) {
38                 val <<= 1;
39                 bits++;
40         }
41         return bits ? bits: 1;
42 }
43
44 typedef struct s_chanode {
45         struct s_chanode *next;
46         int icurr;
47 } chanode_t;
48
49 typedef struct s_chastore {
50         int isize, nsize;
51         chanode_t *ancur;
52 } chastore_t;
53
54 static void cha_init(chastore_t *cha, int isize, int icount)
55 {
56         cha->isize = isize;
57         cha->nsize = icount * isize;
58         cha->ancur = NULL;
59 }
60
61 static void *cha_alloc(chastore_t *cha)
62 {
63         chanode_t *ancur;
64         void *data;
65
66         ancur = cha->ancur;
67         if (!ancur || ancur->icurr == cha->nsize) {
68                 ancur = malloc(sizeof(chanode_t) + cha->nsize);
69                 if (!ancur)
70                         return NULL;
71                 ancur->icurr = 0;
72                 ancur->next = cha->ancur;
73                 cha->ancur = ancur;
74         }
75
76         data = (void *)ancur + sizeof(chanode_t) + ancur->icurr;
77         ancur->icurr += cha->isize;
78         return data;
79 }
80
81 static void cha_free(chastore_t *cha)
82 {
83         chanode_t *cur = cha->ancur;
84         while (cur) {
85                 chanode_t *tmp = cur;
86                 cur = cur->next;
87                 free(tmp);
88         }
89 }
90
91 typedef struct s_bdrecord {
92         struct s_bdrecord *next;
93         unsigned int fp;
94         const unsigned char *ptr;
95 } bdrecord_t;
96
97 typedef struct s_bdfile {
98         chastore_t cha;
99         unsigned int fphbits;
100         bdrecord_t **fphash;
101 } bdfile_t;
102
103 static int delta_prepare(const unsigned char *buf, int bufsize, bdfile_t *bdf)
104 {
105         unsigned int fphbits;
106         int i, hsize;
107         const unsigned char *data, *top;
108         bdrecord_t *brec;
109         bdrecord_t **fphash;
110
111         fphbits = hashbits(bufsize / BLK_SIZE + 1);
112         hsize = 1 << fphbits;
113         fphash = malloc(hsize * sizeof(bdrecord_t *));
114         if (!fphash)
115                 return -1;
116         for (i = 0; i < hsize; i++)
117                 fphash[i] = NULL;
118         cha_init(&bdf->cha, sizeof(bdrecord_t), hsize / 4 + 1);
119
120         top = buf + bufsize;
121         data = buf + (bufsize / BLK_SIZE) * BLK_SIZE;
122         if (data == top)
123                 data -= BLK_SIZE;
124
125         for ( ; data >= buf; data -= BLK_SIZE) {
126                 brec = cha_alloc(&bdf->cha);
127                 if (!brec) {
128                         cha_free(&bdf->cha);
129                         free(fphash);
130                         return -1;
131                 }
132                 brec->fp = adler32(0, data, MIN(BLK_SIZE, top - data));
133                 brec->ptr = data;
134                 i = HASH(brec->fp, fphbits);
135                 brec->next = fphash[i];
136                 fphash[i] = brec;
137         }
138
139         bdf->fphbits = fphbits;
140         bdf->fphash = fphash;
141
142         return 0;
143 }
144
145 static void delta_cleanup(bdfile_t *bdf)
146 {
147         free(bdf->fphash);
148         cha_free(&bdf->cha);
149 }
150
151 /* provide the size of the copy opcode given the block offset and size */
152 #define COPYOP_SIZE(o, s) \
153     (!!(o & 0xff) + !!(o & 0xff00) + !!(o & 0xff0000) + !!(o & 0xff000000) + \
154      !!(s & 0xff) + !!(s & 0xff00) + 1)
155
156 /* the maximum size for any opcode */
157 #define MAX_OP_SIZE COPYOP_SIZE(0xffffffff, 0xffffffff)
158
159 void *diff_delta(void *from_buf, unsigned long from_size,
160                  void *to_buf, unsigned long to_size,
161                  unsigned long *delta_size,
162                  unsigned long max_size)
163 {
164         unsigned int i, outpos, outsize, inscnt, csize, msize, moff;
165         unsigned int fp;
166         const unsigned char *ref_data, *ref_top, *data, *top, *ptr1, *ptr2;
167         unsigned char *out, *orig;
168         bdrecord_t *brec;
169         bdfile_t bdf;
170
171         if (!from_size || !to_size || delta_prepare(from_buf, from_size, &bdf))
172                 return NULL;
173         
174         outpos = 0;
175         outsize = 8192;
176         if (max_size && outsize >= max_size)
177                 outsize = max_size + MAX_OP_SIZE + 1;
178         out = malloc(outsize);
179         if (!out) {
180                 delta_cleanup(&bdf);
181                 return NULL;
182         }
183
184         ref_data = from_buf;
185         ref_top = from_buf + from_size;
186         data = to_buf;
187         top = to_buf + to_size;
188
189         /* store reference buffer size */
190         out[outpos++] = from_size;
191         from_size >>= 7;
192         while (from_size) {
193                 out[outpos - 1] |= 0x80;
194                 out[outpos++] = from_size;
195                 from_size >>= 7;
196         }
197
198         /* store target buffer size */
199         out[outpos++] = to_size;
200         to_size >>= 7;
201         while (to_size) {
202                 out[outpos - 1] |= 0x80;
203                 out[outpos++] = to_size;
204                 to_size >>= 7;
205         }
206
207         inscnt = 0;
208         moff = 0;
209         while (data < top) {
210                 msize = 0;
211                 fp = adler32(0, data, MIN(top - data, BLK_SIZE));
212                 i = HASH(fp, bdf.fphbits);
213                 for (brec = bdf.fphash[i]; brec; brec = brec->next) {
214                         if (brec->fp == fp) {
215                                 csize = ref_top - brec->ptr;
216                                 if (csize > top - data)
217                                         csize = top - data;
218                                 for (ptr1 = brec->ptr, ptr2 = data; 
219                                      csize && *ptr1 == *ptr2;
220                                      csize--, ptr1++, ptr2++);
221
222                                 csize = ptr1 - brec->ptr;
223                                 if (csize > msize) {
224                                         moff = brec->ptr - ref_data;
225                                         msize = csize;
226                                         if (msize >= 0x10000) {
227                                                 msize = 0x10000;
228                                                 break;
229                                         }
230                                 }
231                         }
232                 }
233
234                 if (!msize || msize < COPYOP_SIZE(moff, msize)) {
235                         if (!inscnt)
236                                 outpos++;
237                         out[outpos++] = *data++;
238                         inscnt++;
239                         if (inscnt == 0x7f) {
240                                 out[outpos - inscnt - 1] = inscnt;
241                                 inscnt = 0;
242                         }
243                 } else {
244                         if (inscnt) {
245                                 out[outpos - inscnt - 1] = inscnt;
246                                 inscnt = 0;
247                         }
248
249                         data += msize;
250                         orig = out + outpos++;
251                         i = 0x80;
252
253                         if (moff & 0xff) { out[outpos++] = moff; i |= 0x01; }
254                         moff >>= 8;
255                         if (moff & 0xff) { out[outpos++] = moff; i |= 0x02; }
256                         moff >>= 8;
257                         if (moff & 0xff) { out[outpos++] = moff; i |= 0x04; }
258                         moff >>= 8;
259                         if (moff & 0xff) { out[outpos++] = moff; i |= 0x08; }
260
261                         if (msize & 0xff) { out[outpos++] = msize; i |= 0x10; }
262                         msize >>= 8;
263                         if (msize & 0xff) { out[outpos++] = msize; i |= 0x20; }
264
265                         *orig = i;
266                 }
267
268                 if (outpos >= outsize - MAX_OP_SIZE) {
269                         void *tmp = out;
270                         outsize = outsize * 3 / 2;
271                         if (max_size && outsize >= max_size)
272                                 outsize = max_size + MAX_OP_SIZE + 1;
273                         if (max_size && outpos > max_size)
274                                 out = NULL;
275                         else
276                                 out = realloc(out, outsize);
277                         if (!out) {
278                                 free(tmp);
279                                 delta_cleanup(&bdf);
280                                 return NULL;
281                         }
282                 }
283         }
284
285         if (inscnt)
286                 out[outpos - inscnt - 1] = inscnt;
287
288         delta_cleanup(&bdf);
289         *delta_size = outpos;
290         return out;
291 }