svndiff.c revision 362181
11573Srgrimes/*
21573Srgrimes * svndiff.c -- Encoding and decoding svndiff-format deltas.
31573Srgrimes *
41573Srgrimes * ====================================================================
51573Srgrimes *    Licensed to the Apache Software Foundation (ASF) under one
61573Srgrimes *    or more contributor license agreements.  See the NOTICE file
71573Srgrimes *    distributed with this work for additional information
81573Srgrimes *    regarding copyright ownership.  The ASF licenses this file
91573Srgrimes *    to you under the Apache License, Version 2.0 (the
101573Srgrimes *    "License"); you may not use this file except in compliance
111573Srgrimes *    with the License.  You may obtain a copy of the License at
121573Srgrimes *
131573Srgrimes *      http://www.apache.org/licenses/LICENSE-2.0
141573Srgrimes *
151573Srgrimes *    Unless required by applicable law or agreed to in writing,
161573Srgrimes *    software distributed under the License is distributed on an
171573Srgrimes *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
181573Srgrimes *    KIND, either express or implied.  See the License for the
191573Srgrimes *    specific language governing permissions and limitations
201573Srgrimes *    under the License.
211573Srgrimes * ====================================================================
221573Srgrimes */
231573Srgrimes
241573Srgrimes
251573Srgrimes#include <assert.h>
261573Srgrimes#include <string.h>
271573Srgrimes#include "svn_delta.h"
281573Srgrimes#include "svn_io.h"
291573Srgrimes#include "delta.h"
301573Srgrimes#include "svn_pools.h"
311573Srgrimes#include "svn_private_config.h"
321573Srgrimes
331573Srgrimes#include "private/svn_error_private.h"
3423668Speter#include "private/svn_delta_private.h"
351573Srgrimes#include "private/svn_subr_private.h"
3690041Sobrien#include "private/svn_string_private.h"
3790041Sobrien#include "private/svn_dep_compat.h"
381573Srgrimes
39108626Stjrstatic const char SVNDIFF_V0[] = { 'S', 'V', 'N', 0 };
401573Srgrimesstatic const char SVNDIFF_V1[] = { 'S', 'V', 'N', 1 };
417615Swpaulstatic const char SVNDIFF_V2[] = { 'S', 'V', 'N', 2 };
4295459Sdes
437615Swpaul#define SVNDIFF_HEADER_SIZE (sizeof(SVNDIFF_V0))
441573Srgrimes
457615Swpaulstatic const char *
469978Swpaulget_svndiff_header(int version)
479978Swpaul{
489978Swpaul  if (version == 2)
499978Swpaul    return SVNDIFF_V2;
509978Swpaul  else if (version == 1)
519978Swpaul    return SVNDIFF_V1;
529978Swpaul  else
539978Swpaul    return SVNDIFF_V0;
549978Swpaul}
559978Swpaul
569978Swpaul/* ----- Text delta to svndiff ----- */
579978Swpaul
589978Swpaul/* We make one of these and get it passed back to us in calls to the
599978Swpaul   window handler.  We only use it to record the write function and
609978Swpaul   baton passed to svn_txdelta_to_svndiff3().  */
619978Swpaulstruct encoder_baton {
629978Swpaul  svn_stream_t *output;
639978Swpaul  svn_boolean_t header_done;
649978Swpaul  int version;
659978Swpaul  int compression_level;
669978Swpaul  /* Pool for temporary allocations, will be cleared periodically. */
679978Swpaul  apr_pool_t *scratch_pool;
689978Swpaul};
699978Swpaul
709978Swpaul/* This is at least as big as the largest size for a single instruction. */
719978Swpaul#define MAX_INSTRUCTION_LEN (2*SVN__MAX_ENCODED_UINT_LEN+1)
729978Swpaul/* This is at least as big as the largest possible instructions
739978Swpaul   section: in theory, the instructions could be SVN_DELTA_WINDOW_SIZE
749978Swpaul   1-byte copy-from-source instructions (though this is very unlikely). */
759978Swpaul#define MAX_INSTRUCTION_SECTION_LEN (SVN_DELTA_WINDOW_SIZE*MAX_INSTRUCTION_LEN)
769978Swpaul
779978Swpaul
789978Swpaul/* Append an encoded integer to a string.  */
799978Swpaulstatic void
809978Swpaulappend_encoded_int(svn_stringbuf_t *header, svn_filesize_t val)
819978Swpaul{
829978Swpaul  unsigned char buf[SVN__MAX_ENCODED_UINT_LEN], *p;
839978Swpaul
8430288Swpaul  SVN_ERR_ASSERT_NO_RETURN(val >= 0);
857615Swpaul  p = svn__encode_uint(buf, (apr_uint64_t)val);
867615Swpaul  svn_stringbuf_appendbytes(header, (const char *)buf, p - buf);
877615Swpaul}
889978Swpaul
899978Swpaulstatic svn_error_t *
909978Swpaulsend_simple_insertion_window(svn_txdelta_window_t *window,
919978Swpaul                             struct encoder_baton *eb)
929978Swpaul{
9310521Swpaul  unsigned char headers[SVNDIFF_HEADER_SIZE + 5 * SVN__MAX_ENCODED_UINT_LEN
947615Swpaul                          + MAX_INSTRUCTION_LEN];
959978Swpaul  unsigned char ibuf[MAX_INSTRUCTION_LEN];
967615Swpaul  unsigned char *header_current;
977615Swpaul  apr_size_t header_len;
989978Swpaul  apr_size_t ip_len, i;
991573Srgrimes  apr_size_t len = window->new_data->len;
1009978Swpaul
1011573Srgrimes  /* there is only one target copy op. It must span the whole window */
1021573Srgrimes  assert(window->ops[0].action_code == svn_txdelta_new);
1031573Srgrimes  assert(window->ops[0].length == window->tview_len);
1041573Srgrimes  assert(window->ops[0].offset == 0);
1051573Srgrimes
1061573Srgrimes  /* write stream header if necessary */
1071573Srgrimes  if (!eb->header_done)
1081573Srgrimes    {
1091573Srgrimes      eb->header_done = TRUE;
1101573Srgrimes      memcpy(headers, get_svndiff_header(eb->version), SVNDIFF_HEADER_SIZE);
1111573Srgrimes      header_current = headers + SVNDIFF_HEADER_SIZE;
1121573Srgrimes    }
1131573Srgrimes  else
1141573Srgrimes    {
1151573Srgrimes      header_current = headers;
1161573Srgrimes    }
1171573Srgrimes
1181573Srgrimes  /* Encode the action code and length.  */
1191573Srgrimes  if (window->tview_len >> 6 == 0)
1201573Srgrimes    {
1211573Srgrimes      ibuf[0] = (unsigned char)(window->tview_len + (0x2 << 6));
1221573Srgrimes      ip_len = 1;
1231573Srgrimes    }
1241573Srgrimes  else
1251573Srgrimes    {
1261573Srgrimes      ibuf[0] = (0x2 << 6);
1271573Srgrimes      ip_len = svn__encode_uint(ibuf + 1, window->tview_len) - ibuf;
1281573Srgrimes    }
1291573Srgrimes
1301573Srgrimes  /* encode the window header.  Please note that the source window may
1311573Srgrimes   * have content despite not being used for deltification. */
1321573Srgrimes  header_current = svn__encode_uint(header_current,
1331573Srgrimes                                    (apr_uint64_t)window->sview_offset);
1341573Srgrimes  header_current = svn__encode_uint(header_current, window->sview_len);
1351573Srgrimes  header_current = svn__encode_uint(header_current, window->tview_len);
136132793Sdes  header_current[0] = (unsigned char)ip_len;  /* 1 instruction */
137132793Sdes  header_current = svn__encode_uint(&header_current[1], len);
138132793Sdes
139132793Sdes  /* append instructions (1 to a handful of bytes) */
140132793Sdes  for (i = 0; i < ip_len; ++i)
141132793Sdes    header_current[i] = ibuf[i];
142132793Sdes
1431573Srgrimes  header_len = header_current - headers + ip_len;
1441573Srgrimes
1451573Srgrimes  /* Write out the window.  */
1461573Srgrimes  SVN_ERR(svn_stream_write(eb->output, (const char *)headers, &header_len));
1471573Srgrimes  if (len)
1481573Srgrimes    SVN_ERR(svn_stream_write(eb->output, window->new_data->data, &len));
1491573Srgrimes
1501573Srgrimes  return SVN_NO_ERROR;
1511573Srgrimes}
152132793Sdes
1531573Srgrimes/* Encodes delta window WINDOW to svndiff-format.
1549978Swpaul   The svndiff version is VERSION. COMPRESSION_LEVEL is the
1559978Swpaul   compression level to use.
1569978Swpaul   Returned values will be allocated in POOL or refer to *WINDOW
1579978Swpaul   fields. */
1589978Swpaulstatic svn_error_t *
1597336Swpaulencode_window(svn_stringbuf_t **instructions_p,
1607289Swpaul              svn_stringbuf_t **header_p,
1617336Swpaul              const svn_string_t **newdata_p,
1627289Swpaul              svn_txdelta_window_t *window,
1637336Swpaul              int version,
164237160Skib              int compression_level,
1651573Srgrimes              apr_pool_t *pool)
1669978Swpaul{
16710521Swpaul  svn_stringbuf_t *instructions;
16810521Swpaul  svn_stringbuf_t *header;
1699978Swpaul  const svn_string_t *newdata;
17015264Swpaul  unsigned char ibuf[MAX_INSTRUCTION_LEN], *ip;
1719978Swpaul  const svn_txdelta_op_t *op;
1729978Swpaul
1739978Swpaul  /* create the necessary data buffers */
174237160Skib  instructions = svn_stringbuf_create_empty(pool);
1759978Swpaul  header = svn_stringbuf_create_empty(pool);
176244092Sjilles
1779978Swpaul  /* Encode the instructions.  */
1789978Swpaul  for (op = window->ops; op < window->ops + window->num_ops; op++)
1799978Swpaul    {
1809978Swpaul      /* Encode the action code and length.  */
1819978Swpaul      ip = ibuf;
1829978Swpaul      switch (op->action_code)
1839978Swpaul        {
1849978Swpaul        case svn_txdelta_source: *ip = 0; break;
1859978Swpaul        case svn_txdelta_target: *ip = (0x1 << 6); break;
1869978Swpaul        case svn_txdelta_new:    *ip = (0x2 << 6); break;
1879978Swpaul        }
1889978Swpaul      if (op->length >> 6 == 0)
1899978Swpaul        *ip++ |= (unsigned char)op->length;
1909978Swpaul      else
1919978Swpaul        ip = svn__encode_uint(ip + 1, op->length);
1929978Swpaul      if (op->action_code != svn_txdelta_new)
19310521Swpaul        ip = svn__encode_uint(ip, op->offset);
19410521Swpaul      svn_stringbuf_appendbytes(instructions, (const char *)ibuf, ip - ibuf);
19512585Swpaul    }
19612585Swpaul
1979978Swpaul  /* Encode the header.  */
19810521Swpaul  append_encoded_int(header, window->sview_offset);
1999978Swpaul  append_encoded_int(header, window->sview_len);
200244092Sjilles  append_encoded_int(header, window->tview_len);
2019978Swpaul  if (version == 2)
2021573Srgrimes    {
2031573Srgrimes      svn_stringbuf_t *compressed_instructions;
2041573Srgrimes      compressed_instructions = svn_stringbuf_create_empty(pool);
205235740Sghelmer      SVN_ERR(svn__compress_lz4(instructions->data, instructions->len,
2061573Srgrimes                                compressed_instructions));
2079978Swpaul      instructions = compressed_instructions;
2089978Swpaul    }
2091573Srgrimes  else if (version == 1)
2101573Srgrimes    {
2111573Srgrimes      svn_stringbuf_t *compressed_instructions;
2121573Srgrimes      compressed_instructions = svn_stringbuf_create_empty(pool);
2131573Srgrimes      SVN_ERR(svn__compress_zlib(instructions->data, instructions->len,
2141573Srgrimes                                 compressed_instructions, compression_level));
2151573Srgrimes      instructions = compressed_instructions;
2161573Srgrimes    }
2171573Srgrimes  append_encoded_int(header, instructions->len);
218132793Sdes
2191573Srgrimes  /* Encode the data. */
2209978Swpaul  if (version == 2)
2219978Swpaul    {
2229978Swpaul      svn_stringbuf_t *compressed = svn_stringbuf_create_empty(pool);
2231573Srgrimes
2241573Srgrimes      SVN_ERR(svn__compress_lz4(window->new_data->data, window->new_data->len,
2251573Srgrimes                                compressed));
2261573Srgrimes      newdata = svn_stringbuf__morph_into_string(compressed);
2271573Srgrimes    }
2281573Srgrimes  else if (version == 1)
2291573Srgrimes    {
2301573Srgrimes      svn_stringbuf_t *compressed = svn_stringbuf_create_empty(pool);
2311573Srgrimes
2321573Srgrimes      SVN_ERR(svn__compress_zlib(window->new_data->data, window->new_data->len,
2331573Srgrimes                                 compressed, compression_level));
2341573Srgrimes      newdata = svn_stringbuf__morph_into_string(compressed);
2351573Srgrimes    }
2361573Srgrimes  else
2371573Srgrimes    newdata = window->new_data;
238132793Sdes
2391573Srgrimes  append_encoded_int(header, newdata->len);
24090041Sobrien
24190041Sobrien  *instructions_p = instructions;
2421573Srgrimes  *header_p = header;
2431573Srgrimes  *newdata_p = newdata;
2441573Srgrimes
2451573Srgrimes  return SVN_NO_ERROR;
2461573Srgrimes}
2471573Srgrimes
2481573Srgrimes/* Note: When changing things here, check the related comment in
249237160Skib   the svn_txdelta_to_svndiff_stream() function.  */
2501573Srgrimesstatic svn_error_t *
251237160Skibwindow_handler(svn_txdelta_window_t *window, void *baton)
2521573Srgrimes{
2531573Srgrimes  struct encoder_baton *eb = baton;
254237160Skib  apr_size_t len;
2551573Srgrimes  svn_stringbuf_t *instructions;
2561573Srgrimes  svn_stringbuf_t *header;
2571573Srgrimes  const svn_string_t *newdata;
2581573Srgrimes
2591573Srgrimes  /* use specialized code if there is no source */
260237160Skib  if (window && !window->src_ops && window->num_ops == 1 && !eb->version)
261237160Skib    return svn_error_trace(send_simple_insertion_window(window, eb));
262237160Skib
263237160Skib  /* Make sure we write the header.  */
2641573Srgrimes  if (!eb->header_done)
265237160Skib    {
266237160Skib      len = SVNDIFF_HEADER_SIZE;
2677149Swpaul      SVN_ERR(svn_stream_write(eb->output, get_svndiff_header(eb->version),
2687223Swpaul                               &len));
2697149Swpaul      eb->header_done = TRUE;
2701573Srgrimes    }
2711573Srgrimes
2729978Swpaul  if (window == NULL)
273132793Sdes    {
274132793Sdes      /* We're done; clean up. */
2759978Swpaul      SVN_ERR(svn_stream_close(eb->output));
276132793Sdes
277132793Sdes      svn_pool_destroy(eb->scratch_pool);
27831180Swpaul
2799978Swpaul      return SVN_NO_ERROR;
28031180Swpaul    }
281237160Skib
28231180Swpaul  svn_pool_clear(eb->scratch_pool);
28315264Swpaul
28433950Ssteve  SVN_ERR(encode_window(&instructions, &header, &newdata, window,
28533950Ssteve                        eb->version, eb->compression_level,
28652856Sache                        eb->scratch_pool));
28733950Ssteve
28833950Ssteve  /* Write out the window.  */
289235739Sghelmer  len = header->len;
290237160Skib  SVN_ERR(svn_stream_write(eb->output, header->data, &len));
29133950Ssteve  if (instructions->len > 0)
29215839Swpaul    {
29315839Swpaul      len = instructions->len;
294235739Sghelmer      SVN_ERR(svn_stream_write(eb->output, instructions->data, &len));
2959978Swpaul    }
2969978Swpaul  if (newdata->len > 0)
297132793Sdes    {
298148317Sjon      len = newdata->len;
299148317Sjon      SVN_ERR(svn_stream_write(eb->output, newdata->data, &len));
3009978Swpaul    }
301148317Sjon
302148317Sjon  return SVN_NO_ERROR;
303148317Sjon}
304148317Sjon
305148317Sjonvoid
306148317Sjonsvn_txdelta_to_svndiff3(svn_txdelta_window_handler_t *handler,
307148317Sjon                        void **handler_baton,
308236402Sghelmer                        svn_stream_t *output,
309235739Sghelmer                        int svndiff_version,
310235739Sghelmer                        int compression_level,
311235739Sghelmer                        apr_pool_t *pool)
312236402Sghelmer{
313235739Sghelmer  struct encoder_baton *eb;
314235739Sghelmer
315236402Sghelmer  eb = apr_palloc(pool, sizeof(*eb));
316235739Sghelmer  eb->output = output;
317235739Sghelmer  eb->header_done = FALSE;
318235739Sghelmer  eb->scratch_pool = svn_pool_create(pool);
319236402Sghelmer  eb->version = svndiff_version;
320235739Sghelmer  eb->compression_level = compression_level;
321235739Sghelmer
322236402Sghelmer  *handler = window_handler;
323236402Sghelmer  *handler_baton = eb;
324148317Sjon}
325148317Sjon
326236402Sghelmervoid
327148317Sjonsvn_txdelta_to_svndiff2(svn_txdelta_window_handler_t *handler,
328148317Sjon                        void **handler_baton,
329148317Sjon                        svn_stream_t *output,
330236402Sghelmer                        int svndiff_version,
331236402Sghelmer                        apr_pool_t *pool)
332148317Sjon{
333148317Sjon  svn_txdelta_to_svndiff3(handler, handler_baton, output, svndiff_version,
334148317Sjon                          SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
335148317Sjon}
336148317Sjon
337148317Sjonvoid
338235739Sghelmersvn_txdelta_to_svndiff(svn_stream_t *output,
339148317Sjon                       apr_pool_t *pool,
3409978Swpaul                       svn_txdelta_window_handler_t *handler,
3419978Swpaul                       void **handler_baton)
3429978Swpaul{
3439978Swpaul  svn_txdelta_to_svndiff3(handler, handler_baton, output, 0,
3441573Srgrimes                          SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
3451573Srgrimes}
3461573Srgrimes
3471573Srgrimes
348132793Sdes/* ----- svndiff to text delta ----- */
3491573Srgrimes
3501573Srgrimes/* An svndiff parser object.  */
3517336Swpaulstruct decode_baton
3529287Swpaul{
3537336Swpaul  /* Once the svndiff parser has enough data buffered to create a
3547336Swpaul     "window", it passes this window to the caller's consumer routine.  */
3557336Swpaul  svn_txdelta_window_handler_t consumer_func;
3569978Swpaul  void *consumer_baton;
3579978Swpaul
3589978Swpaul  /* Pool to create subpools from; each developing window will be a
3591573Srgrimes     subpool.  */
3609978Swpaul  apr_pool_t *pool;
36115264Swpaul
3629978Swpaul  /* The current subpool which contains our current window-buffer.  */
3639978Swpaul  apr_pool_t *subpool;
3649978Swpaul
365148317Sjon  /* The actual svndiff data buffer, living within subpool.  */
366148317Sjon  svn_stringbuf_t *buffer;
367148317Sjon
368148317Sjon  /* The offset and size of the last source view, so that we can check
369148317Sjon     to make sure the next one isn't sliding backwards.  */
370148317Sjon  svn_filesize_t last_sview_offset;
371148317Sjon  apr_size_t last_sview_len;
372148317Sjon
373148317Sjon  /* We have to discard four bytes at the beginning for the header.
374148317Sjon     This field keeps track of how many of those bytes we have read.  */
375148317Sjon  apr_size_t header_bytes;
376148317Sjon
377148317Sjon  /* Do we want an error to occur when we close the stream that
378148317Sjon     indicates we didn't send the whole svndiff data?  If you plan to
379148317Sjon     not transmit the whole svndiff data stream, you will want this to
380148317Sjon     be FALSE. */
381148317Sjon  svn_boolean_t error_on_early_close;
382148317Sjon
383148317Sjon  /* svndiff version in use by delta.  */
3849978Swpaul  unsigned char version;
385148317Sjon
386148317Sjon  /* Length of parsed delta window header. 0 if window is not parsed yet. */
3879978Swpaul  apr_size_t window_header_len;
388235739Sghelmer
389148317Sjon  /* Five integer fields of parsed delta window header. Valid only if
390148317Sjon     WINDOW_HEADER_LEN > 0 */
391148317Sjon  svn_filesize_t  sview_offset;
392148317Sjon  apr_size_t sview_len;
393235739Sghelmer  apr_size_t tview_len;
394148317Sjon  apr_size_t inslen;
395235739Sghelmer  apr_size_t newlen;
3969978Swpaul};
39730390Swpaul
3989978Swpaul
3999978Swpaul/* Wrapper aroung svn__deencode_uint taking a file size as *VAL. */
40030390Swpaulstatic const unsigned char *
4011573Srgrimesdecode_file_offset(svn_filesize_t *val,
4029978Swpaul                   const unsigned char *p,
4039978Swpaul                   const unsigned char *end)
4049978Swpaul{
4051573Srgrimes  apr_uint64_t temp = 0;
4061573Srgrimes  const unsigned char *result = svn__decode_uint(&temp, p, end);
4071573Srgrimes  *val = (svn_filesize_t)temp;
4081573Srgrimes
4091573Srgrimes  return result;
4101573Srgrimes}
4111573Srgrimes
4121573Srgrimes/* Same as above, only decode into a size variable. */
4131573Srgrimesstatic const unsigned char *
4141573Srgrimesdecode_size(apr_size_t *val,
4151573Srgrimes            const unsigned char *p,
416132793Sdes            const unsigned char *end)
4171573Srgrimes{
418236402Sghelmer  apr_uint64_t temp = 0;
419236402Sghelmer  const unsigned char *result = svn__decode_uint(&temp, p, end);
420236402Sghelmer  if (temp > APR_SIZE_MAX)
421236402Sghelmer    return NULL;
422236402Sghelmer
4239287Swpaul  *val = (apr_size_t)temp;
42490041Sobrien  return result;
4259287Swpaul}
4261573Srgrimes
4271573Srgrimes/* Decode an instruction into OP, returning a pointer to the text
4281573Srgrimes   after the instruction.  Note that if the action code is
4291573Srgrimes   svn_txdelta_new, the offset field of *OP will not be set.  */
4301573Srgrimesstatic const unsigned char *
4311573Srgrimesdecode_instruction(svn_txdelta_op_t *op,
4321573Srgrimes                   const unsigned char *p,
4331573Srgrimes                   const unsigned char *end)
4341573Srgrimes{
435237160Skib  apr_size_t c;
4361573Srgrimes  apr_size_t action;
4371573Srgrimes
4389287Swpaul  if (p == end)
4399287Swpaul    return NULL;
4409287Swpaul
4419287Swpaul  /* We need this more than once */
4429287Swpaul  c = *p++;
4439287Swpaul
4449287Swpaul  /* Decode the instruction selector.  */
4451573Srgrimes  action = (c >> 6) & 0x3;
4469287Swpaul  if (action >= 0x3)
4471573Srgrimes      return NULL;
4481573Srgrimes
4491573Srgrimes  /* This relies on enum svn_delta_action values to match and never to be
4501573Srgrimes     redefined. */
4517149Swpaul  op->action_code = (enum svn_delta_action)(action);
4527149Swpaul
4531573Srgrimes  /* Decode the length and offset.  */
454236402Sghelmer  op->length = c & 0x3f;
455235740Sghelmer  if (op->length == 0)
456235740Sghelmer    {
457236402Sghelmer      p = decode_size(&op->length, p, end);
458236402Sghelmer      if (p == NULL)
4591573Srgrimes        return NULL;
4601573Srgrimes    }
4619287Swpaul  if (action != svn_txdelta_new)
4629287Swpaul    {
4639287Swpaul      p = decode_size(&op->offset, p, end);
4641573Srgrimes      if (p == NULL)
465236402Sghelmer        return NULL;
4669287Swpaul    }
4679287Swpaul
4689287Swpaul  return p;
4699287Swpaul}
4709287Swpaul
4719287Swpaul/* Count the instructions in the range [P..END-1] and make sure they
472236402Sghelmer   are valid for the given window lengths.  Return an error if the
473236402Sghelmer   instructions are invalid; otherwise set *NINST to the number of
4749287Swpaul   instructions.  */
475236402Sghelmerstatic svn_error_t *
476236402Sghelmercount_and_verify_instructions(int *ninst,
477236402Sghelmer                              const unsigned char *p,
478236402Sghelmer                              const unsigned char *end,
479236402Sghelmer                              apr_size_t sview_len,
480236402Sghelmer                              apr_size_t tview_len,
481236402Sghelmer                              apr_size_t new_len)
482236402Sghelmer{
483236402Sghelmer  int n = 0;
484236402Sghelmer  svn_txdelta_op_t op;
485236402Sghelmer  apr_size_t tpos = 0, npos = 0;
486236402Sghelmer
487236402Sghelmer  while (p < end)
488236402Sghelmer    {
489236402Sghelmer      p = decode_instruction(&op, p, end);
490236402Sghelmer
491236402Sghelmer      /* Detect any malformed operations from the instruction stream. */
492236402Sghelmer      if (p == NULL)
493236402Sghelmer        return svn_error_createf
494236402Sghelmer          (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
495236402Sghelmer           _("Invalid diff stream: insn %d cannot be decoded"), n);
4961573Srgrimes      else if (op.length == 0)
497235740Sghelmer        return svn_error_createf
498235740Sghelmer          (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
4999287Swpaul           _("Invalid diff stream: insn %d has length zero"), n);
5009287Swpaul      else if (op.length > tview_len - tpos)
5019287Swpaul        return svn_error_createf
5029287Swpaul          (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
5039287Swpaul           _("Invalid diff stream: insn %d overflows the target view"), n);
5049287Swpaul
5059287Swpaul      switch (op.action_code)
506236402Sghelmer        {
507236402Sghelmer        case svn_txdelta_source:
508236402Sghelmer          if (op.length > sview_len - op.offset ||
509236402Sghelmer              op.offset > sview_len)
510236402Sghelmer            return svn_error_createf
511236402Sghelmer              (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
512236402Sghelmer               _("Invalid diff stream: "
513236402Sghelmer                 "[src] insn %d overflows the source view"), n);
514236402Sghelmer          break;
515292138Sngie        case svn_txdelta_target:
5169287Swpaul          if (op.offset >= tpos)
5171573Srgrimes            return svn_error_createf
5181573Srgrimes              (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
5191573Srgrimes               _("Invalid diff stream: "
5207175Swpaul                 "[tgt] insn %d starts beyond the target view position"), n);
5211573Srgrimes          break;
52223668Speter        case svn_txdelta_new:
52323668Speter          if (op.length > new_len - npos)
52423668Speter            return svn_error_createf
52523668Speter              (SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
5261573Srgrimes               _("Invalid diff stream: "
5271573Srgrimes                 "[new] insn %d overflows the new data section"), n);
5281573Srgrimes          npos += op.length;
5291573Srgrimes          break;
5301573Srgrimes        }
5311573Srgrimes      tpos += op.length;
5321573Srgrimes      n++;
5331573Srgrimes    }
5341573Srgrimes  if (tpos != tview_len)
535132793Sdes    return svn_error_create(SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
5361573Srgrimes                            _("Delta does not fill the target window"));
537237159Skib  if (npos != new_len)
53890041Sobrien    return svn_error_create(SVN_ERR_SVNDIFF_INVALID_OPS, NULL,
5391573Srgrimes                            _("Delta does not contain enough new data"));
5401573Srgrimes
54120957Swpaul  *ninst = n;
5427149Swpaul  return SVN_NO_ERROR;
5437149Swpaul}
5447149Swpaul
545235740Sghelmer/* Given the five integer fields of a window header and a pointer to
5461573Srgrimes   the remainder of the window contents, fill in a delta window
5477149Swpaul   structure *WINDOW.  New allocations will be performed in POOL;
5487149Swpaul   the new_data field of *WINDOW will refer directly to memory pointed
5497149Swpaul   to by DATA. */
5507223Swpaulstatic svn_error_t *
5517149Swpauldecode_window(svn_txdelta_window_t *window, svn_filesize_t sview_offset,
5527223Swpaul              apr_size_t sview_len, apr_size_t tview_len, apr_size_t inslen,
553236402Sghelmer              apr_size_t newlen, const unsigned char *data, apr_pool_t *pool,
5547223Swpaul              unsigned int version)
5559978Swpaul{
5569978Swpaul  const unsigned char *insend;
5579978Swpaul  int ninst;
5589978Swpaul  apr_size_t npos;
5599978Swpaul  svn_txdelta_op_t *ops, *op;
5609978Swpaul  svn_string_t *new_data;
5617149Swpaul
56220957Swpaul  window->sview_offset = sview_offset;
5637149Swpaul  window->sview_len = sview_len;
5647149Swpaul  window->tview_len = tview_len;
5657149Swpaul
566235740Sghelmer  insend = data + inslen;
5671573Srgrimes
5687149Swpaul  if (version == 2)
5697149Swpaul    {
5707149Swpaul      svn_stringbuf_t *instout = svn_stringbuf_create_empty(pool);
5717149Swpaul      svn_stringbuf_t *ndout = svn_stringbuf_create_empty(pool);
5727149Swpaul
5737149Swpaul      SVN_ERR(svn__decompress_lz4(insend, newlen, ndout,
5747149Swpaul                                  SVN_DELTA_WINDOW_SIZE));
5757149Swpaul      SVN_ERR(svn__decompress_lz4(data, insend - data, instout,
5761573Srgrimes                                  MAX_INSTRUCTION_SECTION_LEN));
5771573Srgrimes
5781573Srgrimes      newlen = ndout->len;
5791573Srgrimes      data = (unsigned char *)instout->data;
5801573Srgrimes      insend = (unsigned char *)instout->data + instout->len;
5811573Srgrimes
5821573Srgrimes      new_data = svn_stringbuf__morph_into_string(ndout);
5831573Srgrimes    }
5841573Srgrimes  else if (version == 1)
5851573Srgrimes    {
5861573Srgrimes      svn_stringbuf_t *instout = svn_stringbuf_create_empty(pool);
5871573Srgrimes      svn_stringbuf_t *ndout = svn_stringbuf_create_empty(pool);
5881573Srgrimes
589235740Sghelmer      SVN_ERR(svn__decompress_zlib(insend, newlen, ndout,
590235740Sghelmer                                   SVN_DELTA_WINDOW_SIZE));
5911573Srgrimes      SVN_ERR(svn__decompress_zlib(data, insend - data, instout,
5921573Srgrimes                                   MAX_INSTRUCTION_SECTION_LEN));
593235740Sghelmer
594235740Sghelmer      newlen = ndout->len;
595235740Sghelmer      data = (unsigned char *)instout->data;
596235740Sghelmer      insend = (unsigned char *)instout->data + instout->len;
5971573Srgrimes
5981573Srgrimes      new_data = svn_stringbuf__morph_into_string(ndout);
5991573Srgrimes    }
6001573Srgrimes  else
6011573Srgrimes    {
6021573Srgrimes      /* Copy the data because an svn_string_t must have the invariant
6031573Srgrimes         data[len]=='\0'. */
6041573Srgrimes      new_data = svn_string_ncreate((const char*)insend, newlen, pool);
6051573Srgrimes    }
6061573Srgrimes
6071573Srgrimes  /* Count the instructions and make sure they are all valid.  */
6081573Srgrimes  SVN_ERR(count_and_verify_instructions(&ninst, data, insend,
6091573Srgrimes                                        sview_len, tview_len, newlen));
6101573Srgrimes
6111573Srgrimes  /* Allocate a buffer for the instructions and decode them. */
6121573Srgrimes  ops = apr_palloc(pool, ninst * sizeof(*ops));
6131573Srgrimes  npos = 0;
614237159Skib  window->src_ops = 0;
615235740Sghelmer  for (op = ops; op < ops + ninst; op++)
616235740Sghelmer    {
617235740Sghelmer      data = decode_instruction(op, data, insend);
618235740Sghelmer      if (op->action_code == svn_txdelta_source)
6191573Srgrimes        ++window->src_ops;
620237159Skib      else if (op->action_code == svn_txdelta_new)
621237159Skib        {
622237159Skib          op->offset = npos;
623237159Skib          npos += op->length;
6241573Srgrimes        }
6251573Srgrimes    }
6261573Srgrimes  SVN_ERR_ASSERT(data == insend);
627237159Skib
6281573Srgrimes  window->ops = ops;
6291573Srgrimes  window->num_ops = ninst;
6301573Srgrimes  window->new_data = new_data;
6311573Srgrimes
6321573Srgrimes  return SVN_NO_ERROR;
6331573Srgrimes}
6341573Srgrimes
6351573Srgrimesstatic svn_error_t *
6361573Srgrimeswrite_handler(void *baton,
6371573Srgrimes              const char *buffer,
6381573Srgrimes              apr_size_t *len)
6391573Srgrimes{
6401573Srgrimes  struct decode_baton *db = (struct decode_baton *) baton;
6411573Srgrimes  const unsigned char *p, *end;
6421573Srgrimes  apr_size_t buflen = *len;
6431573Srgrimes
6441573Srgrimes  /* Chew up four bytes at the beginning for the header.  */
6451573Srgrimes  if (db->header_bytes < SVNDIFF_HEADER_SIZE)
6461573Srgrimes    {
6471573Srgrimes      apr_size_t nheader = SVNDIFF_HEADER_SIZE - db->header_bytes;
6489978Swpaul      if (nheader > buflen)
6499978Swpaul        nheader = buflen;
6509978Swpaul      if (memcmp(buffer, SVNDIFF_V0 + db->header_bytes, nheader) == 0)
6519978Swpaul        db->version = 0;
6529978Swpaul      else if (memcmp(buffer, SVNDIFF_V1 + db->header_bytes, nheader) == 0)
6539978Swpaul        db->version = 1;
6549978Swpaul      else if (memcmp(buffer, SVNDIFF_V2 + db->header_bytes, nheader) == 0)
6559978Swpaul        db->version = 2;
6569978Swpaul      else
6579978Swpaul        return svn_error_create(SVN_ERR_SVNDIFF_INVALID_HEADER, NULL,
658235740Sghelmer                                _("Svndiff has invalid header"));
6591573Srgrimes      buflen -= nheader;
660      buffer += nheader;
661      db->header_bytes += nheader;
662    }
663
664  /* Concatenate the old with the new.  */
665  svn_stringbuf_appendbytes(db->buffer, buffer, buflen);
666
667  /* We have a buffer of svndiff data that might be good for:
668
669     a) an integral number of windows' worth of data - this is a
670        trivial case.  Make windows from our data and ship them off.
671
672     b) a non-integral number of windows' worth of data - we shall
673        consume the integral portion of the window data, and then
674        somewhere in the following loop the decoding of the svndiff
675        data will run out of stuff to decode, and will simply return
676        SVN_NO_ERROR, anxiously awaiting more data.
677  */
678
679  while (1)
680    {
681      svn_txdelta_window_t window;
682
683      /* Read the header, if we have enough bytes for that.  */
684      p = (const unsigned char *) db->buffer->data;
685      end = (const unsigned char *) db->buffer->data + db->buffer->len;
686
687      if (db->window_header_len == 0)
688        {
689          svn_filesize_t sview_offset;
690          apr_size_t sview_len, tview_len, inslen, newlen;
691          const unsigned char *hdr_start = p;
692
693          p = decode_file_offset(&sview_offset, p, end);
694          if (p == NULL)
695              break;
696
697          p = decode_size(&sview_len, p, end);
698          if (p == NULL)
699              break;
700
701          p = decode_size(&tview_len, p, end);
702          if (p == NULL)
703              break;
704
705          p = decode_size(&inslen, p, end);
706          if (p == NULL)
707              break;
708
709          p = decode_size(&newlen, p, end);
710          if (p == NULL)
711              break;
712
713          if (tview_len > SVN_DELTA_WINDOW_SIZE ||
714              sview_len > SVN_DELTA_WINDOW_SIZE ||
715              /* for svndiff1, newlen includes the original length */
716              newlen > SVN_DELTA_WINDOW_SIZE + SVN__MAX_ENCODED_UINT_LEN ||
717              inslen > MAX_INSTRUCTION_SECTION_LEN)
718            return svn_error_create(
719                     SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
720                     _("Svndiff contains a too-large window"));
721
722          /* Check for integer overflow.  */
723          if (sview_offset < 0 || inslen + newlen < inslen
724              || sview_len + tview_len < sview_len
725              || (apr_size_t)sview_offset + sview_len < (apr_size_t)sview_offset)
726            return svn_error_create(
727                      SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
728                      _("Svndiff contains corrupt window header"));
729
730          /* Check for source windows which slide backwards.  */
731          if (sview_len > 0
732              && (sview_offset < db->last_sview_offset
733                  || (sview_offset + sview_len
734                      < db->last_sview_offset + db->last_sview_len)))
735            return svn_error_create(
736                     SVN_ERR_SVNDIFF_BACKWARD_VIEW, NULL,
737                     _("Svndiff has backwards-sliding source views"));
738
739          /* Remember parsed window header. */
740          db->window_header_len = p - hdr_start;
741          db->sview_offset = sview_offset;
742          db->sview_len = sview_len;
743          db->tview_len = tview_len;
744          db->inslen = inslen;
745          db->newlen = newlen;
746        }
747      else
748        {
749          /* Skip already parsed window header. */
750          p += db->window_header_len;
751        }
752
753      /* Wait for more data if we don't have enough bytes for the
754         whole window. */
755      if ((apr_size_t) (end - p) < db->inslen + db->newlen)
756        return SVN_NO_ERROR;
757
758      /* Decode the window and send it off. */
759      SVN_ERR(decode_window(&window, db->sview_offset, db->sview_len,
760                            db->tview_len, db->inslen, db->newlen, p,
761                            db->subpool, db->version));
762      SVN_ERR(db->consumer_func(&window, db->consumer_baton));
763
764      p += db->inslen + db->newlen;
765
766      /* Remove processed data from the buffer.  */
767      svn_stringbuf_remove(db->buffer, 0, db->buffer->len - (end - p));
768
769      /* Reset window header length. */
770      db->window_header_len = 0;
771
772      /* Remember the offset and length of the source view for next time.  */
773      db->last_sview_offset = db->sview_offset;
774      db->last_sview_len = db->sview_len;
775
776      /* Clear subpool. */
777      svn_pool_clear(db->subpool);
778    }
779
780  /* At this point we processed all integral windows and DB->BUFFER is empty
781     or contains partially read window header.
782     Check that unprocessed data is not larger than theoretical maximum
783     window header size. */
784  if (db->buffer->len > 5 * SVN__MAX_ENCODED_UINT_LEN)
785    return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
786                            _("Svndiff contains a too-large window header"));
787
788  return SVN_NO_ERROR;
789}
790
791/* Minimal svn_stream_t write handler, doing nothing */
792static svn_error_t *
793noop_write_handler(void *baton,
794                   const char *buffer,
795                   apr_size_t *len)
796{
797  return SVN_NO_ERROR;
798}
799
800static svn_error_t *
801close_handler(void *baton)
802{
803  struct decode_baton *db = (struct decode_baton *) baton;
804  svn_error_t *err;
805
806  /* Make sure that we're at a plausible end of stream, returning an
807     error if we are expected to do so.  */
808  if ((db->error_on_early_close)
809      && (db->header_bytes < 4 || db->buffer->len != 0))
810    return svn_error_create(SVN_ERR_SVNDIFF_UNEXPECTED_END, NULL,
811                            _("Unexpected end of svndiff input"));
812
813  /* Tell the window consumer that we're done, and clean up.  */
814  err = db->consumer_func(NULL, db->consumer_baton);
815  svn_pool_destroy(db->pool);
816  return err;
817}
818
819
820svn_stream_t *
821svn_txdelta_parse_svndiff(svn_txdelta_window_handler_t handler,
822                          void *handler_baton,
823                          svn_boolean_t error_on_early_close,
824                          apr_pool_t *pool)
825{
826  svn_stream_t *stream;
827
828  if (handler != svn_delta_noop_window_handler)
829    {
830      apr_pool_t *subpool = svn_pool_create(pool);
831      struct decode_baton *db = apr_palloc(pool, sizeof(*db));
832
833      db->consumer_func = handler;
834      db->consumer_baton = handler_baton;
835      db->pool = subpool;
836      db->subpool = svn_pool_create(subpool);
837      db->buffer = svn_stringbuf_create_empty(db->pool);
838      db->last_sview_offset = 0;
839      db->last_sview_len = 0;
840      db->header_bytes = 0;
841      db->error_on_early_close = error_on_early_close;
842      db->window_header_len = 0;
843      stream = svn_stream_create(db, pool);
844
845      svn_stream_set_write(stream, write_handler);
846      svn_stream_set_close(stream, close_handler);
847    }
848  else
849    {
850      /* And else we just ignore everything as efficiently as we can.
851         by only hooking a no-op handler */
852      stream = svn_stream_create(NULL, pool);
853      svn_stream_set_write(stream, noop_write_handler);
854    }
855  return stream;
856}
857
858
859/* Routines for reading one svndiff window at a time. */
860
861/* Read one byte from STREAM into *BYTE. */
862static svn_error_t *
863read_one_byte(unsigned char *byte, svn_stream_t *stream)
864{
865  char c;
866  apr_size_t len = 1;
867
868  SVN_ERR(svn_stream_read_full(stream, &c, &len));
869  if (len == 0)
870    return svn_error_create(SVN_ERR_SVNDIFF_UNEXPECTED_END, NULL,
871                            _("Unexpected end of svndiff input"));
872  *byte = (unsigned char) c;
873  return SVN_NO_ERROR;
874}
875
876/* Read and decode one integer from STREAM into *SIZE.
877   Increment *BYTE_COUNTER by the number of chars we have read. */
878static svn_error_t *
879read_one_size(apr_size_t *size,
880              apr_size_t *byte_counter,
881              svn_stream_t *stream)
882{
883  unsigned char c;
884
885  *size = 0;
886  while (1)
887    {
888      SVN_ERR(read_one_byte(&c, stream));
889      ++*byte_counter;
890      *size = (*size << 7) | (c & 0x7f);
891      if (!(c & 0x80))
892        break;
893    }
894  return SVN_NO_ERROR;
895}
896
897/* Read a window header from STREAM and check it for integer overflow. */
898static svn_error_t *
899read_window_header(svn_stream_t *stream, svn_filesize_t *sview_offset,
900                   apr_size_t *sview_len, apr_size_t *tview_len,
901                   apr_size_t *inslen, apr_size_t *newlen,
902                   apr_size_t *header_len)
903{
904  unsigned char c;
905
906  /* Read the source view offset by hand, since it's not an apr_size_t. */
907  *header_len = 0;
908  *sview_offset = 0;
909  while (1)
910    {
911      SVN_ERR(read_one_byte(&c, stream));
912      ++*header_len;
913      *sview_offset = (*sview_offset << 7) | (c & 0x7f);
914      if (!(c & 0x80))
915        break;
916    }
917
918  /* Read the four size fields. */
919  SVN_ERR(read_one_size(sview_len, header_len, stream));
920  SVN_ERR(read_one_size(tview_len, header_len, stream));
921  SVN_ERR(read_one_size(inslen, header_len, stream));
922  SVN_ERR(read_one_size(newlen, header_len, stream));
923
924  if (*tview_len > SVN_DELTA_WINDOW_SIZE ||
925      *sview_len > SVN_DELTA_WINDOW_SIZE ||
926      /* for svndiff1, newlen includes the original length */
927      *newlen > SVN_DELTA_WINDOW_SIZE + SVN__MAX_ENCODED_UINT_LEN ||
928      *inslen > MAX_INSTRUCTION_SECTION_LEN)
929    return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
930                            _("Svndiff contains a too-large window"));
931
932  /* Check for integer overflow.  */
933  if (*sview_offset < 0 || *inslen + *newlen < *inslen
934      || *sview_len + *tview_len < *sview_len
935      || (apr_size_t)*sview_offset + *sview_len < (apr_size_t)*sview_offset)
936    return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
937                            _("Svndiff contains corrupt window header"));
938
939  return SVN_NO_ERROR;
940}
941
942svn_error_t *
943svn_txdelta_read_svndiff_window(svn_txdelta_window_t **window,
944                                svn_stream_t *stream,
945                                int svndiff_version,
946                                apr_pool_t *pool)
947{
948  svn_filesize_t sview_offset;
949  apr_size_t sview_len, tview_len, inslen, newlen, len, header_len;
950  unsigned char *buf;
951
952  SVN_ERR(read_window_header(stream, &sview_offset, &sview_len, &tview_len,
953                             &inslen, &newlen, &header_len));
954  len = inslen + newlen;
955  buf = apr_palloc(pool, len);
956  SVN_ERR(svn_stream_read_full(stream, (char*)buf, &len));
957  if (len < inslen + newlen)
958    return svn_error_create(SVN_ERR_SVNDIFF_UNEXPECTED_END, NULL,
959                            _("Unexpected end of svndiff input"));
960  *window = apr_palloc(pool, sizeof(**window));
961  return decode_window(*window, sview_offset, sview_len, tview_len, inslen,
962                       newlen, buf, pool, svndiff_version);
963}
964
965
966svn_error_t *
967svn_txdelta_skip_svndiff_window(apr_file_t *file,
968                                int svndiff_version,
969                                apr_pool_t *pool)
970{
971  svn_stream_t *stream = svn_stream_from_aprfile2(file, TRUE, pool);
972  svn_filesize_t sview_offset;
973  apr_size_t sview_len, tview_len, inslen, newlen, header_len;
974  apr_off_t offset;
975
976  SVN_ERR(read_window_header(stream, &sview_offset, &sview_len, &tview_len,
977                             &inslen, &newlen, &header_len));
978
979  offset = inslen + newlen;
980  return svn_io_file_seek(file, APR_CUR, &offset, pool);
981}
982
983svn_error_t *
984svn_txdelta__read_raw_window_len(apr_size_t *window_len,
985                                 svn_stream_t *stream,
986                                 apr_pool_t *pool)
987{
988  svn_filesize_t sview_offset;
989  apr_size_t sview_len, tview_len, inslen, newlen, header_len;
990
991  SVN_ERR(read_window_header(stream, &sview_offset, &sview_len, &tview_len,
992                             &inslen, &newlen, &header_len));
993
994  *window_len = inslen + newlen + header_len;
995  return SVN_NO_ERROR;
996}
997
998typedef struct svndiff_stream_baton_t
999{
1000  apr_pool_t *scratch_pool;
1001  svn_txdelta_stream_t *txstream;
1002  svn_txdelta_window_handler_t handler;
1003  void *handler_baton;
1004  svn_stringbuf_t *window_buffer;
1005  apr_size_t read_pos;
1006  svn_boolean_t hit_eof;
1007} svndiff_stream_baton_t;
1008
1009static svn_error_t *
1010svndiff_stream_write_fn(void *baton, const char *data, apr_size_t *len)
1011{
1012  svndiff_stream_baton_t *b = baton;
1013
1014  /* The memory usage here is limited, as this buffer doesn't grow
1015     beyond the (header size + max window size in svndiff format).
1016     See the comment in svn_txdelta_to_svndiff_stream().  */
1017  svn_stringbuf_appendbytes(b->window_buffer, data, *len);
1018
1019  return SVN_NO_ERROR;
1020}
1021
1022static svn_error_t *
1023svndiff_stream_read_fn(void *baton, char *buffer, apr_size_t *len)
1024{
1025  svndiff_stream_baton_t *b = baton;
1026  apr_size_t left = *len;
1027  apr_size_t read = 0;
1028
1029  while (left)
1030    {
1031      apr_size_t chunk_size;
1032
1033      if (b->read_pos == b->window_buffer->len && !b->hit_eof)
1034        {
1035          svn_txdelta_window_t *window;
1036
1037          svn_pool_clear(b->scratch_pool);
1038          svn_stringbuf_setempty(b->window_buffer);
1039          SVN_ERR(svn_txdelta_next_window(&window, b->txstream,
1040                                          b->scratch_pool));
1041          SVN_ERR(b->handler(window, b->handler_baton));
1042          b->read_pos = 0;
1043
1044          if (!window)
1045            b->hit_eof = TRUE;
1046        }
1047
1048      if (left > b->window_buffer->len - b->read_pos)
1049        chunk_size = b->window_buffer->len - b->read_pos;
1050      else
1051        chunk_size = left;
1052
1053      if (!chunk_size)
1054          break;
1055
1056      memcpy(buffer, b->window_buffer->data + b->read_pos, chunk_size);
1057      b->read_pos += chunk_size;
1058      buffer += chunk_size;
1059      read += chunk_size;
1060      left -= chunk_size;
1061    }
1062
1063  *len = read;
1064  return SVN_NO_ERROR;
1065}
1066
1067svn_stream_t *
1068svn_txdelta_to_svndiff_stream(svn_txdelta_stream_t *txstream,
1069                              int svndiff_version,
1070                              int compression_level,
1071                              apr_pool_t *pool)
1072{
1073  svndiff_stream_baton_t *baton;
1074  svn_stream_t *push_stream;
1075  svn_stream_t *pull_stream;
1076
1077  baton = apr_pcalloc(pool, sizeof(*baton));
1078  baton->scratch_pool = svn_pool_create(pool);
1079  baton->txstream = txstream;
1080  baton->window_buffer = svn_stringbuf_create_empty(pool);
1081  baton->hit_eof = FALSE;
1082  baton->read_pos = 0;
1083
1084  push_stream = svn_stream_create(baton, pool);
1085  svn_stream_set_write(push_stream, svndiff_stream_write_fn);
1086
1087  /* We rely on the implementation detail of the svn_txdelta_to_svndiff3()
1088     function, namely, on how the window_handler() function behaves.
1089     As long as it writes one svndiff window at a time to the target
1090     stream, the memory usage of this function (in other words, how
1091     much data can be accumulated in the internal 'window_buffer')
1092     is limited.  */
1093  svn_txdelta_to_svndiff3(&baton->handler, &baton->handler_baton,
1094                          push_stream, svndiff_version,
1095                          compression_level, pool);
1096
1097  pull_stream = svn_stream_create(baton, pool);
1098  svn_stream_set_read2(pull_stream, NULL, svndiff_stream_read_fn);
1099
1100  return pull_stream;
1101}
1102