vcs-svn: parse svndiff0 window header
authorJonathan Nieder <jrnieder@gmail.com>
Wed, 13 Oct 2010 09:21:43 +0000 (04:21 -0500)
committerJonathan Nieder <jrnieder@gmail.com>
Mon, 28 Mar 2011 03:42:53 +0000 (22:42 -0500)
Each window in a subversion delta (svndiff0-format file) starts with a
window header, consisting of five integers with variable-length
representation:

source view offset
source view length
output length
instructions length
auxiliary data length

Parse it.  The result is not usable for deltas with nonempty postimage
yet; in fact, this only adds support for deltas without any
instructions or auxiliary data.  This is a good place to stop, though,
since that little support lets us add some simple passing tests
concerning error handling to the test suite.

Improved-by: Ramkumar Ramachandra <artagnon@gmail.com>
Improved-by: David Barr <david.barr@cordelta.com>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
t/t9011-svn-da.sh
vcs-svn/svndiff.c

index ee0c1e2087cf6c0d4061f9f30fa38b17e16827e8..be1234094fc5a5913955ce51973b59aae28f0206 100755 (executable)
@@ -26,10 +26,64 @@ test_expect_success 'reject svndiff2' '
        test_must_fail test-svn-fe -d preimage bad.filetype 4
 '
 
-test_expect_failure 'one-window empty delta' '
+test_expect_success 'one-window empty delta' '
        printf "SVNQ%s" "QQQQQ" | q_to_nul >clear.onewindow &&
        test-svn-fe -d preimage clear.onewindow 9 >actual &&
        test_cmp empty actual
 '
 
+test_expect_success 'reject incomplete window header' '
+       printf "SVNQ%s" "QQQQQ" | q_to_nul >clear.onewindow &&
+       printf "SVNQ%s" "QQ" | q_to_nul >clear.partialwindow &&
+       test_must_fail test-svn-fe -d preimage clear.onewindow 6 &&
+       test_must_fail test-svn-fe -d preimage clear.partialwindow 6
+'
+
+test_expect_success 'reject declared delta longer than actual delta' '
+       printf "SVNQ%s" "QQQQQ" | q_to_nul >clear.onewindow &&
+       printf "SVNQ%s" "QQ" | q_to_nul >clear.partialwindow &&
+       test_must_fail test-svn-fe -d preimage clear.onewindow 14 &&
+       test_must_fail test-svn-fe -d preimage clear.partialwindow 9
+'
+
+test_expect_success 'two-window empty delta' '
+       printf "SVNQ%s%s" "QQQQQ" "QQQQQ" | q_to_nul >clear.twowindow &&
+       test-svn-fe -d preimage clear.twowindow 14 >actual &&
+       test_must_fail test-svn-fe -d preimage clear.twowindow 13 &&
+       test_cmp empty actual
+'
+
+test_expect_success 'noisy zeroes' '
+       printf "SVNQ%s" \
+               "RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQ" |
+               tr R "\200" |
+               q_to_nul >clear.noisy &&
+       len=$(wc -c <clear.noisy) &&
+       test-svn-fe -d preimage clear.noisy $len &&
+       test_cmp empty actual
+'
+
+test_expect_success 'reject variable-length int in magic' '
+       printf "SVNRQ" | tr R "\200" | q_to_nul >clear.badmagic &&
+       test_must_fail test-svn-fe -d preimage clear.badmagic 5
+'
+
+test_expect_success 'reject truncated integer' '
+       printf "SVNQ%s%s" "QQQQQ" "QQQQRRQ" |
+               tr R "\200" |
+               q_to_nul >clear.fullint &&
+       printf "SVNQ%s%s" "QQQQQ" "QQQQRR" |
+               tr RT "\201" |
+               q_to_nul >clear.partialint &&
+       test_must_fail test-svn-fe -d preimage clear.fullint 15 &&
+       test-svn-fe -d preimage clear.fullint 16 &&
+       test_must_fail test-svn-fe -d preimage clear.partialint 15
+'
+
+test_expect_success 'nonempty (but unused) preimage view' '
+       printf "SVNQ%b" "Q\003QQQ" | q_to_nul >clear.readpreimage &&
+       test-svn-fe -d preimage clear.readpreimage 9 >actual &&
+       test_cmp empty actual
+'
+
 test_done
