1289177Speter/*
2289177Speter * binary_diff.c:  handling of git like binary diffs
3289177Speter *
4289177Speter * ====================================================================
5289177Speter *    Licensed to the Apache Software Foundation (ASF) under one
6289177Speter *    or more contributor license agreements.  See the NOTICE file
7289177Speter *    distributed with this work for additional information
8289177Speter *    regarding copyright ownership.  The ASF licenses this file
9289177Speter *    to you under the Apache License, Version 2.0 (the
10289177Speter *    "License"); you may not use this file except in compliance
11289177Speter *    with the License.  You may obtain a copy of the License at
12289177Speter *
13289177Speter *      http://www.apache.org/licenses/LICENSE-2.0
14289177Speter *
15289177Speter *    Unless required by applicable law or agreed to in writing,
16289177Speter *    software distributed under the License is distributed on an
17289177Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18289177Speter *    KIND, either express or implied.  See the License for the
19289177Speter *    specific language governing permissions and limitations
20289177Speter *    under the License.
21289177Speter * ====================================================================
22289177Speter */
23289177Speter
24289177Speter#include <apr.h>
25289177Speter
26289177Speter#include "svn_pools.h"
27289177Speter#include "svn_error.h"
28289177Speter#include "svn_diff.h"
29289177Speter#include "svn_types.h"
30289177Speter
31289177Speter/* Copies the data from ORIGINAL_STREAM to a temporary file, returning both
32289177Speter   the original and compressed size. */
33289177Speterstatic svn_error_t *
34289177Spetercreate_compressed(apr_file_t **result,
35289177Speter                  svn_filesize_t *full_size,
36289177Speter                  svn_filesize_t *compressed_size,
37289177Speter                  svn_stream_t *original_stream,
38289177Speter                  svn_cancel_func_t cancel_func,
39289177Speter                  void *cancel_baton,
40289177Speter                  apr_pool_t *result_pool,
41289177Speter                  apr_pool_t *scratch_pool)
42289177Speter{
43289177Speter  svn_stream_t *compressed;
44289177Speter  svn_filesize_t bytes_read = 0;
45289177Speter  apr_finfo_t finfo;
46289177Speter  apr_size_t rd;
47289177Speter
48289177Speter  SVN_ERR(svn_io_open_uniquely_named(result, NULL, NULL, "diffgz",
49289177Speter                                     NULL, svn_io_file_del_on_pool_cleanup,
50289177Speter                                     result_pool, scratch_pool));
51289177Speter
52289177Speter  compressed = svn_stream_compressed(
53289177Speter                  svn_stream_from_aprfile2(*result, TRUE, scratch_pool),
54289177Speter                  scratch_pool);
55289177Speter
56289177Speter  if (original_stream)
57289177Speter    do
58289177Speter    {
59289177Speter      char buffer[SVN_STREAM_CHUNK_SIZE];
60289177Speter      rd = sizeof(buffer);
61289177Speter
62289177Speter      if (cancel_func)
63289177Speter        SVN_ERR(cancel_func(cancel_baton));
64289177Speter
65289177Speter      SVN_ERR(svn_stream_read_full(original_stream, buffer, &rd));
66289177Speter
67289177Speter      bytes_read += rd;
68289177Speter      SVN_ERR(svn_stream_write(compressed, buffer, &rd));
69289177Speter    }
70289177Speter    while(rd == SVN_STREAM_CHUNK_SIZE);
71289177Speter  else
72289177Speter    {
73289177Speter      apr_size_t zero = 0;
74289177Speter      SVN_ERR(svn_stream_write(compressed, NULL, &zero));
75289177Speter    }
76289177Speter
77289177Speter  SVN_ERR(svn_stream_close(compressed)); /* Flush compression */
78289177Speter
79289177Speter  *full_size = bytes_read;
80289177Speter  SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_SIZE, *result, scratch_pool));
81289177Speter  *compressed_size = finfo.size;
82289177Speter
83289177Speter  return SVN_NO_ERROR;
84289177Speter}
85289177Speter
86289177Speter#define GIT_BASE85_CHUNKSIZE 52
87289177Speter
88289177Speter/* Git Base-85 table for write_literal */
89289177Speterstatic const char b85str[] =
90289177Speter    "0123456789"
91289177Speter    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
92289177Speter    "abcdefghijklmnopqrstuvwxyz"
93289177Speter    "!#$%&()*+-;<=>?@^_`{|}~";
94289177Speter
95289177Speter/* Git length encoding table for write_literal */
96289177Speterstatic const char b85lenstr[] =
97289177Speter    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
98289177Speter    "abcdefghijklmnopqrstuvwxyz";
99289177Speter
100289177Speter/* Writes out a git-like literal output of the compressed data in
101289177Speter   COMPRESSED_DATA to OUTPUT_STREAM, describing that its normal length is
102289177Speter   UNCOMPRESSED_SIZE. */
103289177Speterstatic svn_error_t *
104289177Speterwrite_literal(svn_filesize_t uncompressed_size,
105289177Speter              svn_stream_t *compressed_data,
106289177Speter              svn_stream_t *output_stream,
107289177Speter              svn_cancel_func_t cancel_func,
108289177Speter              void *cancel_baton,
109289177Speter              apr_pool_t *scratch_pool)
110289177Speter{
111289177Speter  apr_size_t rd;
112289177Speter  SVN_ERR(svn_stream_seek(compressed_data, NULL)); /* Seek to start */
113289177Speter
114289177Speter  SVN_ERR(svn_stream_printf(output_stream, scratch_pool,
115289177Speter                            "literal %" SVN_FILESIZE_T_FMT APR_EOL_STR,
116289177Speter                            uncompressed_size));
117289177Speter
118289177Speter  do
119289177Speter    {
120289177Speter      char chunk[GIT_BASE85_CHUNKSIZE];
121289177Speter      const unsigned char *next;
122289177Speter      apr_size_t left;
123289177Speter
124289177Speter      rd = sizeof(chunk);
125289177Speter
126289177Speter      if (cancel_func)
127289177Speter        SVN_ERR(cancel_func(cancel_baton));
128289177Speter
129289177Speter      SVN_ERR(svn_stream_read_full(compressed_data, chunk, &rd));
130289177Speter
131289177Speter      {
132289177Speter        apr_size_t one = 1;
133289177Speter        SVN_ERR(svn_stream_write(output_stream, &b85lenstr[rd-1], &one));
134289177Speter      }
135289177Speter
136289177Speter      left = rd;
137289177Speter      next = (void*)chunk;
138289177Speter      while (left)
139289177Speter      {
140289177Speter        char five[5];
141289177Speter        unsigned info = 0;
142289177Speter        int n;
143289177Speter        apr_size_t five_sz;
144289177Speter
145289177Speter        /* Push 4 bytes into the 32 bit info, when available */
146289177Speter        for (n = 24; n >= 0 && left; n -= 8, next++, left--)
147289177Speter        {
148289177Speter            info |= (*next) << n;
149289177Speter        }
150289177Speter
151289177Speter        /* Write out info as base85 */
152289177Speter        for (n = 4; n >= 0; n--)
153289177Speter        {
154289177Speter            five[n] = b85str[info % 85];
155289177Speter            info /= 85;
156289177Speter        }
157289177Speter
158289177Speter        five_sz = 5;
159289177Speter        SVN_ERR(svn_stream_write(output_stream, five, &five_sz));
160289177Speter      }
161289177Speter
162289177Speter      SVN_ERR(svn_stream_puts(output_stream, APR_EOL_STR));
163289177Speter    }
164289177Speter  while (rd == GIT_BASE85_CHUNKSIZE);
165289177Speter
166289177Speter  return SVN_NO_ERROR;
167289177Speter}
168289177Speter
169289177Spetersvn_error_t *
170289177Spetersvn_diff_output_binary(svn_stream_t *output_stream,
171289177Speter                       svn_stream_t *original,
172289177Speter                       svn_stream_t *latest,
173289177Speter                       svn_cancel_func_t cancel_func,
174289177Speter                       void *cancel_baton,
175289177Speter                       apr_pool_t *scratch_pool)
176289177Speter{
177289177Speter  apr_file_t *original_apr;
178289177Speter  svn_filesize_t original_full;
179289177Speter  svn_filesize_t original_deflated;
180289177Speter  apr_file_t *latest_apr;
181289177Speter  svn_filesize_t latest_full;
182289177Speter  svn_filesize_t latest_deflated;
183289177Speter  apr_pool_t *subpool = svn_pool_create(scratch_pool);
184289177Speter
185289177Speter  SVN_ERR(create_compressed(&original_apr, &original_full, &original_deflated,
186289177Speter                            original, cancel_func, cancel_baton,
187289177Speter                            scratch_pool, subpool));
188289177Speter  svn_pool_clear(subpool);
189289177Speter
190289177Speter  SVN_ERR(create_compressed(&latest_apr, &latest_full, &latest_deflated,
191289177Speter                            latest,  cancel_func, cancel_baton,
192289177Speter                            scratch_pool, subpool));
193289177Speter  svn_pool_clear(subpool);
194289177Speter
195289177Speter  SVN_ERR(svn_stream_puts(output_stream, "GIT binary patch" APR_EOL_STR));
196289177Speter
197299742Sdim  /* ### git would first calculate if a git-delta latest->original would be
198289177Speter         shorter than the zipped data. For now lets assume that it is not
199289177Speter         and just dump the literal data */
200299742Sdim  SVN_ERR(write_literal(latest_full,
201299742Sdim                        svn_stream_from_aprfile2(latest_apr, FALSE, subpool),
202289177Speter                        output_stream,
203289177Speter                        cancel_func, cancel_baton,
204289177Speter                        scratch_pool));
205289177Speter  svn_pool_clear(subpool);
206289177Speter  SVN_ERR(svn_stream_puts(output_stream, APR_EOL_STR));
207289177Speter
208299742Sdim  /* ### git would first calculate if a git-delta original->latest would be
209289177Speter         shorter than the zipped data. For now lets assume that it is not
210289177Speter         and just dump the literal data */
211299742Sdim  SVN_ERR(write_literal(original_full,
212299742Sdim                        svn_stream_from_aprfile2(original_apr, FALSE, subpool),
213289177Speter                        output_stream,
214289177Speter                        cancel_func, cancel_baton,
215289177Speter                        scratch_pool));
216289177Speter  svn_pool_destroy(subpool);
217289177Speter
218289177Speter  SVN_ERR(svn_stream_puts(output_stream, APR_EOL_STR));
219289177Speter
220289177Speter  return SVN_NO_ERROR;
221289177Speter}
222