blob: d7d6e69dac082c49d4753ff1cbd47adc3ad975b1 [file] [log] [blame]
/*
* svndiff.c -- Encoding and decoding svndiff-format deltas.
*
* ====================================================================
* Copyright (c) 2000-2002 CollabNet. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://subversion.tigris.org/license-1.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
*
* This software consists of voluntary contributions made by many
* individuals. For exact contribution history, see the revision
* history and logs, available at http://subversion.tigris.org/.
* ====================================================================
*/
#include <assert.h>
#include <string.h>
#include "svn_delta.h"
#include "svn_io.h"
#include "delta.h"
#include "svn_pools.h"
#define NORMAL_BITS 7
#define LENGTH_BITS 5
/* ----- Text delta to svndiff ----- */
/* We make one of these and get it passed back to us in calls to the
window handler. We only use it to record the write function and
baton passed to svn_txdelta_to_svndiff (). */
struct encoder_baton {
svn_stream_t *output;
svn_boolean_t header_done;
apr_pool_t *pool;
};
/* Encode VAL into the buffer P using the variable-length svndiff
integer format. Return the incremented value of P after the
encoded bytes have been written.
This encoding uses the high bit of each byte as a continuation bit
and the other seven bits as data bits. High-order data bits are
encoded first, followed by lower-order bits, so the value can be
reconstructed by concatenating the data bits from left to right and
interpreting the result as a binary number. Examples (brackets
denote byte boundaries, spaces are for clarity only):
1 encodes as [0 0000001]
33 encodes as [0 0100001]
129 encodes as [1 0000001] [0 0000001]
2000 encodes as [1 0001111] [0 1010000]
*/
static char *
encode_int (char *p, apr_off_t val)
{
int n;
apr_off_t v;
unsigned char cont;
assert (val >= 0);
/* Figure out how many bytes we'll need. */
v = val >> 7;
n = 1;
while (v > 0)
{
v = v >> 7;
n++;
}
/* Encode the remaining bytes; n is always the number of bytes
coming after the one we're encoding. */
while (--n >= 0)
{
cont = ((n > 0) ? 0x1 : 0x0) << 7;
*p++ = ((val >> (n * 7)) & 0x7f) | cont;
}
return p;
}
/* Append an encoded integer to a string. */
static void
append_encoded_int (svn_stringbuf_t *header, apr_off_t val, apr_pool_t *pool)
{
char buf[128], *p;
p = encode_int (buf, val);
svn_stringbuf_appendbytes (header, buf, p - buf);
}
static svn_error_t *
window_handler (svn_txdelta_window_t *window, void *baton)
{
struct encoder_baton *eb = baton;
apr_pool_t *pool = svn_pool_create (eb->pool);
svn_stringbuf_t *instructions = svn_stringbuf_create ("", pool);
svn_stringbuf_t *header = svn_stringbuf_create ("", pool);
char ibuf[128], *ip;
const svn_txdelta_op_t *op;
svn_error_t *err;
apr_size_t len;
/* Make sure we write the header. */
if (eb->header_done == FALSE)
{
len = 4;
err = svn_stream_write (eb->output, "SVN\0", &len);
if (err != SVN_NO_ERROR)
return err;
eb->header_done = TRUE;
}
if (window == NULL)
{
svn_stream_t *output = eb->output;
/* We're done; clean up.
We clean our pool first. Given that the output stream was passed
TO us, we'll assume it has a longer lifetime, and that it will not
be affected by our pool destruction.
The contrary point of view (close the stream first): that could
tell our user that everything related to the output stream is done,
and a cleanup of the user pool should occur. However, that user
pool could include the subpool we created for our work (eb->pool),
which would then make our call to svn_pool_destroy() puke.
*/
svn_pool_destroy (eb->pool);
return svn_stream_close (output);
}
/* Encode the instructions. */
for (op = window->ops; op < window->ops + window->num_ops; op++)
{
/* Encode the action code and length. */
ip = ibuf;
switch (op->action_code)
{
case svn_txdelta_source: *ip = (char)0; break;
case svn_txdelta_target: *ip = (char)(0x1 << 6); break;
case svn_txdelta_new: *ip = (char)(0x2 << 6); break;
}
if (op->length >> 6 == 0)
*ip++ |= op->length;
else
ip = encode_int (ip + 1, op->length);
if (op->action_code != svn_txdelta_new)
ip = encode_int (ip, op->offset);
svn_stringbuf_appendbytes (instructions, ibuf, ip - ibuf);
}
/* Encode the header. */
append_encoded_int (header, window->sview_offset, pool);
append_encoded_int (header, window->sview_len, pool);
append_encoded_int (header, window->tview_len, pool);
append_encoded_int (header, instructions->len, pool);
append_encoded_int (header, window->new_data->len, pool);
/* Write out the window. */
len = header->len;
err = svn_stream_write (eb->output, header->data, &len);
if (err == SVN_NO_ERROR && instructions->len > 0)
{
len = instructions->len;
err = svn_stream_write (eb->output, instructions->data, &len);
}
if (err == SVN_NO_ERROR && window->new_data->len > 0)
{
len = window->new_data->len;
err = svn_stream_write (eb->output, window->new_data->data, &len);
}
svn_pool_destroy (pool);
return err;
}
void
svn_txdelta_to_svndiff (svn_stream_t *output,
apr_pool_t *pool,
svn_txdelta_window_handler_t *handler,
void **handler_baton)
{
apr_pool_t *subpool = svn_pool_create (pool);
struct encoder_baton *eb;
eb = apr_palloc (subpool, sizeof (*eb));
eb->output = output;
eb->header_done = FALSE;
eb->pool = subpool;
*handler = window_handler;
*handler_baton = eb;
}
/* ----- svndiff to text delta ----- */
/* An svndiff parser object. */
struct decode_baton
{
/* Once the svndiff parser has enough data buffered to create a
"window", it passes this window to the caller's consumer routine. */
svn_txdelta_window_handler_t consumer_func;
void *consumer_baton;
/* Pool to create subpools from; each developing window will be a
subpool. */
apr_pool_t *pool;
/* The current subpool which contains our current window-buffer. */
apr_pool_t *subpool;
/* The actual svndiff data buffer, living within subpool. */
svn_stringbuf_t *buffer;
/* The offset and size of the last source view, so that we can check
to make sure the next one isn't sliding backwards. */
apr_off_t last_sview_offset;
apr_size_t last_sview_len;
/* We have to discard four bytes at the beginning for the header.
This field keeps track of how many of those bytes we have read. */
int header_bytes;
/* Do we want an error to occur when we close the stream that
indicates we didn't send the whole svndiff data? If you plan to
not transmit the whole svndiff data stream, you will want this to
be FALSE. */
svn_boolean_t error_on_early_close;
};
/* Decode an svndiff-encoded integer into VAL and return a pointer to
the byte after the integer. The bytes to be decoded live in the
range [P..END-1]. See the comment for encode_int earlier in this
file for more detail on the encoding format. */
static const unsigned char *
decode_int (apr_off_t *val,
const unsigned char *p,
const unsigned char *end)
{
/* Decode bytes until we're done. */
*val = 0;
while (p < end)
{
*val = (*val << 7) | (*p & 0x7f);
if (((*p++ >> 7) & 0x1) == 0)
return p;
}
return NULL;
}
/* Decode an instruction into OP, returning a pointer to the text
after the instruction. Note that if the action code is
svn_txdelta_new, the opcode field of *OP will not be set. */
static const unsigned char *
decode_instruction (svn_txdelta_op_t *op,
const unsigned char *p,
const unsigned char *end)
{
apr_off_t val;
if (p == end)
return NULL;
/* Decode the instruction selector. */
switch ((*p >> 6) & 0x3)
{
case 0x0: op->action_code = svn_txdelta_source; break;
case 0x1: op->action_code = svn_txdelta_target; break;
case 0x2: op->action_code = svn_txdelta_new; break;
case 0x3: return NULL;
}
/* Decode the length and offset. */
op->length = *p++ & 0x3f;
if (op->length == 0)
{
p = decode_int (&val, p, end);
if (p == NULL)
return NULL;
op->length = val;
}
if (op->action_code != svn_txdelta_new)
{
p = decode_int (&val, p, end);
if (p == NULL)
return NULL;
op->offset = val;
}
return p;
}
/* Count the instructions in the range [P..END-1] and make sure they
are valid for the given window lengths. Return -1 if the
instructions are invalid; otherwise return the number of
instructions. */
static int
count_and_verify_instructions (const unsigned char *p,
const unsigned char *end,
apr_size_t sview_len,
apr_size_t tview_len,
apr_size_t new_len)
{
int n = 0;
svn_txdelta_op_t op;
apr_size_t tpos = 0, npos = 0;
while (p < end)
{
p = decode_instruction (&op, p, end);
if (p == NULL || op.length < 0 || op.length > tview_len - tpos)
return -1;
switch (op.action_code)
{
case svn_txdelta_source:
if (op.offset < 0 || op.length > sview_len - op.offset)
return -1;
break;
case svn_txdelta_target:
if (op.offset < 0 || op.offset >= tpos)
return -1;
break;
case svn_txdelta_new:
if (op.length > new_len - npos)
return -1;
npos += op.length;
break;
}
tpos += op.length;
if (tpos < 0)
return -1;
n++;
}
if (tpos != tview_len || npos != new_len)
return -1;
return n;
}
static svn_error_t *
write_handler (void *baton,
const char *buffer,
apr_size_t *len)
{
struct decode_baton *db = (struct decode_baton *) baton;
const unsigned char *p, *end;
apr_off_t val, sview_offset;
apr_size_t sview_len, tview_len, inslen, newlen, remaining, npos;
svn_txdelta_op_t *op;
int ninst;
/* Chew up four bytes at the beginning for the header. */
if (db->header_bytes < 4)
{
apr_size_t nheader = 4 - db->header_bytes;
if (nheader > *len)
nheader = *len;
if (memcmp (buffer, "SVN\0" + db->header_bytes, nheader) != 0)
return svn_error_create (SVN_ERR_SVNDIFF_INVALID_HEADER,
0, NULL, db->pool,
"svndiff has invalid header");
*len -= nheader;
buffer += nheader;
db->header_bytes += nheader;
}
/* Concatenate the old with the new. */
svn_stringbuf_appendbytes (db->buffer, buffer, *len);
/* We have a buffer of svndiff data that might be good for:
a) an integral number of windows' worth of data - this is a
trivial case. Make windows from our data and ship them off.
b) a non-integral number of windows' worth of data - we shall
consume the integral portion of the window data, and then
somewhere in the following loop the decoding of the svndiff
data will run out of stuff to decode, and will simply return
SVN_NO_ERROR, anxiously awaiting more data.
*/
while (1)
{
apr_pool_t *newpool;
svn_txdelta_window_t window = { 0 };
svn_string_t new_data;
svn_txdelta_op_t *ops;
/* Read the header, if we have enough bytes for that. */
p = (const unsigned char *) db->buffer->data;
end = (const unsigned char *) db->buffer->data + db->buffer->len;
p = decode_int (&val, p, end);
if (p == NULL)
return SVN_NO_ERROR;
sview_offset = val;
p = decode_int (&val, p, end);
if (p == NULL)
return SVN_NO_ERROR;
sview_len = val;
p = decode_int (&val, p, end);
if (p == NULL)
return SVN_NO_ERROR;
tview_len = val;
p = decode_int (&val, p, end);
if (p == NULL)
return SVN_NO_ERROR;
inslen = val;
p = decode_int (&val, p, end);
if (p == NULL)
return SVN_NO_ERROR;
newlen = val;
/* Check for integer overflow (don't want to let the input trick
us into invalid pointer games using negative numbers). */
/* FIXME: Some of these are apr_size_t, which is
unsigned. Should they be apr_ptrdiff_t instead? --xbc */
if (sview_offset < 0 || sview_len < 0 || tview_len < 0 || inslen < 0
|| newlen < 0 || inslen + newlen < 0 || sview_offset + sview_len < 0)
return svn_error_create (SVN_ERR_SVNDIFF_CORRUPT_WINDOW, 0, NULL,
db->pool,
"svndiff contains corrupt window header");
/* Check for source windows which slide backwards. */
if (sview_offset < db->last_sview_offset
|| (sview_offset + sview_len
< db->last_sview_offset + db->last_sview_len))
return svn_error_create (SVN_ERR_SVNDIFF_BACKWARD_VIEW, 0, NULL,
db->pool,
"svndiff has backwards-sliding source views");
/* Wait for more data if we don't have enough bytes for the
whole window. */
if ((apr_size_t) (end - p) < inslen + newlen)
return SVN_NO_ERROR;
/* Count the instructions and make sure they are all valid. */
end = p + inslen;
ninst = count_and_verify_instructions (p, end, sview_len,
tview_len, newlen);
if (ninst == -1)
return svn_error_create (SVN_ERR_SVNDIFF_INVALID_OPS, 0, NULL,
db->pool,
"svndiff contains invalid instructions");
/* Build the window structure. */
window.sview_offset = sview_offset;
window.sview_len = sview_len;
window.tview_len = tview_len;
ops = apr_palloc (db->subpool, ninst * sizeof (*ops));
npos = 0;
for (op = ops; op < ops + ninst; op++)
{
p = decode_instruction (op, p, end);
if (op->action_code == svn_txdelta_new)
{
op->offset = npos;
npos += op->length;
}
}
window.num_ops = ninst;
window.ops = ops;
new_data.data = (const char *)p;
new_data.len = newlen;
window.new_data = &new_data;
/* Send it off. */
SVN_ERR(db->consumer_func (&window, db->consumer_baton));
/* Make a new subpool and buffer, saving aside the remaining
data in the old buffer. */
newpool = svn_pool_create (db->pool);
p += newlen;
remaining = db->buffer->data + db->buffer->len - (const char *) p;
db->buffer =
svn_stringbuf_ncreate ((const char *) p, remaining, newpool);
/* Remember the offset and length of the source view for next time. */
db->last_sview_offset = sview_offset;
db->last_sview_len = sview_len;
/* We've copied stuff out of the old pool. Toss that pool and use
our new pool.
### might be nice to avoid the copy and just use svn_pool_clear
### to get rid of whatever the "other stuff" is. future project...
*/
svn_pool_destroy(db->subpool);
db->subpool = newpool;
}
return SVN_NO_ERROR;
}
static svn_error_t *
close_handler (void *baton)
{
struct decode_baton *db = (struct decode_baton *) baton;
svn_error_t *err;
/* Make sure that we're at a plausible end of stream, returning an
error if we are expected to do so. */
if ((db->error_on_early_close)
&& (db->header_bytes < 4 || db->buffer->len != 0))
return svn_error_create (SVN_ERR_SVNDIFF_UNEXPECTED_END, 0, NULL, db->pool,
"unexpected end of svndiff input");
/* Tell the window consumer that we're done, and clean up. */
err = db->consumer_func (NULL, db->consumer_baton);
svn_pool_destroy (db->pool);
return err;
}
svn_stream_t *
svn_txdelta_parse_svndiff (svn_txdelta_window_handler_t handler,
void *handler_baton,
svn_boolean_t error_on_early_close,
apr_pool_t *pool)
{
apr_pool_t *subpool = svn_pool_create (pool);
struct decode_baton *db = apr_palloc (pool, sizeof (*db));
svn_stream_t *stream;
db->consumer_func = handler;
db->consumer_baton = handler_baton;
db->pool = subpool;
db->subpool = svn_pool_create (subpool);
db->buffer = svn_stringbuf_create ("", db->subpool);
db->last_sview_offset = 0;
db->last_sview_len = 0;
db->header_bytes = 0;
db->error_on_early_close = error_on_early_close;
stream = svn_stream_create (db, pool);
svn_stream_set_write (stream, write_handler);
svn_stream_set_close (stream, close_handler);
return stream;
}
/*
* local variables:
* eval: (load-file "../../tools/dev/svn-dev.el")
* end: */