index 591603669c733e9b36ef614b72598ee6a0e5e235..249efb6eeda1b6fe7eff488cdeedbaf74fddff73 100644 (file)
  * See http://svn.apache.org/repos/asf/subversion/trunk/notes/svndiff.
  *
  * svndiff0 ::= 'SVN\0' window*
+ * window ::= int int int int int instructions inline_data;
+ * int ::= highdigit* lowdigit;
+ * highdigit ::= # binary 1000 0000 OR-ed with 7 bit value;
+ * lowdigit ::= # 7 bit value;
  */
 
+#define VLI_CONTINUE   0x80
+#define VLI_DIGIT_MASK 0x7f
+#define VLI_BITS_PER_DIGIT 7
+
 static int error_short_read(struct line_buffer *input)
 {
        if (buffer_ferror(input))
@@ -28,17 +36,84 @@ static int read_magic(struct line_buffer *in, off_t *len)
        struct strbuf sb = STRBUF_INIT;
 
        if (*len < sizeof(magic) ||
-           buffer_read_binary(in, &sb, sizeof(magic)) != sizeof(magic))
-               return error_short_read(in);
+           buffer_read_binary(in, &sb, sizeof(magic)) != sizeof(magic)) {
+               error_short_read(in);
+               strbuf_release(&sb);
+               return -1;
+       }
 
-       if (memcmp(sb.buf, magic, sizeof(magic)))
+       if (memcmp(sb.buf, magic, sizeof(magic))) {
+               strbuf_release(&sb);
                return error("invalid delta: unrecognized file type");
+       }
 
        *len -= sizeof(magic);
        strbuf_release(&sb);
        return 0;
 }
 
+static int read_int(struct line_buffer *in, uintmax_t *result, off_t *len)
+{
+       uintmax_t rv = 0;
+       off_t sz;
+       for (sz = *len; sz; sz--) {
+               const int ch = buffer_read_char(in);
+               if (ch == EOF)
+                       break;
+
+               rv <<= VLI_BITS_PER_DIGIT;
+               rv += (ch & VLI_DIGIT_MASK);
+               if (ch & VLI_CONTINUE)
+                       continue;
+
+               *result = rv;
+               *len = sz - 1;
+               return 0;
+       }
+       return error_short_read(in);
+}
+
+static int read_offset(struct line_buffer *in, off_t *result, off_t *len)
+{
+       uintmax_t val;
+       if (read_int(in, &val, len))
+               return -1;
+       if (val > maximum_signed_value_of_type(off_t))
+               return error("unrepresentable offset in delta: %"PRIuMAX"", val);
+       *result = val;
+       return 0;
+}
+
+static int read_length(struct line_buffer *in, size_t *result, off_t *len)
+{
+       uintmax_t val;
+       if (read_int(in, &val, len))
+               return -1;
+       if (val > SIZE_MAX)
+               return error("unrepresentable length in delta: %"PRIuMAX"", val);
+       *result = val;
+       return 0;
+}
+
+static int apply_one_window(struct line_buffer *delta, off_t *delta_len)
+{
+       size_t out_len;
+       size_t instructions_len;
+       size_t data_len;
+       assert(delta_len);
+
+       /* "source view" offset and length already handled; */
+       if (read_length(delta, &out_len, delta_len) ||
+           read_length(delta, &instructions_len, delta_len) ||
+           read_length(delta, &data_len, delta_len))
+               return -1;
+       if (instructions_len)
+               return error("What do you think I am?  A delta applier?");
+       if (data_len)
+               return error("No support for inline data yet");
+       return 0;
+}
+
 int svndiff0_apply(struct line_buffer *delta, off_t delta_len,
                        struct sliding_view *preimage, FILE *postimage)
 {
@@ -46,7 +121,14 @@ int svndiff0_apply(struct line_buffer *delta, off_t delta_len,
 
        if (read_magic(delta, &delta_len))
                return -1;
-       if (delta_len)
-               return error("What do you think I am?  A delta applier?");
+       while (delta_len) {     /* For each window: */
+               off_t pre_off;
+               size_t pre_len;
+
+               if (read_offset(delta, &pre_off, &delta_len) ||
+                   read_length(delta, &pre_len, &delta_len) ||
+                   apply_one_window(delta, &delta_len))
+                       return -1;
+       }
        return 0;
 }