diff options
Diffstat (limited to 'src/or/consdiff.c')
-rw-r--r-- | src/or/consdiff.c | 337 |
1 files changed, 216 insertions, 121 deletions
diff --git a/src/or/consdiff.c b/src/or/consdiff.c index dfb97d432a..848ea13ea7 100644 --- a/src/or/consdiff.c +++ b/src/or/consdiff.c @@ -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; } |