text_delta.c revision 289180
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
41struct 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. */
49struct 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
70struct 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
88struct 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
115svn_txdelta_window_t *
116svn_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. */
146static svn_txdelta_window_t *
147compute_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
172svn_txdelta_window_t *
173svn_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. */
196svn_txdelta_window_t *
197svn_txdelta__copy_window(const svn_txdelta_window_t *window,
198                         apr_pool_t *pool);
199svn_txdelta_window_t *
200svn_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
209void
210svn_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
280apr_size_t
281svn_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
326svn_txdelta_stream_t *
327svn_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
341svn_error_t *
342svn_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
349const unsigned char *
350svn_txdelta_md5_digest(svn_txdelta_stream_t *stream)
351{
352  return stream->md5_digest(stream->baton);
353}
354
355
356
357static svn_error_t *
358txdelta_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
400static const unsigned char *
401txdelta_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
418svn_error_t *
419svn_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
470void
471svn_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
493void
494svn_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. */
509static svn_error_t *
510tpush_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. */
559static svn_error_t *
560tpush_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
578svn_stream_t *
579svn_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.  */
610static APR_INLINE svn_error_t *
611size_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.  */
629static APR_INLINE char *
630patterning_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
655void
656svn_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.  */
719static svn_error_t *
720apply_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
801void
802svn_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
841svn_error_t *
842svn_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
868svn_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
924svn_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
952svn_error_t *
953svn_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