|
@@ -28,12 +28,19 @@
|
|
|
* and will send small couples of slices to calc_changes, keeping the running
|
|
|
* time near-linear. This is explained in more detail in the gen_ed_diff
|
|
|
* comments.
|
|
|
+ *
|
|
|
+ * The allocation strategy tries to save time and memory by avoiding needless
|
|
|
+ * copies. Instead of actually splitting the inputs into separate strings, we
|
|
|
+ * allocate cdline_t objects, each of which represents a line in the original
|
|
|
+ * object or in the output. We use memarea_t allocators to manage the
|
|
|
+ * temporary memory we use when generating or applying diffs.
|
|
|
**/
|
|
|
|
|
|
#define CONSDIFF_PRIVATE
|
|
|
|
|
|
#include "or.h"
|
|
|
#include "consdiff.h"
|
|
|
+#include "memarea.h"
|
|
|
#include "routerparse.h"
|
|
|
|
|
|
static const char* ns_diff_version = "network-status-diff-version 1";
|
|
@@ -41,6 +48,36 @@ static const char* hash_token = "hash";
|
|
|
|
|
|
static char *consensus_join_lines(const smartlist_t *inp);
|
|
|
|
|
|
+/** Return true iff a and b have the same contents. */
|
|
|
+STATIC int
|
|
|
+lines_eq(const cdline_t *a, const cdline_t *b)
|
|
|
+{
|
|
|
+ return a->len == b->len && fast_memeq(a->s, b->s, a->len);
|
|
|
+}
|
|
|
+
|
|
|
+/** Return true iff a has the same contents as the nul-terminated string b. */
|
|
|
+STATIC int
|
|
|
+line_str_eq(const cdline_t *a, const char *b)
|
|
|
+{
|
|
|
+ const size_t len = strlen(b);
|
|
|
+ tor_assert(len <= UINT32_MAX);
|
|
|
+ cdline_t bline = { b, (uint32_t)len };
|
|
|
+ return lines_eq(a, &bline);
|
|
|
+}
|
|
|
+
|
|
|
+/** Add a cdline_t to <b>lst</b> holding as its contents the nul-terminated
|
|
|
+ * string s. Use the provided memory area for storage. */
|
|
|
+STATIC void
|
|
|
+smartlist_add_linecpy(smartlist_t *lst, memarea_t *area, const char *s)
|
|
|
+{
|
|
|
+ size_t len = strlen(s);
|
|
|
+ const char *ss = memarea_memdup(area, s, len);
|
|
|
+ cdline_t *line = memarea_alloc(area, sizeof(cdline_t));
|
|
|
+ line->s = ss;
|
|
|
+ line->len = (uint32_t)len;
|
|
|
+ smartlist_add(lst, line);
|
|
|
+}
|
|
|
+
|
|
|
/** Compute the digest of <b>cons</b>, and store the result in
|
|
|
* <b>digest_out</b>. Return 0 on success, -1 on failure. */
|
|
|
/* This is a separate, mockable function so that we can override it when
|
|
@@ -114,7 +151,7 @@ lcs_lengths(const smartlist_slice_t *slice1, const smartlist_slice_t *slice2,
|
|
|
|
|
|
for (int i = 0; i < slice1->len; ++i, si+=direction) {
|
|
|
|
|
|
- const char *line1 = smartlist_get(slice1->list, si);
|
|
|
+ const cdline_t *line1 = smartlist_get(slice1->list, si);
|
|
|
/* Store the last results. */
|
|
|
memcpy(prev, result, a_size);
|
|
|
|
|
@@ -125,8 +162,8 @@ lcs_lengths(const smartlist_slice_t *slice1, const smartlist_slice_t *slice2,
|
|
|
|
|
|
for (int j = 0; j < slice2->len; ++j, sj+=direction) {
|
|
|
|
|
|
- const char *line2 = smartlist_get(slice2->list, sj);
|
|
|
- if (!strcmp(line1, line2)) {
|
|
|
+ const cdline_t *line2 = smartlist_get(slice2->list, sj);
|
|
|
+ if (lines_eq(line1, line2)) {
|
|
|
/* If the lines are equal, the lcs is one line longer. */
|
|
|
result[j + 1] = prev[j] + 1;
|
|
|
} else {
|
|
@@ -146,9 +183,9 @@ STATIC void
|
|
|
trim_slices(smartlist_slice_t *slice1, smartlist_slice_t *slice2)
|
|
|
{
|
|
|
while (slice1->len>0 && slice2->len>0) {
|
|
|
- const char *line1 = smartlist_get(slice1->list, slice1->offset);
|
|
|
- const char *line2 = smartlist_get(slice2->list, slice2->offset);
|
|
|
- if (strcmp(line1, line2)) {
|
|
|
+ const cdline_t *line1 = smartlist_get(slice1->list, slice1->offset);
|
|
|
+ const cdline_t *line2 = smartlist_get(slice2->list, slice2->offset);
|
|
|
+ if (!lines_eq(line1, line2)) {
|
|
|
break;
|
|
|
}
|
|
|
slice1->offset++; slice1->len--;
|
|
@@ -159,9 +196,9 @@ trim_slices(smartlist_slice_t *slice1, smartlist_slice_t *slice2)
|
|
|
int i2 = (slice2->offset+slice2->len)-1;
|
|
|
|
|
|
while (slice1->len>0 && slice2->len>0) {
|
|
|
- const char *line1 = smartlist_get(slice1->list, i1);
|
|
|
- const char *line2 = smartlist_get(slice2->list, i2);
|
|
|
- if (strcmp(line1, line2)) {
|
|
|
+ const cdline_t *line1 = smartlist_get(slice1->list, i1);
|
|
|
+ const cdline_t *line2 = smartlist_get(slice2->list, i2);
|
|
|
+ if (!lines_eq(line1, line2)) {
|
|
|
break;
|
|
|
}
|
|
|
i1--;
|
|
@@ -171,15 +208,17 @@ trim_slices(smartlist_slice_t *slice1, smartlist_slice_t *slice2)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/** Like smartlist_string_pos, but limited to the bounds of the slice.
|
|
|
+/** Like smartlist_string_pos, but uses a cdline_t, and is restricted to the
|
|
|
+ * bounds of the slice.
|
|
|
*/
|
|
|
STATIC int
|
|
|
-smartlist_slice_string_pos(const smartlist_slice_t *slice, const char *string)
|
|
|
+smartlist_slice_string_pos(const smartlist_slice_t *slice,
|
|
|
+ const cdline_t *string)
|
|
|
{
|
|
|
int end = slice->offset + slice->len;
|
|
|
for (int i = slice->offset; i < end; ++i) {
|
|
|
- const char *el = smartlist_get(slice->list, i);
|
|
|
- if (!strcmp(el, string)) {
|
|
|
+ const cdline_t *el = smartlist_get(slice->list, i);
|
|
|
+ if (lines_eq(el, string)) {
|
|
|
return i;
|
|
|
}
|
|
|
}
|
|
@@ -199,7 +238,7 @@ set_changed(bitarray_t *changed1, bitarray_t *changed2,
|
|
|
tor_assert(slice1->len == 0 || slice1->len == 1);
|
|
|
|
|
|
if (slice1->len == 1) {
|
|
|
- const char *line_common = smartlist_get(slice1->list, slice1->offset);
|
|
|
+ const cdline_t *line_common = smartlist_get(slice1->list, slice1->offset);
|
|
|
toskip = smartlist_slice_string_pos(slice2, line_common);
|
|
|
if (toskip == -1) {
|
|
|
bitarray_set(changed1, slice1->offset);
|
|
@@ -318,16 +357,20 @@ static const uint8_t base64_compare_table[256] = {
|
|
|
|
|
|
/** Helper: Get the identity hash from a router line, assuming that the line
|
|
|
* at least appears to be a router line and thus starts with "r ".
|
|
|
+ *
|
|
|
+ * If an identity hash is found, store it (without decoding it) in
|
|
|
+ * <b>hash_out</b>, and return 0. On failure, return -1.
|
|
|
*/
|
|
|
-STATIC const char *
|
|
|
-get_id_hash(const char *r_line)
|
|
|
+STATIC int
|
|
|
+get_id_hash(const cdline_t *line, cdline_t *hash_out)
|
|
|
{
|
|
|
- r_line += strlen("r ");
|
|
|
+ if (line->len < 2)
|
|
|
+ return -1;
|
|
|
|
|
|
/* Skip the router name. */
|
|
|
- const char *hash = strchr(r_line, ' ');
|
|
|
+ const char *hash = memchr(line->s + 2, ' ', line->len - 2);
|
|
|
if (!hash) {
|
|
|
- return NULL;
|
|
|
+ return -1;
|
|
|
}
|
|
|
|
|
|
hash++;
|
|
@@ -335,28 +378,34 @@ get_id_hash(const char *r_line)
|
|
|
/* Stop when the first non-base64 character is found. Use unsigned chars to
|
|
|
* avoid negative indexes causing crashes.
|
|
|
*/
|
|
|
- while (base64_compare_table[*((unsigned char*)hash_end)] != X) {
|
|
|
+ while (base64_compare_table[*((unsigned char*)hash_end)] != X &&
|
|
|
+ hash_end < line->s + line->len) {
|
|
|
hash_end++;
|
|
|
}
|
|
|
|
|
|
/* Empty hash. */
|
|
|
if (hash_end == hash) {
|
|
|
- return NULL;
|
|
|
+ return -1;
|
|
|
}
|
|
|
|
|
|
- return hash;
|
|
|
+ hash_out->s = hash;
|
|
|
+ /* Always true because lines are limited to this length */
|
|
|
+ tor_assert(hash_end - hash <= UINT32_MAX);
|
|
|
+ hash_out->len = (uint32_t)(hash_end - hash);
|
|
|
+
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
/** Helper: Check that a line is a valid router entry. We must at least be
|
|
|
* able to fetch a proper identity hash from it for it to be valid.
|
|
|
*/
|
|
|
STATIC int
|
|
|
-is_valid_router_entry(const char *line)
|
|
|
+is_valid_router_entry(const cdline_t *line)
|
|
|
{
|
|
|
- if (strcmpstart(line, "r ") != 0) {
|
|
|
+ if (line->len < 2 || fast_memneq(line->s, "r ", 2))
|
|
|
return 0;
|
|
|
- }
|
|
|
- return (get_id_hash(line) != NULL);
|
|
|
+ cdline_t tmp;
|
|
|
+ return (get_id_hash(line, &tmp) == 0);
|
|
|
}
|
|
|
|
|
|
/** Helper: Find the next router line starting at the current position.
|
|
@@ -374,7 +423,7 @@ next_router(const smartlist_t *cons, int cur)
|
|
|
return len;
|
|
|
}
|
|
|
|
|
|
- const char *line = smartlist_get(cons, cur);
|
|
|
+ const cdline_t *line = smartlist_get(cons, cur);
|
|
|
while (!is_valid_router_entry(line)) {
|
|
|
if (++cur >= len) {
|
|
|
return len;
|
|
@@ -384,26 +433,28 @@ next_router(const smartlist_t *cons, int cur)
|
|
|
return cur;
|
|
|
}
|
|
|
|
|
|
-/** Helper: compare two base64-encoded identity hashes which may be of
|
|
|
+/** Helper: compare two base64-encoded identity hashes, which may be of
|
|
|
* different lengths. Comparison ends when the first non-base64 char is found.
|
|
|
*/
|
|
|
STATIC int
|
|
|
-base64cmp(const char *hash1, const char *hash2)
|
|
|
+base64cmp(const cdline_t *hash1, const cdline_t *hash2)
|
|
|
{
|
|
|
/* NULL is always lower, useful for last_hash which starts at NULL. */
|
|
|
- if (!hash1 && !hash2) {
|
|
|
+ if (!hash1->s && !hash2->s) {
|
|
|
return 0;
|
|
|
}
|
|
|
- if (!hash1) {
|
|
|
+ if (!hash1->s) {
|
|
|
return -1;
|
|
|
}
|
|
|
- if (!hash2) {
|
|
|
+ if (!hash2->s) {
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
/* Don't index with a char; char may be signed. */
|
|
|
- const unsigned char *a = (unsigned char*)hash1;
|
|
|
- const unsigned char *b = (unsigned char*)hash2;
|
|
|
+ const unsigned char *a = (unsigned char*)hash1->s;
|
|
|
+ const unsigned char *b = (unsigned char*)hash2->s;
|
|
|
+ const unsigned char *a_end = a + hash1->len;
|
|
|
+ const unsigned char *b_end = b + hash2->len;
|
|
|
while (1) {
|
|
|
uint8_t av = base64_compare_table[*a];
|
|
|
uint8_t bv = base64_compare_table[*b];
|
|
@@ -427,6 +478,15 @@ base64cmp(const char *hash1, const char *hash2)
|
|
|
} else {
|
|
|
a++;
|
|
|
b++;
|
|
|
+ if (a == a_end) {
|
|
|
+ if (b == b_end) {
|
|
|
+ return 0;
|
|
|
+ } else {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ } else if (b == b_end) {
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -436,6 +496,9 @@ base64cmp(const char *hash1, const char *hash2)
|
|
|
* happen if any lines the script had to add matched "." or if the routers
|
|
|
* were not properly ordered.
|
|
|
*
|
|
|
+ * All cdline_t objects in the resulting object are either references to lines
|
|
|
+ * in one of the inputs, or are newly allocated lines in the provided memarea.
|
|
|
+ *
|
|
|
* This implementation is consensus-specific. To generate an ed diff for any
|
|
|
* given input in quadratic time, you can replace all the code until the
|
|
|
* navigation in reverse order with the following:
|
|
@@ -449,7 +512,8 @@ base64cmp(const char *hash1, const char *hash2)
|
|
|
* calc_changes(cons1_sl, cons2_sl, changed1, changed2);
|
|
|
*/
|
|
|
STATIC smartlist_t *
|
|
|
-gen_ed_diff(const smartlist_t *cons1, const smartlist_t *cons2)
|
|
|
+gen_ed_diff(const smartlist_t *cons1, const smartlist_t *cons2,
|
|
|
+ memarea_t *area)
|
|
|
{
|
|
|
int len1 = smartlist_len(cons1);
|
|
|
int len2 = smartlist_len(cons2);
|
|
@@ -463,12 +527,9 @@ gen_ed_diff(const smartlist_t *cons1, const smartlist_t *cons2)
|
|
|
int i1=-1, i2=-1;
|
|
|
int start1=0, start2=0;
|
|
|
|
|
|
- const char *hash1 = NULL;
|
|
|
- const char *hash2 = NULL;
|
|
|
-
|
|
|
/* To check that hashes are ordered properly */
|
|
|
- const char *last_hash1 = NULL;
|
|
|
- const char *last_hash2 = NULL;
|
|
|
+ cdline_t hash1 = { NULL, 0 }, hash2 = { NULL, 0 };
|
|
|
+ cdline_t last_hash1 = { NULL, 0 }, last_hash2 = { NULL, 0 };
|
|
|
|
|
|
/* i1 and i2 are initialized at the first line of each consensus. They never
|
|
|
* reach past len1 and len2 respectively, since next_router doesn't let that
|
|
@@ -485,9 +546,9 @@ gen_ed_diff(const smartlist_t *cons1, const smartlist_t *cons2)
|
|
|
if (i1 < len1) {
|
|
|
i1 = next_router(cons1, i1);
|
|
|
if (i1 != len1) {
|
|
|
- last_hash1 = hash1;
|
|
|
- hash1 = get_id_hash(smartlist_get(cons1, i1));
|
|
|
- if (base64cmp(hash1, last_hash1) <= 0) {
|
|
|
+ memcpy(&last_hash1, &hash1, sizeof(cdline_t));
|
|
|
+ if (get_id_hash(smartlist_get(cons1, i1), &hash1) < 0 ||
|
|
|
+ base64cmp(&hash1, &last_hash1) <= 0) {
|
|
|
log_warn(LD_CONSDIFF, "Refusing to generate consensus diff because "
|
|
|
"the base consensus doesn't have its router entries "
|
|
|
"sorted properly.");
|
|
@@ -499,9 +560,9 @@ gen_ed_diff(const smartlist_t *cons1, const smartlist_t *cons2)
|
|
|
if (i2 < len2) {
|
|
|
i2 = next_router(cons2, i2);
|
|
|
if (i2 != len2) {
|
|
|
- last_hash2 = hash2;
|
|
|
- hash2 = get_id_hash(smartlist_get(cons2, i2));
|
|
|
- if (base64cmp(hash2, last_hash2) <= 0) {
|
|
|
+ memcpy(&last_hash2, &hash2, sizeof(cdline_t));
|
|
|
+ if (get_id_hash(smartlist_get(cons2, i2), &hash2) < 0 ||
|
|
|
+ base64cmp(&hash2, &last_hash2) <= 0) {
|
|
|
log_warn(LD_CONSDIFF, "Refusing to generate consensus diff because "
|
|
|
"the target consensus doesn't have its router entries "
|
|
|
"sorted properly.");
|
|
@@ -525,7 +586,7 @@ gen_ed_diff(const smartlist_t *cons1, const smartlist_t *cons2)
|
|
|
* consensus has already reached the end, both are extended to their
|
|
|
* respecting ends since we are done.
|
|
|
*/
|
|
|
- int cmp = base64cmp(hash1, hash2);
|
|
|
+ int cmp = base64cmp(&hash1, &hash2);
|
|
|
while (cmp != 0) {
|
|
|
if (i1 < len1 && cmp < 0) {
|
|
|
i1 = next_router(cons1, i1);
|
|
@@ -536,9 +597,9 @@ gen_ed_diff(const smartlist_t *cons1, const smartlist_t *cons2)
|
|
|
i2 = len2;
|
|
|
break;
|
|
|
}
|
|
|
- last_hash1 = hash1;
|
|
|
- hash1 = get_id_hash(smartlist_get(cons1, i1));
|
|
|
- if (base64cmp(hash1, last_hash1) <= 0) {
|
|
|
+ memcpy(&last_hash1, &hash1, sizeof(cdline_t));
|
|
|
+ if (get_id_hash(smartlist_get(cons1, i1), &hash1) < 0 ||
|
|
|
+ base64cmp(&hash1, &last_hash1) <= 0) {
|
|
|
log_warn(LD_CONSDIFF, "Refusing to generate consensus diff "
|
|
|
"because the base consensus doesn't have its router entries "
|
|
|
"sorted properly.");
|
|
@@ -553,9 +614,9 @@ gen_ed_diff(const smartlist_t *cons1, const smartlist_t *cons2)
|
|
|
i1 = len1;
|
|
|
break;
|
|
|
}
|
|
|
- last_hash2 = hash2;
|
|
|
- hash2 = get_id_hash(smartlist_get(cons2, i2));
|
|
|
- if (base64cmp(hash2, last_hash2) <= 0) {
|
|
|
+ memcpy(&last_hash2, &hash2, sizeof(cdline_t));
|
|
|
+ if (get_id_hash(smartlist_get(cons2, i2), &hash2) < 0 ||
|
|
|
+ base64cmp(&hash2, &last_hash2) <= 0) {
|
|
|
log_warn(LD_CONSDIFF, "Refusing to generate consensus diff "
|
|
|
"because the target consensus doesn't have its router entries "
|
|
|
"sorted properly.");
|
|
@@ -566,7 +627,7 @@ gen_ed_diff(const smartlist_t *cons1, const smartlist_t *cons2)
|
|
|
i2 = len2;
|
|
|
break;
|
|
|
}
|
|
|
- cmp = base64cmp(hash1, hash2);
|
|
|
+ cmp = base64cmp(&hash1, &hash2);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -595,6 +656,7 @@ gen_ed_diff(const smartlist_t *cons1, const smartlist_t *cons2)
|
|
|
* each chunk of changes.
|
|
|
*/
|
|
|
i1=len1-1, i2=len2-1;
|
|
|
+ char buf[128];
|
|
|
while (i1 > 0 || i2 > 0) {
|
|
|
|
|
|
int start1x, start2x, end1, end2, added, deleted;
|
|
@@ -626,31 +688,36 @@ gen_ed_diff(const smartlist_t *cons1, const smartlist_t *cons2)
|
|
|
|
|
|
if (added == 0) {
|
|
|
if (deleted == 1) {
|
|
|
- smartlist_add_asprintf(result, "%id", start1x+1);
|
|
|
+ tor_snprintf(buf, sizeof(buf), "%id", start1x+1);
|
|
|
+ smartlist_add_linecpy(result, area, buf);
|
|
|
} else {
|
|
|
- smartlist_add_asprintf(result, "%i,%id", start1x+1, start1x+deleted);
|
|
|
+ tor_snprintf(buf, sizeof(buf), "%i,%id", start1x+1, start1x+deleted);
|
|
|
+ smartlist_add_linecpy(result, area, buf);
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
int i;
|
|
|
if (deleted == 0) {
|
|
|
- smartlist_add_asprintf(result, "%ia", start1x);
|
|
|
+ tor_snprintf(buf, sizeof(buf), "%ia", start1x);
|
|
|
+ smartlist_add_linecpy(result, area, buf);
|
|
|
} else if (deleted == 1) {
|
|
|
- smartlist_add_asprintf(result, "%ic", start1x+1);
|
|
|
+ tor_snprintf(buf, sizeof(buf), "%ic", start1x+1);
|
|
|
+ smartlist_add_linecpy(result, area, buf);
|
|
|
} else {
|
|
|
- smartlist_add_asprintf(result, "%i,%ic", start1x+1, start1x+deleted);
|
|
|
+ tor_snprintf(buf, sizeof(buf), "%i,%ic", start1x+1, start1x+deleted);
|
|
|
+ smartlist_add_linecpy(result, area, buf);
|
|
|
}
|
|
|
|
|
|
for (i = start2x; i <= end2; ++i) {
|
|
|
- const char *line = smartlist_get(cons2, i);
|
|
|
- if (!strcmp(line, ".")) {
|
|
|
+ cdline_t *line = smartlist_get(cons2, i);
|
|
|
+ if (line_str_eq(line, ".")) {
|
|
|
log_warn(LD_CONSDIFF, "Cannot generate consensus diff because "
|
|
|
"one of the lines to be added is \".\".");
|
|
|
goto error_cleanup;
|
|
|
}
|
|
|
- smartlist_add(result, tor_strdup(line));
|
|
|
+ smartlist_add(result, line);
|
|
|
}
|
|
|
- smartlist_add_asprintf(result, ".");
|
|
|
+ smartlist_add_linecpy(result, area, ".");
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -664,7 +731,6 @@ gen_ed_diff(const smartlist_t *cons1, const smartlist_t *cons2)
|
|
|
bitarray_free(changed1);
|
|
|
bitarray_free(changed2);
|
|
|
|
|
|
- SMARTLIST_FOREACH(result, char*, line, tor_free(line));
|
|
|
smartlist_free(result);
|
|
|
|
|
|
return NULL;
|
|
@@ -673,6 +739,9 @@ gen_ed_diff(const smartlist_t *cons1, const smartlist_t *cons2)
|
|
|
/** Apply the ed diff, starting at <b>diff_starting_line</b>, to the consensus
|
|
|
* and return a new consensus, also as a line-based smartlist. Will return
|
|
|
* NULL if the ed diff is not properly formatted.
|
|
|
+ *
|
|
|
+ * All cdline_t objects in the resulting object are references to lines
|
|
|
+ * in one of the inputs; nothing is copied.
|
|
|
*/
|
|
|
STATIC smartlist_t *
|
|
|
apply_ed_diff(const smartlist_t *cons1, const smartlist_t *diff,
|
|
@@ -683,9 +752,17 @@ apply_ed_diff(const smartlist_t *cons1, const smartlist_t *diff,
|
|
|
smartlist_t *cons2 = smartlist_new();
|
|
|
|
|
|
for (int i=diff_starting_line; i<diff_len; ++i) {
|
|
|
- const char *diff_line = smartlist_get(diff, i);
|
|
|
+ const cdline_t *diff_cdline = smartlist_get(diff, i);
|
|
|
+ char diff_line[128];
|
|
|
char *endptr1, *endptr2;
|
|
|
|
|
|
+ if (diff_cdline->len > sizeof(diff_line) - 1) {
|
|
|
+ log_warn(LD_CONSDIFF, "Could not apply consensus diff because "
|
|
|
+ "an ed command was far too long");
|
|
|
+ goto error_cleanup;
|
|
|
+ }
|
|
|
+ memcpy(diff_line, diff_cdline->s, diff_cdline->len);
|
|
|
+ diff_line[diff_cdline->len] = 0;
|
|
|
int start = (int)strtol(diff_line, &endptr1, 10);
|
|
|
int end;
|
|
|
if (endptr1 == diff_line) {
|
|
@@ -748,8 +825,8 @@ apply_ed_diff(const smartlist_t *cons1, const smartlist_t *diff,
|
|
|
|
|
|
/* Add unchanged lines. */
|
|
|
for (; j && j > end; --j) {
|
|
|
- const char *cons_line = smartlist_get(cons1, j-1);
|
|
|
- smartlist_add(cons2, tor_strdup(cons_line));
|
|
|
+ cdline_t *cons_line = smartlist_get(cons1, j-1);
|
|
|
+ smartlist_add(cons2, cons_line);
|
|
|
}
|
|
|
|
|
|
/* Ignore removed lines. */
|
|
@@ -767,7 +844,7 @@ apply_ed_diff(const smartlist_t *cons1, const smartlist_t *diff,
|
|
|
|
|
|
i++; /* Skip the line with the range and command. */
|
|
|
while (i < diff_len) {
|
|
|
- if (!strcmp(smartlist_get(diff, i), ".")) {
|
|
|
+ if (line_str_eq(smartlist_get(diff, i), ".")) {
|
|
|
break;
|
|
|
}
|
|
|
if (++i == diff_len) {
|
|
@@ -787,16 +864,16 @@ apply_ed_diff(const smartlist_t *cons1, const smartlist_t *diff,
|
|
|
}
|
|
|
|
|
|
while (added_i > added_end) {
|
|
|
- const char *added_line = smartlist_get(diff, added_i--);
|
|
|
- smartlist_add(cons2, tor_strdup(added_line));
|
|
|
+ cdline_t *added_line = smartlist_get(diff, added_i--);
|
|
|
+ smartlist_add(cons2, added_line);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* Add remaining unchanged lines. */
|
|
|
for (; j > 0; --j) {
|
|
|
- const char *cons_line = smartlist_get(cons1, j-1);
|
|
|
- smartlist_add(cons2, tor_strdup(cons_line));
|
|
|
+ cdline_t *cons_line = smartlist_get(cons1, j-1);
|
|
|
+ smartlist_add(cons2, cons_line);
|
|
|
}
|
|
|
|
|
|
/* Reverse the whole thing since we did it from the end. */
|
|
@@ -805,7 +882,6 @@ apply_ed_diff(const smartlist_t *cons1, const smartlist_t *diff,
|
|
|
|
|
|
error_cleanup:
|
|
|
|
|
|
- SMARTLIST_FOREACH(cons2, char*, line, tor_free(line));
|
|
|
smartlist_free(cons2);
|
|
|
|
|
|
return NULL;
|
|
@@ -817,11 +893,13 @@ apply_ed_diff(const smartlist_t *cons1, const smartlist_t *diff,
|
|
|
* up to the caller to free their resources.
|
|
|
*/
|
|
|
smartlist_t *
|
|
|
-consdiff_gen_diff(const smartlist_t *cons1, const smartlist_t *cons2,
|
|
|
+consdiff_gen_diff(const smartlist_t *cons1,
|
|
|
+ const smartlist_t *cons2,
|
|
|
const consensus_digest_t *digests1,
|
|
|
- const consensus_digest_t *digests2)
|
|
|
+ const consensus_digest_t *digests2,
|
|
|
+ memarea_t *area)
|
|
|
{
|
|
|
- smartlist_t *ed_diff = gen_ed_diff(cons1, cons2);
|
|
|
+ smartlist_t *ed_diff = gen_ed_diff(cons1, cons2, area);
|
|
|
/* ed diff could not be generated - reason already logged by gen_ed_diff. */
|
|
|
if (!ed_diff) {
|
|
|
goto error_cleanup;
|
|
@@ -838,8 +916,18 @@ consdiff_gen_diff(const smartlist_t *cons1, const smartlist_t *cons2,
|
|
|
/* LCOV_EXCL_STOP */
|
|
|
}
|
|
|
|
|
|
- int cons2_eq = smartlist_strings_eq(cons2, ed_cons2);
|
|
|
- SMARTLIST_FOREACH(ed_cons2, char*, line, tor_free(line));
|
|
|
+ int cons2_eq = 1;
|
|
|
+ if (smartlist_len(cons2) == smartlist_len(ed_cons2)) {
|
|
|
+ SMARTLIST_FOREACH_BEGIN(cons2, const cdline_t *, line1) {
|
|
|
+ const cdline_t *line2 = smartlist_get(ed_cons2, line1_sl_idx);
|
|
|
+ if (! lines_eq(line1, line2) ) {
|
|
|
+ cons2_eq = 0;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } SMARTLIST_FOREACH_END(line1);
|
|
|
+ } else {
|
|
|
+ cons2_eq = 0;
|
|
|
+ }
|
|
|
smartlist_free(ed_cons2);
|
|
|
if (!cons2_eq) {
|
|
|
/* LCOV_EXCL_START -- impossible if diff generation is correct. */
|
|
@@ -858,10 +946,13 @@ consdiff_gen_diff(const smartlist_t *cons1, const smartlist_t *cons2,
|
|
|
(const char*)digests2->sha3_256, DIGEST256_LEN);
|
|
|
|
|
|
/* Create the resulting consensus diff. */
|
|
|
+ char buf[160];
|
|
|
smartlist_t *result = smartlist_new();
|
|
|
- smartlist_add_asprintf(result, "%s", ns_diff_version);
|
|
|
- smartlist_add_asprintf(result, "%s %s %s", hash_token,
|
|
|
+ tor_snprintf(buf, sizeof(buf), "%s", ns_diff_version);
|
|
|
+ smartlist_add_linecpy(result, area, buf);
|
|
|
+ tor_snprintf(buf, sizeof(buf), "%s %s %s", hash_token,
|
|
|
cons1_hash_hex, cons2_hash_hex);
|
|
|
+ smartlist_add_linecpy(result, area, buf);
|
|
|
smartlist_add_all(result, ed_diff);
|
|
|
smartlist_free(ed_diff);
|
|
|
return result;
|
|
@@ -870,7 +961,6 @@ consdiff_gen_diff(const smartlist_t *cons1, const smartlist_t *cons2,
|
|
|
|
|
|
if (ed_diff) {
|
|
|
/* LCOV_EXCL_START -- ed_diff is NULL except in unreachable cases above */
|
|
|
- SMARTLIST_FOREACH(ed_diff, char *, cp, tor_free(cp));
|
|
|
smartlist_free(ed_diff);
|
|
|
/* LCOV_EXCL_STOP */
|
|
|
}
|
|
@@ -888,7 +978,7 @@ consdiff_get_digests(const smartlist_t *diff,
|
|
|
char *digest2_out)
|
|
|
{
|
|
|
smartlist_t *hash_words = NULL;
|
|
|
- const char *format;
|
|
|
+ const cdline_t *format;
|
|
|
char cons1_hash[DIGEST256_LEN], cons2_hash[DIGEST256_LEN];
|
|
|
char *cons1_hash_hex, *cons2_hash_hex;
|
|
|
if (smartlist_len(diff) < 2) {
|
|
@@ -898,14 +988,19 @@ consdiff_get_digests(const smartlist_t *diff,
|
|
|
|
|
|
/* Check that it's the format and version we know. */
|
|
|
format = smartlist_get(diff, 0);
|
|
|
- if (strcmp(format, ns_diff_version)) {
|
|
|
+ if (!line_str_eq(format, ns_diff_version)) {
|
|
|
log_warn(LD_CONSDIFF, "The provided consensus diff format is not known.");
|
|
|
goto error_cleanup;
|
|
|
}
|
|
|
|
|
|
/* Grab the base16 digests. */
|
|
|
hash_words = smartlist_new();
|
|
|
- smartlist_split_string(hash_words, smartlist_get(diff, 1), " ", 0, 0);
|
|
|
+ {
|
|
|
+ const cdline_t *line2 = smartlist_get(diff, 1);
|
|
|
+ char *h = tor_memdup_nulterm(line2->s, line2->len);
|
|
|
+ smartlist_split_string(hash_words, h, " ", 0, 0);
|
|
|
+ tor_free(h);
|
|
|
+ }
|
|
|
|
|
|
/* There have to be three words, the first of which must be hash_token. */
|
|
|
if (smartlist_len(hash_words) != 3 ||
|
|
@@ -1038,7 +1133,6 @@ consdiff_apply_diff(const smartlist_t *cons1,
|
|
|
|
|
|
done:
|
|
|
if (cons2) {
|
|
|
- SMARTLIST_FOREACH(cons2, char *, cp, tor_free(cp));
|
|
|
smartlist_free(cons2);
|
|
|
}
|
|
|
|
|
@@ -1046,34 +1140,40 @@ consdiff_apply_diff(const smartlist_t *cons1,
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Helper: For every NL-terminated line in <b>s</b>, add that line
|
|
|
- * (without trailing newline) to <b>out</b>. Return -1 if there are any
|
|
|
- * non-NL terminated lines; 0 otherwise.
|
|
|
- *
|
|
|
- * Modifies <b>s</b> in place: don't do anything with <b>s</b> after you're
|
|
|
- * done here, besides freeing it.
|
|
|
+ * Helper: For every NL-terminated line in <b>s</b>, add a cdline referring to
|
|
|
+ * that line (without trailing newline) to <b>out</b>. Return -1 if there are
|
|
|
+ * any non-NL terminated lines; 0 otherwise.
|
|
|
*
|
|
|
* Unlike tor_split_lines, this function avoids ambiguity on its
|
|
|
* handling of a final line that isn't NL-terminated.
|
|
|
+ *
|
|
|
+ * All cdline_t objects are allocated in the provided memarea. Strings
|
|
|
+ * are not copied: if <b>s</b> changes or becomes invalid, then all
|
|
|
+ * generated cdlines will become invalid.
|
|
|
*/
|
|
|
-static int
|
|
|
-consensus_split_lines(smartlist_t *out, char *s)
|
|
|
+STATIC int
|
|
|
+consensus_split_lines(smartlist_t *out, const char *s, memarea_t *area)
|
|
|
{
|
|
|
- /* XXXX If we used string slices, we could avoid a bunch of copies here. */
|
|
|
while (*s) {
|
|
|
- char *eol = strchr(s, '\n');
|
|
|
+ const char *eol = strchr(s, '\n');
|
|
|
if (!eol) {
|
|
|
/* File doesn't end with newline. */
|
|
|
return -1;
|
|
|
}
|
|
|
- *eol = 0;
|
|
|
- smartlist_add(out, s);
|
|
|
+ if (eol - s > UINT32_MAX) {
|
|
|
+ /* Line is far too long. */
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ cdline_t *line = memarea_alloc(area, sizeof(cdline_t));
|
|
|
+ line->s = s;
|
|
|
+ line->len = (uint32_t)(eol - s);
|
|
|
+ smartlist_add(out, line);
|
|
|
s = eol+1;
|
|
|
}
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-/** Given a list of lines, return a newly allocated string containing
|
|
|
+/** Given a list of cdline_t, return a newly allocated string containing
|
|
|
* all of the lines, terminated with NL, concatenated.
|
|
|
*
|
|
|
* Unlike smartlist_join_strings(), avoids lossy operations on empty
|
|
@@ -1082,16 +1182,15 @@ static char *
|
|
|
consensus_join_lines(const smartlist_t *inp)
|
|
|
{
|
|
|
size_t n = 0;
|
|
|
- SMARTLIST_FOREACH(inp, const char *, cp, n += strlen(cp) + 1);
|
|
|
+ SMARTLIST_FOREACH(inp, const cdline_t *, cdline, n += cdline->len + 1);
|
|
|
n += 1;
|
|
|
char *result = tor_malloc(n);
|
|
|
char *out = result;
|
|
|
- SMARTLIST_FOREACH_BEGIN(inp, const char *, cp) {
|
|
|
- size_t len = strlen(cp);
|
|
|
- memcpy(out, cp, len);
|
|
|
- out += len;
|
|
|
+ SMARTLIST_FOREACH_BEGIN(inp, const cdline_t *, cdline) {
|
|
|
+ memcpy(out, cdline->s, cdline->len);
|
|
|
+ out += cdline->len;
|
|
|
*out++ = '\n';
|
|
|
- } SMARTLIST_FOREACH_END(cp);
|
|
|
+ } SMARTLIST_FOREACH_END(cdline);
|
|
|
*out++ = '\0';
|
|
|
tor_assert(out == result+n);
|
|
|
return result;
|
|
@@ -1114,28 +1213,26 @@ consensus_diff_generate(const char *cons1,
|
|
|
if (BUG(r1 < 0 || r2 < 0))
|
|
|
return NULL; // LCOV_EXCL_LINE
|
|
|
|
|
|
- char *cons1_copy = tor_strdup(cons1);
|
|
|
- char *cons2_copy = tor_strdup(cons2);
|
|
|
+ memarea_t *area = memarea_new();
|
|
|
lines1 = smartlist_new();
|
|
|
lines2 = smartlist_new();
|
|
|
- if (consensus_split_lines(lines1, cons1_copy) < 0)
|
|
|
+ if (consensus_split_lines(lines1, cons1, area) < 0)
|
|
|
goto done;
|
|
|
- if (consensus_split_lines(lines2, cons2_copy) < 0)
|
|
|
+ if (consensus_split_lines(lines2, cons2, area) < 0)
|
|
|
goto done;
|
|
|
|
|
|
- result_lines = consdiff_gen_diff(lines1, lines2, &d1, &d2);
|
|
|
+ result_lines = consdiff_gen_diff(lines1, lines2, &d1, &d2, area);
|
|
|
|
|
|
done:
|
|
|
- smartlist_free(lines1);
|
|
|
- smartlist_free(lines2);
|
|
|
- tor_free(cons1_copy);
|
|
|
- tor_free(cons2_copy);
|
|
|
-
|
|
|
if (result_lines) {
|
|
|
result = consensus_join_lines(result_lines);
|
|
|
- SMARTLIST_FOREACH(result_lines, char *, cp, tor_free(cp));
|
|
|
smartlist_free(result_lines);
|
|
|
}
|
|
|
+
|
|
|
+ memarea_drop_all(area);
|
|
|
+ smartlist_free(lines1);
|
|
|
+ smartlist_free(lines2);
|
|
|
+
|
|
|
return result;
|
|
|
}
|
|
|
|
|
@@ -1150,18 +1247,17 @@ consensus_diff_apply(const char *consensus,
|
|
|
smartlist_t *lines1 = NULL, *lines2 = NULL;
|
|
|
int r1;
|
|
|
char *result = NULL;
|
|
|
+ memarea_t *area = memarea_new();
|
|
|
|
|
|
r1 = consensus_compute_digest(consensus, &d1);
|
|
|
if (BUG(r1 < 0))
|
|
|
return NULL; // LCOV_EXCL_LINE
|
|
|
|
|
|
- char *cons_copy = tor_strdup(consensus);
|
|
|
- char *diff_copy = tor_strdup(diff);
|
|
|
lines1 = smartlist_new();
|
|
|
lines2 = smartlist_new();
|
|
|
- if (consensus_split_lines(lines1, cons_copy) < 0)
|
|
|
+ if (consensus_split_lines(lines1, consensus, area) < 0)
|
|
|
goto done;
|
|
|
- if (consensus_split_lines(lines2, diff_copy) < 0)
|
|
|
+ if (consensus_split_lines(lines2, diff, area) < 0)
|
|
|
goto done;
|
|
|
|
|
|
result = consdiff_apply_diff(lines1, lines2, &d1);
|
|
@@ -1169,8 +1265,7 @@ consensus_diff_apply(const char *consensus,
|
|
|
done:
|
|
|
smartlist_free(lines1);
|
|
|
smartlist_free(lines2);
|
|
|
- tor_free(cons_copy);
|
|
|
- tor_free(diff_copy);
|
|
|
+ memarea_drop_all(area);
|
|
|
|
|
|
return result;
|
|
|
}
|