2 * text-delta.c -- Internal text delta representation
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
28 #include <apr_general.h> /* for APR_INLINE */
29 #include <apr_md5.h> /* for, um...MD5 stuff */
31 #include "svn_delta.h"
33 #include "svn_pools.h"
34 #include "svn_checksum.h"
39 /* Text delta stream descriptor. */
41 struct svn_txdelta_stream_t {
42 /* Copied from parameters to svn_txdelta_stream_create. */
44 svn_txdelta_next_window_fn_t next_window;
45 svn_txdelta_md5_digest_fn_t md5_digest;
48 /* Delta stream baton. */
49 struct txdelta_baton {
50 /* These are copied from parameters passed to svn_txdelta. */
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. */
60 svn_checksum_ctx_t *context; /* If not NULL, the context for computing
62 svn_checksum_t *checksum; /* If non-NULL, the checksum of TARGET. */
64 apr_pool_t *result_pool; /* For results (e.g. checksum) */
68 /* Target-push stream descriptor. */
71 /* These are copied from parameters passed to svn_txdelta_target_push. */
73 svn_txdelta_window_handler_t wh;
79 svn_filesize_t source_offset;
80 apr_size_t source_len;
81 svn_boolean_t source_done;
82 apr_size_t target_len;
86 /* Text delta applicator. */
89 /* These are copied from parameters passed to svn_txdelta_apply. */
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
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 */
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
110 const char *error_info; /* Optional extra info for error returns. */
115 svn_txdelta_window_t *
116 svn_txdelta__make_window(const svn_txdelta__ops_baton_t *build_baton,
119 svn_txdelta_window_t *window;
120 svn_string_t *new_data = apr_palloc(pool, sizeof(*new_data));
122 window = apr_palloc(pool, sizeof(*window));
123 window->sview_offset = 0;
124 window->sview_len = 0;
125 window->tview_len = 0;
127 window->num_ops = build_baton->num_ops;
128 window->src_ops = build_baton->src_ops;
129 window->ops = build_baton->ops;
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;
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 *
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)
150 svn_txdelta__ops_baton_t build_baton = { 0 };
151 svn_txdelta_window_t *window;
153 /* Compute the delta operations. */
154 build_baton.new_data = svn_stringbuf_create_empty(pool);
157 svn_txdelta__insert_op(&build_baton, svn_txdelta_new, 0, target_len, data,
160 svn_txdelta__xdelta(&build_baton, data, source_len, target_len, pool);
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;
172 svn_txdelta_window_t *
173 svn_txdelta_window_dup(const svn_txdelta_window_t *window,
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));
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);
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;
195 /* This is a private interlibrary compatibility wrapper. */
196 svn_txdelta_window_t *
197 svn_txdelta__copy_window(const svn_txdelta_window_t *window,
199 svn_txdelta_window_t *
200 svn_txdelta__copy_window(const svn_txdelta_window_t *window,
203 return svn_txdelta_window_dup(window, pool);
207 /* Insert a delta op into a delta window. */
210 svn_txdelta__insert_op(svn_txdelta__ops_baton_t *build_baton,
211 enum svn_delta_action opcode,
214 const char *new_data,
217 svn_txdelta_op_t *op;
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)
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))
229 op->length += length;
230 if (opcode == svn_txdelta_new)
231 svn_stringbuf_appendbytes(build_baton->new_data,
237 /* Create space for the new op. */
238 if (build_baton->num_ops == build_baton->ops_size)
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);
244 apr_palloc(pool, new_ops_size * sizeof(*build_baton->ops));
246 /* Copy any existing ops into the new array */
248 memcpy(build_baton->ops, old_ops,
249 build_baton->ops_size * sizeof(*build_baton->ops));
250 build_baton->ops_size = new_ops_size;
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];
259 case svn_txdelta_source:
260 ++build_baton->src_ops;
262 case svn_txdelta_target:
263 op->action_code = opcode;
267 case svn_txdelta_new:
268 op->action_code = opcode;
269 op->offset = build_baton->new_data->len;
271 svn_stringbuf_appendbytes(build_baton->new_data, new_data, length);
274 assert(!"unknown delta op.");
277 ++build_baton->num_ops;
281 svn_txdelta__remove_copy(svn_txdelta__ops_baton_t *build_baton,
284 svn_txdelta_op_t *op;
287 /* remove ops back to front */
288 while (build_baton->num_ops > 0)
290 op = &build_baton->ops[build_baton->num_ops-1];
292 /* we can't modify svn_txdelta_target ops -> stop there */
293 if (op->action_code == svn_txdelta_target)
296 /* handle the case that we cannot remove the op entirely */
297 if (op->length + len > max_len)
299 /* truncate only insertions. Copies don't benefit
300 from being truncated. */
301 if (op->action_code == svn_txdelta_new)
303 build_baton->new_data->len -= max_len - len;
304 op->length -= max_len - len;
311 /* drop the op entirely */
312 if (op->action_code == svn_txdelta_new)
313 build_baton->new_data->len -= op->length;
316 --build_baton->num_ops;
324 /* Generic delta stream functions. */
326 svn_txdelta_stream_t *
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,
332 svn_txdelta_stream_t *stream = apr_palloc(pool, sizeof(*stream));
334 stream->baton = baton;
335 stream->next_window = next_window;
336 stream->md5_digest = md5_digest;
342 svn_txdelta_next_window(svn_txdelta_window_t **window,
343 svn_txdelta_stream_t *stream,
346 return stream->next_window(window, stream->baton, pool);
349 const unsigned char *
350 svn_txdelta_md5_digest(svn_txdelta_stream_t *stream)
352 return stream->md5_digest(stream->baton);
358 txdelta_next_window(svn_txdelta_window_t **window,
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;
366 /* Read the source stream. */
369 SVN_ERR(svn_stream_read(b->source, b->buf, &source_len));
370 b->more_source = (source_len == SVN_DELTA_WINDOW_SIZE);
375 /* Read the target stream. */
376 SVN_ERR(svn_stream_read(b->target, b->buf + source_len, &target_len));
377 b->pos += source_len;
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));
389 else if (b->context != NULL)
390 SVN_ERR(svn_checksum_update(b->context, b->buf + source_len, target_len));
392 *window = compute_window(b->buf, source_len, target_len,
393 b->pos - source_len, pool);
400 static const unsigned char *
401 txdelta_md5_digest(void *baton)
403 struct txdelta_baton *b = baton;
404 /* If there are more windows for this stream, the digest has not yet
409 /* If checksumming has not been activated, there will be no digest. */
410 if (b->context == NULL)
413 /* The checksum should be there. */
414 return b->checksum->digest;
419 svn_txdelta_run(svn_stream_t *source,
420 svn_stream_t *target,
421 svn_txdelta_window_handler_t handler,
423 svn_checksum_kind_t checksum_kind,
424 svn_checksum_t **checksum,
425 svn_cancel_func_t cancel_func,
427 apr_pool_t *result_pool,
428 apr_pool_t *scratch_pool)
430 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
431 struct txdelta_baton tb = { 0 };
432 svn_txdelta_window_t *window;
436 tb.more_source = TRUE;
439 tb.buf = apr_palloc(scratch_pool, 2 * SVN_DELTA_WINDOW_SIZE);
440 tb.result_pool = result_pool;
442 if (checksum != NULL)
443 tb.context = svn_checksum_ctx_create(checksum_kind, scratch_pool);
447 /* free the window (if any) */
448 svn_pool_clear(iterpool);
450 /* read in a single delta window */
451 SVN_ERR(txdelta_next_window(&window, &tb, iterpool));
453 /* shove it at the handler */
454 SVN_ERR((*handler)(window, handler_baton));
457 SVN_ERR(cancel_func(cancel_baton));
459 while (window != NULL);
461 svn_pool_destroy(iterpool);
463 if (checksum != NULL)
464 *checksum = tb.checksum; /* should be there! */
471 svn_txdelta2(svn_txdelta_stream_t **stream,
472 svn_stream_t *source,
473 svn_stream_t *target,
474 svn_boolean_t calculate_checksum,
477 struct txdelta_baton *b = apr_pcalloc(pool, sizeof(*b));
481 b->more_source = 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)
487 b->result_pool = pool;
489 *stream = svn_txdelta_stream_create(b, txdelta_next_window,
490 txdelta_md5_digest, pool);
494 svn_txdelta(svn_txdelta_stream_t **stream,
495 svn_stream_t *source,
496 svn_stream_t *target,
499 svn_txdelta2(stream, source, target, TRUE, pool);
504 /* Functions for implementing a "target push" delta. */
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. */
510 tpush_write_handler(void *baton, const char *data, apr_size_t *len)
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;
519 svn_pool_clear(pool);
521 /* Make sure we're all full up on source data, if possible. */
522 if (tb->source_len == 0 && !tb->source_done)
524 tb->source_len = SVN_DELTA_WINDOW_SIZE;
525 SVN_ERR(svn_stream_read(tb->source, tb->buf, &tb->source_len));
526 if (tb->source_len < SVN_DELTA_WINDOW_SIZE)
527 tb->source_done = TRUE;
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);
536 data_len -= chunk_len;
537 tb->target_len += chunk_len;
539 /* If we're full of target data, compute and fire off a window. */
540 if (tb->target_len == SVN_DELTA_WINDOW_SIZE)
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;
551 svn_pool_destroy(pool);
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. */
560 tpush_close_handler(void *baton)
562 struct tpush_baton *tb = baton;
563 svn_txdelta_window_t *window;
565 /* Send a final window if we have any residual target data. */
566 if (tb->target_len > 0)
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));
573 /* Send a final NULL window signifying the end. */
574 return tb->wh(NULL, tb->whb);
579 svn_txdelta_target_push(svn_txdelta_window_handler_t handler,
580 void *handler_baton, svn_stream_t *source,
583 struct tpush_baton *tb;
584 svn_stream_t *stream;
586 /* Initialize baton. */
587 tb = apr_palloc(pool, sizeof(*tb));
590 tb->whb = handler_baton;
592 tb->buf = apr_palloc(pool, 2 * SVN_DELTA_WINDOW_SIZE);
593 tb->source_offset = 0;
595 tb->source_done = FALSE;
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);
607 /* Functions for applying deltas. */
609 /* Ensure that BUF has enough space for VIEW_LEN bytes. */
610 static APR_INLINE svn_error_t *
611 size_buffer(char **buf, apr_size_t *buf_size,
612 apr_size_t view_len, apr_pool_t *pool)
614 if (view_len > *buf_size)
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);
626 /* Copy LEN bytes from SOURCE to TARGET, optimizing for the case where LEN
627 * is often very small. Return a pointer to the first byte after the copied
628 * target range, unlike standard memcpy(), as a potential further
629 * optimization for the caller.
631 * memcpy() is hard to tune for a wide range of buffer lengths. Therefore,
632 * it is often tuned for high throughput on large buffers and relatively
633 * low latency for mid-sized buffers (tens of bytes). However, the overhead
634 * for very small buffers (<10 bytes) is still high. Even passing the
635 * parameters, for instance, may take as long as copying 3 bytes.
637 * Because short copy sequences seem to be a common case, at least in
638 * "format 2" FSFS repositories, we copy them directly. Larger buffer sizes
639 * aren't hurt measurably by the exta 'if' clause. */
640 static APR_INLINE char *
641 fast_memcpy(char *target, const char *source, apr_size_t len)
645 memcpy(target, source, len);
650 /* memcpy is not exactly fast for small block sizes.
651 * Since they are common, let's run optimized code for them. */
652 const char *end = source + len;
653 for (; source != end; source++)
654 *(target++) = *source;
660 /* Copy LEN bytes from SOURCE to TARGET. Unlike memmove() or memcpy(),
661 * create repeating patterns if the source and target ranges overlap.
662 * Return a pointer to the first byte after the copied target range. */
663 static APR_INLINE char *
664 patterning_copy(char *target, const char *source, apr_size_t len)
666 const char *end = source + len;
668 /* On many machines, we can do "chunky" copies. */
670 #if SVN_UNALIGNED_ACCESS_IS_OK
672 if (end + sizeof(apr_uint32_t) <= target)
674 /* Source and target are at least 4 bytes apart, so we can copy in
676 for (; source + sizeof(apr_uint32_t) <= end;
677 source += sizeof(apr_uint32_t),
678 target += sizeof(apr_uint32_t))
679 *(apr_uint32_t *)(target) = *(apr_uint32_t *)(source);
684 /* fall through to byte-wise copy (either for the below-chunk-size tail
685 * or the whole copy) */
686 for (; source != end; source++)
687 *(target++) = *source;
693 svn_txdelta_apply_instructions(svn_txdelta_window_t *window,
694 const char *sbuf, char *tbuf,
697 const svn_txdelta_op_t *op;
700 for (op = window->ops; op < window->ops + window->num_ops; op++)
702 const apr_size_t buf_len = (op->length < *tlen - tpos
703 ? op->length : *tlen - tpos);
705 /* Check some invariants common to all instructions. */
706 assert(tpos + op->length <= window->tview_len);
708 switch (op->action_code)
710 case svn_txdelta_source:
711 /* Copy from source area. */
713 assert(op->offset + op->length <= window->sview_len);
714 fast_memcpy(tbuf + tpos, sbuf + op->offset, buf_len);
717 case svn_txdelta_target:
718 /* Copy from target area. We can't use memcpy() or the like
719 * since we need a specific semantics for overlapping copies:
720 * they must result in repeating patterns.
721 * Note that most copies won't have overlapping source and
722 * target ranges (they are just a result of self-compressed
723 * data) but a small percentage will. */
724 assert(op->offset < tpos);
725 patterning_copy(tbuf + tpos, tbuf + op->offset, buf_len);
728 case svn_txdelta_new:
729 /* Copy from window new area. */
730 assert(op->offset + op->length <= window->new_data->len);
731 fast_memcpy(tbuf + tpos,
732 window->new_data->data + op->offset,
737 assert(!"Invalid delta instruction code");
742 return; /* The buffer is full. */
745 /* Check that we produced the right amount of data. */
746 assert(tpos == window->tview_len);
750 /* This is a private interlibrary compatibility wrapper. */
752 svn_txdelta__apply_instructions(svn_txdelta_window_t *window,
753 const char *sbuf, char *tbuf,
756 svn_txdelta__apply_instructions(svn_txdelta_window_t *window,
757 const char *sbuf, char *tbuf,
760 svn_txdelta_apply_instructions(window, sbuf, tbuf, tlen);
764 /* Apply WINDOW to the streams given by APPL. */
766 apply_window(svn_txdelta_window_t *window, void *baton)
768 struct apply_baton *ab = (struct apply_baton *) baton;
774 /* We're done; just clean up. */
775 if (ab->result_digest)
776 apr_md5_final(ab->result_digest, &(ab->md5_context));
778 err = svn_stream_close(ab->target);
779 svn_pool_destroy(ab->pool);
784 /* Make sure the source view didn't slide backwards. */
785 SVN_ERR_ASSERT(window->sview_len == 0
786 || (window->sview_offset >= ab->sbuf_offset
787 && (window->sview_offset + window->sview_len
788 >= ab->sbuf_offset + ab->sbuf_len)));
790 /* Make sure there's enough room in the target buffer. */
791 SVN_ERR(size_buffer(&ab->tbuf, &ab->tbuf_size, window->tview_len, ab->pool));
793 /* Prepare the source buffer for reading from the input stream. */
794 if (window->sview_offset != ab->sbuf_offset
795 || window->sview_len > ab->sbuf_size)
797 char *old_sbuf = ab->sbuf;
799 /* Make sure there's enough room. */
800 SVN_ERR(size_buffer(&ab->sbuf, &ab->sbuf_size, window->sview_len,
803 /* If the existing view overlaps with the new view, copy the
804 * overlap to the beginning of the new buffer. */
805 if ( (apr_size_t)ab->sbuf_offset + ab->sbuf_len
806 > (apr_size_t)window->sview_offset)
809 (apr_size_t)(window->sview_offset - ab->sbuf_offset);
810 memmove(ab->sbuf, old_sbuf + start, ab->sbuf_len - start);
811 ab->sbuf_len -= start;
815 ab->sbuf_offset = window->sview_offset;
818 /* Read the remainder of the source view into the buffer. */
819 if (ab->sbuf_len < window->sview_len)
821 len = window->sview_len - ab->sbuf_len;
822 err = svn_stream_read(ab->source, ab->sbuf + ab->sbuf_len, &len);
823 if (err == SVN_NO_ERROR && len != window->sview_len - ab->sbuf_len)
824 err = svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL,
825 "Delta source ended unexpectedly");
826 if (err != SVN_NO_ERROR)
828 ab->sbuf_len = window->sview_len;
831 /* Apply the window instructions to the source view to generate
833 len = window->tview_len;
834 svn_txdelta_apply_instructions(window, ab->sbuf, ab->tbuf, &len);
835 SVN_ERR_ASSERT(len == window->tview_len);
837 /* Write out the output. */
839 /* ### We've also considered just adding two (optionally null)
840 arguments to svn_stream_create(): read_checksum and
841 write_checksum. Then instead of every caller updating an md5
842 context when it calls svn_stream_write() or svn_stream_read(),
843 streams would do it automatically, and verify the checksum in
844 svn_stream_closed(). But this might be overkill for issue #689;
845 so for now we just update the context here. */
846 if (ab->result_digest)
847 apr_md5_update(&(ab->md5_context), ab->tbuf, len);
849 return svn_stream_write(ab->target, ab->tbuf, &len);
854 svn_txdelta_apply(svn_stream_t *source,
855 svn_stream_t *target,
856 unsigned char *result_digest,
857 const char *error_info,
859 svn_txdelta_window_handler_t *handler,
860 void **handler_baton)
862 apr_pool_t *subpool = svn_pool_create(pool);
863 struct apply_baton *ab;
865 ab = apr_palloc(subpool, sizeof(*ab));
875 ab->result_digest = result_digest;
878 apr_md5_init(&(ab->md5_context));
881 ab->error_info = apr_pstrdup(subpool, error_info);
883 ab->error_info = NULL;
885 *handler = apply_window;
891 /* Convenience routines */
894 svn_txdelta_send_string(const svn_string_t *string,
895 svn_txdelta_window_handler_t handler,
899 svn_txdelta_window_t window = { 0 };
902 /* Build a single `new' op */
903 op.action_code = svn_txdelta_new;
905 op.length = string->len;
907 /* Build a single window containing a ptr to the string. */
908 window.tview_len = string->len;
911 window.new_data = string;
913 /* Push the one window at the handler. */
914 SVN_ERR((*handler)(&window, handler_baton));
916 /* Push a NULL at the handler, because we're done. */
917 return (*handler)(NULL, handler_baton);
920 svn_error_t *svn_txdelta_send_stream(svn_stream_t *stream,
921 svn_txdelta_window_handler_t handler,
923 unsigned char *digest,
926 svn_txdelta_window_t delta_window = { 0 };
927 svn_txdelta_op_t delta_op;
928 svn_string_t window_data;
929 char read_buf[SVN__STREAM_CHUNK_SIZE + 1];
930 svn_checksum_ctx_t *md5_checksum_ctx;
933 md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
937 apr_size_t read_len = SVN__STREAM_CHUNK_SIZE;
939 SVN_ERR(svn_stream_read(stream, read_buf, &read_len));
943 window_data.data = read_buf;
944 window_data.len = read_len;
946 delta_op.action_code = svn_txdelta_new;
948 delta_op.length = read_len;
950 delta_window.tview_len = read_len;
951 delta_window.num_ops = 1;
952 delta_window.ops = &delta_op;
953 delta_window.new_data = &window_data;
955 SVN_ERR(handler(&delta_window, handler_baton));
958 SVN_ERR(svn_checksum_update(md5_checksum_ctx, read_buf, read_len));
960 if (read_len < SVN__STREAM_CHUNK_SIZE)
963 SVN_ERR(handler(NULL, handler_baton));
967 svn_checksum_t *md5_checksum;
969 SVN_ERR(svn_checksum_final(&md5_checksum, md5_checksum_ctx, pool));
970 memcpy(digest, md5_checksum->digest, APR_MD5_DIGESTSIZE);
976 svn_error_t *svn_txdelta_send_txstream(svn_txdelta_stream_t *txstream,
977 svn_txdelta_window_handler_t handler,
981 svn_txdelta_window_t *window;
983 /* create a pool just for the windows */
984 apr_pool_t *wpool = svn_pool_create(pool);
988 /* free the window (if any) */
989 svn_pool_clear(wpool);
991 /* read in a single delta window */
992 SVN_ERR(svn_txdelta_next_window(&window, txstream, wpool));
994 /* shove it at the handler */
995 SVN_ERR((*handler)(window, handler_baton));
997 while (window != NULL);
999 svn_pool_destroy(wpool);
1001 return SVN_NO_ERROR;
1005 svn_txdelta_send_contents(const unsigned char *contents,
1007 svn_txdelta_window_handler_t handler,
1008 void *handler_baton,
1011 svn_string_t new_data;
1012 svn_txdelta_op_t op = { svn_txdelta_new, 0, 0 };
1013 svn_txdelta_window_t window = { 0, 0, 0, 1, 0 };
1015 window.new_data = &new_data;
1017 /* send CONTENT as a series of max-sized windows */
1020 /* stuff next chunk into the window */
1021 window.tview_len = len < SVN_DELTA_WINDOW_SIZE
1023 : SVN_DELTA_WINDOW_SIZE;
1024 op.length = window.tview_len;
1025 new_data.len = window.tview_len;
1026 new_data.data = (const char*)contents;
1028 /* update remaining */
1029 contents += window.tview_len;
1030 len -= window.tview_len;
1032 /* shove it at the handler */
1033 SVN_ERR((*handler)(&window, handler_baton));
1036 /* indicate end of stream */
1037 SVN_ERR((*handler)(NULL, handler_baton));
1039 return SVN_NO_ERROR;