1 /*
2  * text-delta.c -- Internal text delta representation
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23 
24 
25 #include <assert.h>
26 #include <string.h>
27 
28 #include <apr_general.h>        /* for APR_INLINE */
29 #include <apr_md5.h>            /* for, um...MD5 stuff */
30 
31 #include "svn_delta.h"
32 #include "svn_io.h"
33 #include "svn_pools.h"
34 #include "svn_checksum.h"
35 
36 #include "delta.h"
37 
38 
39 /* Text delta stream descriptor. */
40 
41 struct svn_txdelta_stream_t {
42   /* Copied from parameters to svn_txdelta_stream_create. */
43   void *baton;
44   svn_txdelta_next_window_fn_t next_window;
45   svn_txdelta_md5_digest_fn_t md5_digest;
46 };
47 
48 /* Delta stream baton. */
49 struct txdelta_baton {
50   /* These are copied from parameters passed to svn_txdelta. */
51   svn_stream_t *source;
52   svn_stream_t *target;
53 
54   /* Private data */
55   svn_boolean_t more_source;    /* FALSE if source stream hit EOF. */
56   svn_boolean_t more;           /* TRUE if there are more data in the pool. */
57   svn_filesize_t pos;           /* Offset of next read in source file. */
58   char *buf;                    /* Buffer for input data. */
59 
60   svn_checksum_ctx_t *context;  /* If not NULL, the context for computing
61                                    the checksum. */
62   svn_checksum_t *checksum;     /* If non-NULL, the checksum of TARGET. */
63 
64   apr_pool_t *result_pool;      /* For results (e.g. checksum) */
65 };
66 
67 
68 /* Target-push stream descriptor. */
69 
70 struct tpush_baton {
71   /* These are copied from parameters passed to svn_txdelta_target_push. */
72   svn_stream_t *source;
73   svn_txdelta_window_handler_t wh;
74   void *whb;
75   apr_pool_t *pool;
76 
77   /* Private data */
78   char *buf;
79   svn_filesize_t source_offset;
80   apr_size_t source_len;
81   svn_boolean_t source_done;
82   apr_size_t target_len;
83 };
84 
85 
86 /* Text delta applicator.  */
87 
88 struct apply_baton {
89   /* These are copied from parameters passed to svn_txdelta_apply.  */
90   svn_stream_t *source;
91   svn_stream_t *target;
92 
93   /* Private data.  Between calls, SBUF contains the data from the
94    * last window's source view, as specified by SBUF_OFFSET and
95    * SBUF_LEN.  The contents of TBUF are not interesting between
96    * calls.  */
97   apr_pool_t *pool;             /* Pool to allocate data from */
98   char *sbuf;                   /* Source buffer */
99   apr_size_t sbuf_size;         /* Allocated source buffer space */
100   svn_filesize_t sbuf_offset;   /* Offset of SBUF data in source stream */
101   apr_size_t sbuf_len;          /* Length of SBUF data */
102   char *tbuf;                   /* Target buffer */
103   apr_size_t tbuf_size;         /* Allocated target buffer space */
104 
105   apr_md5_ctx_t md5_context;    /* Leads to result_digest below. */
106   unsigned char *result_digest; /* MD5 digest of resultant fulltext;
107                                    must point to at least APR_MD5_DIGESTSIZE
108                                    bytes of storage. */
109 
110   const char *error_info;       /* Optional extra info for error returns. */
111 };
112 
113 
114 
115 svn_txdelta_window_t *
svn_txdelta__make_window(const svn_txdelta__ops_baton_t * build_baton,apr_pool_t * pool)116 svn_txdelta__make_window(const svn_txdelta__ops_baton_t *build_baton,
117                          apr_pool_t *pool)
118 {
119   svn_txdelta_window_t *window;
120   svn_string_t *new_data = apr_palloc(pool, sizeof(*new_data));
121 
122   window = apr_palloc(pool, sizeof(*window));
123   window->sview_offset = 0;
124   window->sview_len = 0;
125   window->tview_len = 0;
126 
127   window->num_ops = build_baton->num_ops;
128   window->src_ops = build_baton->src_ops;
129   window->ops = build_baton->ops;
130 
131   /* just copy the fields over, rather than alloc/copying into a whole new
132      svn_string_t structure. */
133   /* ### would be much nicer if window->new_data were not a ptr... */
134   new_data->data = build_baton->new_data->data;
135   new_data->len = build_baton->new_data->len;
136   window->new_data = new_data;
137 
138   return window;
139 }
140 
141 
142 /* Compute and return a delta window using the xdelta algorithm on
143    DATA, which contains SOURCE_LEN bytes of source data and TARGET_LEN
144    bytes of target data.  SOURCE_OFFSET gives the offset of the source
145    data, and is simply copied into the window's sview_offset field. */
146 static svn_txdelta_window_t *
compute_window(const char * data,apr_size_t source_len,apr_size_t target_len,svn_filesize_t source_offset,apr_pool_t * pool)147 compute_window(const char *data, apr_size_t source_len, apr_size_t target_len,
148                svn_filesize_t source_offset, apr_pool_t *pool)
149 {
150   svn_txdelta__ops_baton_t build_baton = { 0 };
151   svn_txdelta_window_t *window;
152 
153   /* Compute the delta operations. */
154   build_baton.new_data = svn_stringbuf_create_empty(pool);
155 
156   if (source_len == 0)
157     svn_txdelta__insert_op(&build_baton, svn_txdelta_new, 0, target_len, data,
158                            pool);
159   else
160     svn_txdelta__xdelta(&build_baton, data, source_len, target_len, pool);
161 
162   /* Create and return the delta window. */
163   window = svn_txdelta__make_window(&build_baton, pool);
164   window->sview_offset = source_offset;
165   window->sview_len = source_len;
166   window->tview_len = target_len;
167   return window;
168 }
169 
170 
171 
172 svn_txdelta_window_t *
svn_txdelta_window_dup(const svn_txdelta_window_t * window,apr_pool_t * pool)173 svn_txdelta_window_dup(const svn_txdelta_window_t *window,
174                        apr_pool_t *pool)
175 {
176   svn_txdelta__ops_baton_t build_baton = { 0 };
177   svn_txdelta_window_t *new_window;
178   const apr_size_t ops_size = (window->num_ops * sizeof(*build_baton.ops));
179 
180   build_baton.num_ops = window->num_ops;
181   build_baton.src_ops = window->src_ops;
182   build_baton.ops_size = window->num_ops;
183   build_baton.ops = apr_palloc(pool, ops_size);
184   memcpy(build_baton.ops, window->ops, ops_size);
185   build_baton.new_data =
186     svn_stringbuf_create_from_string(window->new_data, pool);
187 
188   new_window = svn_txdelta__make_window(&build_baton, pool);
189   new_window->sview_offset = window->sview_offset;
190   new_window->sview_len = window->sview_len;
191   new_window->tview_len = window->tview_len;
192   return new_window;
193 }
194 
195 /* This is a private interlibrary compatibility wrapper. */
196 svn_txdelta_window_t *
197 svn_txdelta__copy_window(const svn_txdelta_window_t *window,
198                          apr_pool_t *pool);
199 svn_txdelta_window_t *
svn_txdelta__copy_window(const svn_txdelta_window_t * window,apr_pool_t * pool)200 svn_txdelta__copy_window(const svn_txdelta_window_t *window,
201                          apr_pool_t *pool)
202 {
203   return svn_txdelta_window_dup(window, pool);
204 }
205 
206 
207 /* Insert a delta op into a delta window. */
208 
209 void
svn_txdelta__insert_op(svn_txdelta__ops_baton_t * build_baton,enum svn_delta_action opcode,apr_size_t offset,apr_size_t length,const char * new_data,apr_pool_t * pool)210 svn_txdelta__insert_op(svn_txdelta__ops_baton_t *build_baton,
211                        enum svn_delta_action opcode,
212                        apr_size_t offset,
213                        apr_size_t length,
214                        const char *new_data,
215                        apr_pool_t *pool)
216 {
217   svn_txdelta_op_t *op;
218 
219   /* Check if this op can be merged with the previous op. The delta
220      combiner sometimes generates such ops, and this is the obvious
221      place to make the check. */
222   if (build_baton->num_ops > 0)
223     {
224       op = &build_baton->ops[build_baton->num_ops - 1];
225       if (op->action_code == opcode
226           && (opcode == svn_txdelta_new
227               || op->offset + op->length == offset))
228         {
229           op->length += length;
230           if (opcode == svn_txdelta_new)
231             svn_stringbuf_appendbytes(build_baton->new_data,
232                                       new_data, length);
233           return;
234         }
235     }
236 
237   /* Create space for the new op. */
238   if (build_baton->num_ops == build_baton->ops_size)
239     {
240       svn_txdelta_op_t *const old_ops = build_baton->ops;
241       int const new_ops_size = (build_baton->ops_size == 0
242                                 ? 16 : 2 * build_baton->ops_size);
243       build_baton->ops =
244         apr_palloc(pool, new_ops_size * sizeof(*build_baton->ops));
245 
246       /* Copy any existing ops into the new array */
247       if (old_ops)
248         memcpy(build_baton->ops, old_ops,
249                build_baton->ops_size * sizeof(*build_baton->ops));
250       build_baton->ops_size = new_ops_size;
251     }
252 
253   /* Insert the op. svn_delta_source and svn_delta_target are
254      just inserted. For svn_delta_new, the new data must be
255      copied into the window. */
256   op = &build_baton->ops[build_baton->num_ops];
257   switch (opcode)
258     {
259     case svn_txdelta_source:
260       ++build_baton->src_ops;
261       /*** FALLTHRU ***/
262     case svn_txdelta_target:
263       op->action_code = opcode;
264       op->offset = offset;
265       op->length = length;
266       break;
267     case svn_txdelta_new:
268       op->action_code = opcode;
269       op->offset = build_baton->new_data->len;
270       op->length = length;
271       svn_stringbuf_appendbytes(build_baton->new_data, new_data, length);
272       break;
273     default:
274       assert(!"unknown delta op.");
275     }
276 
277   ++build_baton->num_ops;
278 }
279 
280 apr_size_t
svn_txdelta__remove_copy(svn_txdelta__ops_baton_t * build_baton,apr_size_t max_len)281 svn_txdelta__remove_copy(svn_txdelta__ops_baton_t *build_baton,
282                          apr_size_t max_len)
283 {
284   svn_txdelta_op_t *op;
285   apr_size_t len = 0;
286 
287   /* remove ops back to front */
288   while (build_baton->num_ops > 0)
289     {
290       op = &build_baton->ops[build_baton->num_ops-1];
291 
292       /*  we can't modify svn_txdelta_target ops -> stop there */
293       if (op->action_code == svn_txdelta_target)
294         break;
295 
296       /*  handle the case that we cannot remove the op entirely */
297       if (op->length + len > max_len)
298         {
299           /* truncate only insertions. Copies don't benefit
300              from being truncated. */
301           if (op->action_code == svn_txdelta_new)
302             {
303                build_baton->new_data->len -= max_len - len;
304                op->length -= max_len - len;
305                len = max_len;
306             }
307 
308           break;
309         }
310 
311       /* drop the op entirely */
312       if (op->action_code == svn_txdelta_new)
313         build_baton->new_data->len -= op->length;
314 
315       len += op->length;
316       --build_baton->num_ops;
317     }
318 
319   return len;
320 }
321 
322 
323 
324 /* Generic delta stream functions. */
325 
326 svn_txdelta_stream_t *
svn_txdelta_stream_create(void * baton,svn_txdelta_next_window_fn_t next_window,svn_txdelta_md5_digest_fn_t md5_digest,apr_pool_t * pool)327 svn_txdelta_stream_create(void *baton,
328                           svn_txdelta_next_window_fn_t next_window,
329                           svn_txdelta_md5_digest_fn_t md5_digest,
330                           apr_pool_t *pool)
331 {
332   svn_txdelta_stream_t *stream = apr_palloc(pool, sizeof(*stream));
333 
334   stream->baton = baton;
335   stream->next_window = next_window;
336   stream->md5_digest = md5_digest;
337 
338   return stream;
339 }
340 
341 svn_error_t *
svn_txdelta_next_window(svn_txdelta_window_t ** window,svn_txdelta_stream_t * stream,apr_pool_t * pool)342 svn_txdelta_next_window(svn_txdelta_window_t **window,
343                         svn_txdelta_stream_t *stream,
344                         apr_pool_t *pool)
345 {
346   return stream->next_window(window, stream->baton, pool);
347 }
348 
349 const unsigned char *
svn_txdelta_md5_digest(svn_txdelta_stream_t * stream)350 svn_txdelta_md5_digest(svn_txdelta_stream_t *stream)
351 {
352   return stream->md5_digest(stream->baton);
353 }
354 
355 
356 
357 static svn_error_t *
txdelta_next_window(svn_txdelta_window_t ** window,void * baton,apr_pool_t * pool)358 txdelta_next_window(svn_txdelta_window_t **window,
359                     void *baton,
360                     apr_pool_t *pool)
361 {
362   struct txdelta_baton *b = baton;
363   apr_size_t source_len = SVN_DELTA_WINDOW_SIZE;
364   apr_size_t target_len = SVN_DELTA_WINDOW_SIZE;
365 
366   /* Read the source stream. */
367   if (b->more_source)
368     {
369       SVN_ERR(svn_stream_read_full(b->source, b->buf, &source_len));
370       b->more_source = (source_len == SVN_DELTA_WINDOW_SIZE);
371     }
372   else
373     source_len = 0;
374 
375   /* Read the target stream. */
376   SVN_ERR(svn_stream_read_full(b->target, b->buf + source_len, &target_len));
377   b->pos += source_len;
378 
379   if (target_len == 0)
380     {
381       /* No target data?  We're done; return the final window. */
382       if (b->context != NULL)
383         SVN_ERR(svn_checksum_final(&b->checksum, b->context, b->result_pool));
384 
385       *window = NULL;
386       b->more = FALSE;
387       return SVN_NO_ERROR;
388     }
389   else if (b->context != NULL)
390     SVN_ERR(svn_checksum_update(b->context, b->buf + source_len, target_len));
391 
392   *window = compute_window(b->buf, source_len, target_len,
393                            b->pos - source_len, pool);
394 
395   /* That's it. */
396   return SVN_NO_ERROR;
397 }
398 
399 
400 static const unsigned char *
txdelta_md5_digest(void * baton)401 txdelta_md5_digest(void *baton)
402 {
403   struct txdelta_baton *b = baton;
404   /* If there are more windows for this stream, the digest has not yet
405      been calculated.  */
406   if (b->more)
407     return NULL;
408 
409   /* If checksumming has not been activated, there will be no digest. */
410   if (b->context == NULL)
411     return NULL;
412 
413   /* The checksum should be there. */
414   return b->checksum->digest;
415 }
416 
417 
418 svn_error_t *
svn_txdelta_run(svn_stream_t * source,svn_stream_t * target,svn_txdelta_window_handler_t handler,void * handler_baton,svn_checksum_kind_t checksum_kind,svn_checksum_t ** checksum,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)419 svn_txdelta_run(svn_stream_t *source,
420                 svn_stream_t *target,
421                 svn_txdelta_window_handler_t handler,
422                 void *handler_baton,
423                 svn_checksum_kind_t checksum_kind,
424                 svn_checksum_t **checksum,
425                 svn_cancel_func_t cancel_func,
426                 void *cancel_baton,
427                 apr_pool_t *result_pool,
428                 apr_pool_t *scratch_pool)
429 {
430   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
431   struct txdelta_baton tb = { 0 };
432   svn_txdelta_window_t *window;
433 
434   tb.source = source;
435   tb.target = target;
436   tb.more_source = TRUE;
437   tb.more = TRUE;
438   tb.pos = 0;
439   tb.buf = apr_palloc(scratch_pool, 2 * SVN_DELTA_WINDOW_SIZE);
440   tb.result_pool = result_pool;
441 
442   if (checksum != NULL)
443     tb.context = svn_checksum_ctx_create(checksum_kind, scratch_pool);
444 
445   do
446     {
447       /* free the window (if any) */
448       svn_pool_clear(iterpool);
449 
450       /* read in a single delta window */
451       SVN_ERR(txdelta_next_window(&window, &tb, iterpool));
452 
453       /* shove it at the handler */
454       SVN_ERR((*handler)(window, handler_baton));
455 
456       if (cancel_func)
457         SVN_ERR(cancel_func(cancel_baton));
458     }
459   while (window != NULL);
460 
461   svn_pool_destroy(iterpool);
462 
463   if (checksum != NULL)
464     *checksum = tb.checksum;  /* should be there! */
465 
466   return SVN_NO_ERROR;
467 }
468 
469 
470 void
svn_txdelta2(svn_txdelta_stream_t ** stream,svn_stream_t * source,svn_stream_t * target,svn_boolean_t calculate_checksum,apr_pool_t * pool)471 svn_txdelta2(svn_txdelta_stream_t **stream,
472              svn_stream_t *source,
473              svn_stream_t *target,
474              svn_boolean_t calculate_checksum,
475              apr_pool_t *pool)
476 {
477   struct txdelta_baton *b = apr_pcalloc(pool, sizeof(*b));
478 
479   b->source = source;
480   b->target = target;
481   b->more_source = TRUE;
482   b->more = TRUE;
483   b->buf = apr_palloc(pool, 2 * SVN_DELTA_WINDOW_SIZE);
484   b->context = calculate_checksum
485              ? svn_checksum_ctx_create(svn_checksum_md5, pool)
486              : NULL;
487   b->result_pool = pool;
488 
489   *stream = svn_txdelta_stream_create(b, txdelta_next_window,
490                                       txdelta_md5_digest, pool);
491 }
492 
493 void
svn_txdelta(svn_txdelta_stream_t ** stream,svn_stream_t * source,svn_stream_t * target,apr_pool_t * pool)494 svn_txdelta(svn_txdelta_stream_t **stream,
495             svn_stream_t *source,
496             svn_stream_t *target,
497             apr_pool_t *pool)
498 {
499   svn_txdelta2(stream, source, target, TRUE, pool);
500 }
501 
502 
503 
504 /* Functions for implementing a "target push" delta. */
505 
506 /* This is the write handler for a target-push delta stream.  It reads
507  * source data, buffers target data, and fires off delta windows when
508  * the target data buffer is full. */
509 static svn_error_t *
tpush_write_handler(void * baton,const char * data,apr_size_t * len)510 tpush_write_handler(void *baton, const char *data, apr_size_t *len)
511 {
512   struct tpush_baton *tb = baton;
513   apr_size_t chunk_len, data_len = *len;
514   apr_pool_t *pool = svn_pool_create(tb->pool);
515   svn_txdelta_window_t *window;
516 
517   while (data_len > 0)
518     {
519       svn_pool_clear(pool);
520 
521       /* Make sure we're all full up on source data, if possible. */
522       if (tb->source_len == 0 && !tb->source_done)
523         {
524           tb->source_len = SVN_DELTA_WINDOW_SIZE;
525           SVN_ERR(svn_stream_read_full(tb->source, tb->buf, &tb->source_len));
526           if (tb->source_len < SVN_DELTA_WINDOW_SIZE)
527             tb->source_done = TRUE;
528         }
529 
530       /* Copy in the target data, up to SVN_DELTA_WINDOW_SIZE. */
531       chunk_len = SVN_DELTA_WINDOW_SIZE - tb->target_len;
532       if (chunk_len > data_len)
533         chunk_len = data_len;
534       memcpy(tb->buf + tb->source_len + tb->target_len, data, chunk_len);
535       data += chunk_len;
536       data_len -= chunk_len;
537       tb->target_len += chunk_len;
538 
539       /* If we're full of target data, compute and fire off a window. */
540       if (tb->target_len == SVN_DELTA_WINDOW_SIZE)
541         {
542           window = compute_window(tb->buf, tb->source_len, tb->target_len,
543                                   tb->source_offset, pool);
544           SVN_ERR(tb->wh(window, tb->whb));
545           tb->source_offset += tb->source_len;
546           tb->source_len = 0;
547           tb->target_len = 0;
548         }
549     }
550 
551   svn_pool_destroy(pool);
552   return SVN_NO_ERROR;
553 }
554 
555 
556 /* This is the close handler for a target-push delta stream.  It sends
557  * a final window if there is any buffered target data, and then sends
558  * a NULL window signifying the end of the window stream. */
559 static svn_error_t *
tpush_close_handler(void * baton)560 tpush_close_handler(void *baton)
561 {
562   struct tpush_baton *tb = baton;
563   svn_txdelta_window_t *window;
564 
565   /* Send a final window if we have any residual target data. */
566   if (tb->target_len > 0)
567     {
568       window = compute_window(tb->buf, tb->source_len, tb->target_len,
569                               tb->source_offset, tb->pool);
570       SVN_ERR(tb->wh(window, tb->whb));
571     }
572 
573   /* Send a final NULL window signifying the end. */
574   return tb->wh(NULL, tb->whb);
575 }
576 
577 
578 svn_stream_t *
svn_txdelta_target_push(svn_txdelta_window_handler_t handler,void * handler_baton,svn_stream_t * source,apr_pool_t * pool)579 svn_txdelta_target_push(svn_txdelta_window_handler_t handler,
580                         void *handler_baton, svn_stream_t *source,
581                         apr_pool_t *pool)
582 {
583   struct tpush_baton *tb;
584   svn_stream_t *stream;
585 
586   /* Initialize baton. */
587   tb = apr_palloc(pool, sizeof(*tb));
588   tb->source = source;
589   tb->wh = handler;
590   tb->whb = handler_baton;
591   tb->pool = pool;
592   tb->buf = apr_palloc(pool, 2 * SVN_DELTA_WINDOW_SIZE);
593   tb->source_offset = 0;
594   tb->source_len = 0;
595   tb->source_done = FALSE;
596   tb->target_len = 0;
597 
598   /* Create and return writable stream. */
599   stream = svn_stream_create(tb, pool);
600   svn_stream_set_write(stream, tpush_write_handler);
601   svn_stream_set_close(stream, tpush_close_handler);
602   return stream;
603 }
604 
605 
606 
607 /* Functions for applying deltas.  */
608 
609 /* Ensure that BUF has enough space for VIEW_LEN bytes.  */
610 static APR_INLINE svn_error_t *
size_buffer(char ** buf,apr_size_t * buf_size,apr_size_t view_len,apr_pool_t * pool)611 size_buffer(char **buf, apr_size_t *buf_size,
612             apr_size_t view_len, apr_pool_t *pool)
613 {
614   if (view_len > *buf_size)
615     {
616       *buf_size *= 2;
617       if (*buf_size < view_len)
618         *buf_size = view_len;
619       SVN_ERR_ASSERT(APR_ALIGN_DEFAULT(*buf_size) >= *buf_size);
620       *buf = apr_palloc(pool, *buf_size);
621     }
622 
623   return SVN_NO_ERROR;
624 }
625 
626 /* Copy LEN bytes from SOURCE to TARGET.  Unlike memmove() or memcpy(),
627  * create repeating patterns if the source and target ranges overlap.
628  * Return a pointer to the first byte after the copied target range.  */
629 static APR_INLINE char *
patterning_copy(char * target,const char * source,apr_size_t len)630 patterning_copy(char *target, const char *source, apr_size_t len)
631 {
632   /* If the source and target overlap, repeat the overlapping pattern
633      in the target buffer. Always copy from the source buffer because
634      presumably it will be in the L1 cache after the first iteration
635      and doing this should avoid pipeline stalls due to write/read
636      dependencies. */
637   const apr_size_t overlap = target - source;
638   while (len > overlap)
639     {
640       memcpy(target, source, overlap);
641       target += overlap;
642       len -= overlap;
643     }
644 
645   /* Copy any remaining source pattern. */
646   if (len)
647     {
648       memcpy(target, source, len);
649       target += len;
650     }
651 
652   return target;
653 }
654 
655 void
svn_txdelta_apply_instructions(svn_txdelta_window_t * window,const char * sbuf,char * tbuf,apr_size_t * tlen)656 svn_txdelta_apply_instructions(svn_txdelta_window_t *window,
657                                const char *sbuf, char *tbuf,
658                                apr_size_t *tlen)
659 {
660   const svn_txdelta_op_t *op;
661   apr_size_t tpos = 0;
662 
663   /* Nothing to do for empty buffers.
664    * This check allows for NULL TBUF in that case. */
665   if (*tlen == 0)
666     return;
667 
668   for (op = window->ops; op < window->ops + window->num_ops; op++)
669     {
670       const apr_size_t buf_len = (op->length < *tlen - tpos
671                                   ? op->length : *tlen - tpos);
672 
673       /* Check some invariants common to all instructions.  */
674       assert(tpos + op->length <= window->tview_len);
675 
676       switch (op->action_code)
677         {
678         case svn_txdelta_source:
679           /* Copy from source area.  */
680           assert(sbuf);
681           assert(op->offset + op->length <= window->sview_len);
682           memcpy(tbuf + tpos, sbuf + op->offset, buf_len);
683           break;
684 
685         case svn_txdelta_target:
686           /* Copy from target area.  We can't use memcpy() or the like
687            * since we need a specific semantics for overlapping copies:
688            * they must result in repeating patterns.
689            * Note that most copies won't have overlapping source and
690            * target ranges (they are just a result of self-compressed
691            * data) but a small percentage will.  */
692           assert(op->offset < tpos);
693           patterning_copy(tbuf + tpos, tbuf + op->offset, buf_len);
694           break;
695 
696         case svn_txdelta_new:
697           /* Copy from window new area.  */
698           assert(op->offset + op->length <= window->new_data->len);
699           memcpy(tbuf + tpos,
700                  window->new_data->data + op->offset,
701                  buf_len);
702           break;
703 
704         default:
705           assert(!"Invalid delta instruction code");
706         }
707 
708       tpos += op->length;
709       if (tpos >= *tlen)
710         return;                 /* The buffer is full. */
711     }
712 
713   /* Check that we produced the right amount of data.  */
714   assert(tpos == window->tview_len);
715   *tlen = tpos;
716 }
717 
718 /* Apply WINDOW to the streams given by APPL.  */
719 static svn_error_t *
apply_window(svn_txdelta_window_t * window,void * baton)720 apply_window(svn_txdelta_window_t *window, void *baton)
721 {
722   struct apply_baton *ab = (struct apply_baton *) baton;
723   apr_size_t len;
724   svn_error_t *err;
725 
726   if (window == NULL)
727     {
728       /* We're done; just clean up.  */
729       if (ab->result_digest)
730         apr_md5_final(ab->result_digest, &(ab->md5_context));
731 
732       err = svn_stream_close(ab->target);
733       svn_pool_destroy(ab->pool);
734 
735       return err;
736     }
737 
738   /* Make sure the source view didn't slide backwards.  */
739   SVN_ERR_ASSERT(window->sview_len == 0
740                  || (window->sview_offset >= ab->sbuf_offset
741                      && (window->sview_offset + window->sview_len
742                          >= ab->sbuf_offset + ab->sbuf_len)));
743 
744   /* Make sure there's enough room in the target buffer.  */
745   SVN_ERR(size_buffer(&ab->tbuf, &ab->tbuf_size, window->tview_len, ab->pool));
746 
747   /* Prepare the source buffer for reading from the input stream.  */
748   if (window->sview_offset != ab->sbuf_offset
749       || window->sview_len > ab->sbuf_size)
750     {
751       char *old_sbuf = ab->sbuf;
752 
753       /* Make sure there's enough room.  */
754       SVN_ERR(size_buffer(&ab->sbuf, &ab->sbuf_size, window->sview_len,
755               ab->pool));
756 
757       /* If the existing view overlaps with the new view, copy the
758        * overlap to the beginning of the new buffer.  */
759       if (  (apr_size_t)ab->sbuf_offset + ab->sbuf_len
760           > (apr_size_t)window->sview_offset)
761         {
762           apr_size_t start =
763             (apr_size_t)(window->sview_offset - ab->sbuf_offset);
764           memmove(ab->sbuf, old_sbuf + start, ab->sbuf_len - start);
765           ab->sbuf_len -= start;
766         }
767       else
768         ab->sbuf_len = 0;
769       ab->sbuf_offset = window->sview_offset;
770     }
771 
772   /* Read the remainder of the source view into the buffer.  */
773   if (ab->sbuf_len < window->sview_len)
774     {
775       len = window->sview_len - ab->sbuf_len;
776       err = svn_stream_read_full(ab->source, ab->sbuf + ab->sbuf_len, &len);
777       if (err == SVN_NO_ERROR && len != window->sview_len - ab->sbuf_len)
778         err = svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL,
779                                "Delta source ended unexpectedly");
780       if (err != SVN_NO_ERROR)
781         return err;
782       ab->sbuf_len = window->sview_len;
783     }
784 
785   /* Apply the window instructions to the source view to generate
786      the target view.  */
787   len = window->tview_len;
788   svn_txdelta_apply_instructions(window, ab->sbuf, ab->tbuf, &len);
789   SVN_ERR_ASSERT(len == window->tview_len);
790 
791   /* Write out the output. */
792 
793   /* Just update the context here. */
794   if (ab->result_digest)
795     apr_md5_update(&(ab->md5_context), ab->tbuf, len);
796 
797   return svn_stream_write(ab->target, ab->tbuf, &len);
798 }
799 
800 
801 void
svn_txdelta_apply(svn_stream_t * source,svn_stream_t * target,unsigned char * result_digest,const char * error_info,apr_pool_t * pool,svn_txdelta_window_handler_t * handler,void ** handler_baton)802 svn_txdelta_apply(svn_stream_t *source,
803                   svn_stream_t *target,
804                   unsigned char *result_digest,
805                   const char *error_info,
806                   apr_pool_t *pool,
807                   svn_txdelta_window_handler_t *handler,
808                   void **handler_baton)
809 {
810   apr_pool_t *subpool = svn_pool_create(pool);
811   struct apply_baton *ab;
812 
813   ab = apr_palloc(subpool, sizeof(*ab));
814   ab->source = source;
815   ab->target = target;
816   ab->pool = subpool;
817   ab->sbuf = NULL;
818   ab->sbuf_size = 0;
819   ab->sbuf_offset = 0;
820   ab->sbuf_len = 0;
821   ab->tbuf = NULL;
822   ab->tbuf_size = 0;
823   ab->result_digest = result_digest;
824 
825   if (result_digest)
826     apr_md5_init(&(ab->md5_context));
827 
828   if (error_info)
829     ab->error_info = apr_pstrdup(subpool, error_info);
830   else
831     ab->error_info = NULL;
832 
833   *handler = apply_window;
834   *handler_baton = ab;
835 }
836 
837 
838 
839 /* Convenience routines */
840 
841 svn_error_t *
svn_txdelta_send_string(const svn_string_t * string,svn_txdelta_window_handler_t handler,void * handler_baton,apr_pool_t * pool)842 svn_txdelta_send_string(const svn_string_t *string,
843                         svn_txdelta_window_handler_t handler,
844                         void *handler_baton,
845                         apr_pool_t *pool)
846 {
847   svn_txdelta_window_t window = { 0 };
848   svn_txdelta_op_t op;
849 
850   /* Build a single `new' op */
851   op.action_code = svn_txdelta_new;
852   op.offset = 0;
853   op.length = string->len;
854 
855   /* Build a single window containing a ptr to the string. */
856   window.tview_len = string->len;
857   window.num_ops = 1;
858   window.ops = &op;
859   window.new_data = string;
860 
861   /* Push the one window at the handler. */
862   SVN_ERR((*handler)(&window, handler_baton));
863 
864   /* Push a NULL at the handler, because we're done. */
865   return (*handler)(NULL, handler_baton);
866 }
867 
svn_txdelta_send_stream(svn_stream_t * stream,svn_txdelta_window_handler_t handler,void * handler_baton,unsigned char * digest,apr_pool_t * pool)868 svn_error_t *svn_txdelta_send_stream(svn_stream_t *stream,
869                                      svn_txdelta_window_handler_t handler,
870                                      void *handler_baton,
871                                      unsigned char *digest,
872                                      apr_pool_t *pool)
873 {
874   svn_txdelta_window_t delta_window = { 0 };
875   svn_txdelta_op_t delta_op;
876   svn_string_t window_data;
877   char read_buf[SVN__STREAM_CHUNK_SIZE + 1];
878   svn_checksum_ctx_t *md5_checksum_ctx;
879 
880   if (digest)
881     md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
882 
883   while (1)
884     {
885       apr_size_t read_len = SVN__STREAM_CHUNK_SIZE;
886 
887       SVN_ERR(svn_stream_read_full(stream, read_buf, &read_len));
888       if (read_len == 0)
889         break;
890 
891       window_data.data = read_buf;
892       window_data.len = read_len;
893 
894       delta_op.action_code = svn_txdelta_new;
895       delta_op.offset = 0;
896       delta_op.length = read_len;
897 
898       delta_window.tview_len = read_len;
899       delta_window.num_ops = 1;
900       delta_window.ops = &delta_op;
901       delta_window.new_data = &window_data;
902 
903       SVN_ERR(handler(&delta_window, handler_baton));
904 
905       if (digest)
906         SVN_ERR(svn_checksum_update(md5_checksum_ctx, read_buf, read_len));
907 
908       if (read_len < SVN__STREAM_CHUNK_SIZE)
909         break;
910     }
911   SVN_ERR(handler(NULL, handler_baton));
912 
913   if (digest)
914     {
915       svn_checksum_t *md5_checksum;
916 
917       SVN_ERR(svn_checksum_final(&md5_checksum, md5_checksum_ctx, pool));
918       memcpy(digest, md5_checksum->digest, APR_MD5_DIGESTSIZE);
919     }
920 
921   return SVN_NO_ERROR;
922 }
923 
svn_txdelta_send_txstream(svn_txdelta_stream_t * txstream,svn_txdelta_window_handler_t handler,void * handler_baton,apr_pool_t * pool)924 svn_error_t *svn_txdelta_send_txstream(svn_txdelta_stream_t *txstream,
925                                        svn_txdelta_window_handler_t handler,
926                                        void *handler_baton,
927                                        apr_pool_t *pool)
928 {
929   svn_txdelta_window_t *window;
930 
931   /* create a pool just for the windows */
932   apr_pool_t *wpool = svn_pool_create(pool);
933 
934   do
935     {
936       /* free the window (if any) */
937       svn_pool_clear(wpool);
938 
939       /* read in a single delta window */
940       SVN_ERR(svn_txdelta_next_window(&window, txstream, wpool));
941 
942       /* shove it at the handler */
943       SVN_ERR((*handler)(window, handler_baton));
944     }
945   while (window != NULL);
946 
947   svn_pool_destroy(wpool);
948 
949   return SVN_NO_ERROR;
950 }
951 
952 svn_error_t *
svn_txdelta_send_contents(const unsigned char * contents,apr_size_t len,svn_txdelta_window_handler_t handler,void * handler_baton,apr_pool_t * pool)953 svn_txdelta_send_contents(const unsigned char *contents,
954                           apr_size_t len,
955                           svn_txdelta_window_handler_t handler,
956                           void *handler_baton,
957                           apr_pool_t *pool)
958 {
959   svn_string_t new_data;
960   svn_txdelta_op_t op = { svn_txdelta_new, 0, 0 };
961   svn_txdelta_window_t window = { 0, 0, 0, 1, 0 };
962   window.ops = &op;
963   window.new_data = &new_data;
964 
965   /* send CONTENT as a series of max-sized windows */
966   while (len > 0)
967     {
968       /* stuff next chunk into the window */
969       window.tview_len = len < SVN_DELTA_WINDOW_SIZE
970                        ? len
971                        : SVN_DELTA_WINDOW_SIZE;
972       op.length = window.tview_len;
973       new_data.len = window.tview_len;
974       new_data.data = (const char*)contents;
975 
976       /* update remaining */
977       contents += window.tview_len;
978       len -= window.tview_len;
979 
980       /* shove it at the handler */
981       SVN_ERR((*handler)(&window, handler_baton));
982     }
983 
984   /* indicate end of stream */
985   SVN_ERR((*handler)(NULL, handler_baton));
986 
987   return SVN_NO_ERROR;
988 }
989 
990