Path: blob/main/crypto/openssl/ssl/quic/uint_set.c
48266 views
/*1* Copyright 2022-2023 The OpenSSL Project Authors. All Rights Reserved.2*3* Licensed under the Apache License 2.0 (the "License"). You may not use4* this file except in compliance with the License. You can obtain a copy5* in the file LICENSE in the source distribution or at6* https://www.openssl.org/source/license.html7*/89#include "internal/uint_set.h"10#include "internal/common.h"11#include <assert.h>1213/*14* uint64_t Integer Sets15* =====================16*17* This data structure supports the following operations:18*19* Insert Range: Adds an inclusive range of integers [start, end]20* to the set. Equivalent to Insert for each number21* in the range.22*23* Remove Range: Removes an inclusive range of integers [start, end]24* from the set. Not all of the range need already be in25* the set, but any part of the range in the set is removed.26*27* Query: Is an integer in the data structure?28*29* The data structure can be iterated.30*31* For greater efficiency in tracking large numbers of contiguous integers, we32* track integer ranges rather than individual integers. The data structure33* manages a list of integer ranges [[start, end]...]. Internally this is34* implemented as a doubly linked sorted list of range structures, which are35* automatically split and merged as necessary.36*37* This data structure requires O(n) traversal of the list for insertion,38* removal and query when we are not adding/removing ranges which are near the39* beginning or end of the set of ranges. For the applications for which this40* data structure is used (e.g. QUIC PN tracking for ACK generation), it is41* expected that the number of integer ranges needed at any given time will42* generally be small and that most operations will be close to the beginning or43* end of the range.44*45* Invariant: The data structure is always sorted in ascending order by value.46*47* Invariant: No two adjacent ranges ever 'border' one another (have no48* numerical gap between them) as the data structure always ensures49* such ranges are merged.50*51* Invariant: No two ranges ever overlap.52*53* Invariant: No range [a, b] ever has a > b.54*55* Invariant: Since ranges are represented using inclusive bounds, no range56* item inside the data structure can represent a span of zero57* integers.58*/59void ossl_uint_set_init(UINT_SET *s)60{61ossl_list_uint_set_init(s);62}6364void ossl_uint_set_destroy(UINT_SET *s)65{66UINT_SET_ITEM *x, *xnext;6768for (x = ossl_list_uint_set_head(s); x != NULL; x = xnext) {69xnext = ossl_list_uint_set_next(x);70OPENSSL_free(x);71}72}7374/* Possible merge of x, prev(x) */75static void uint_set_merge_adjacent(UINT_SET *s, UINT_SET_ITEM *x)76{77UINT_SET_ITEM *xprev = ossl_list_uint_set_prev(x);7879if (xprev == NULL)80return;8182if (x->range.start - 1 != xprev->range.end)83return;8485x->range.start = xprev->range.start;86ossl_list_uint_set_remove(s, xprev);87OPENSSL_free(xprev);88}8990static uint64_t u64_min(uint64_t x, uint64_t y)91{92return x < y ? x : y;93}9495static uint64_t u64_max(uint64_t x, uint64_t y)96{97return x > y ? x : y;98}99100/*101* Returns 1 if there exists an integer x which falls within both ranges a and102* b.103*/104static int uint_range_overlaps(const UINT_RANGE *a,105const UINT_RANGE *b)106{107return u64_min(a->end, b->end)108>= u64_max(a->start, b->start);109}110111static UINT_SET_ITEM *create_set_item(uint64_t start, uint64_t end)112{113UINT_SET_ITEM *x = OPENSSL_malloc(sizeof(UINT_SET_ITEM));114115if (x == NULL)116return NULL;117118ossl_list_uint_set_init_elem(x);119x->range.start = start;120x->range.end = end;121return x;122}123124int ossl_uint_set_insert(UINT_SET *s, const UINT_RANGE *range)125{126UINT_SET_ITEM *x, *xnext, *z, *zprev, *f;127uint64_t start = range->start, end = range->end;128129if (!ossl_assert(start <= end))130return 0;131132if (ossl_list_uint_set_is_empty(s)) {133/* Nothing in the set yet, so just add this range. */134x = create_set_item(start, end);135if (x == NULL)136return 0;137ossl_list_uint_set_insert_head(s, x);138return 1;139}140141z = ossl_list_uint_set_tail(s);142if (start > z->range.end) {143/*144* Range is after the latest range in the set, so append.145*146* Note: The case where the range is before the earliest range in the147* set is handled as a degenerate case of the final case below. See148* optimization note (*) below.149*/150if (z->range.end + 1 == start) {151z->range.end = end;152return 1;153}154155x = create_set_item(start, end);156if (x == NULL)157return 0;158ossl_list_uint_set_insert_tail(s, x);159return 1;160}161162f = ossl_list_uint_set_head(s);163if (start <= f->range.start && end >= z->range.end) {164/*165* New range dwarfs all ranges in our set.166*167* Free everything except the first range in the set, which we scavenge168* and reuse.169*/170x = ossl_list_uint_set_head(s);171x->range.start = start;172x->range.end = end;173for (x = ossl_list_uint_set_next(x); x != NULL; x = xnext) {174xnext = ossl_list_uint_set_next(x);175ossl_list_uint_set_remove(s, x);176}177return 1;178}179180/*181* Walk backwards since we will most often be inserting at the end. As an182* optimization, test the head node first and skip iterating over the183* entire list if we are inserting at the start. The assumption is that184* insertion at the start and end of the space will be the most common185* operations. (*)186*/187z = end < f->range.start ? f : z;188189for (; z != NULL; z = zprev) {190zprev = ossl_list_uint_set_prev(z);191192/* An existing range dwarfs our new range (optimisation). */193if (z->range.start <= start && z->range.end >= end)194return 1;195196if (uint_range_overlaps(&z->range, range)) {197/*198* Our new range overlaps an existing range, or possibly several199* existing ranges.200*/201UINT_SET_ITEM *ovend = z;202203ovend->range.end = u64_max(end, z->range.end);204205/* Get earliest overlapping range. */206while (zprev != NULL && uint_range_overlaps(&zprev->range, range)) {207z = zprev;208zprev = ossl_list_uint_set_prev(z);209}210211ovend->range.start = u64_min(start, z->range.start);212213/* Replace sequence of nodes z..ovend with updated ovend only. */214while (z != ovend) {215z = ossl_list_uint_set_next(x = z);216ossl_list_uint_set_remove(s, x);217OPENSSL_free(x);218}219break;220} else if (end < z->range.start221&& (zprev == NULL || start > zprev->range.end)) {222if (z->range.start == end + 1) {223/* We can extend the following range backwards. */224z->range.start = start;225226/*227* If this closes a gap we now need to merge228* consecutive nodes.229*/230uint_set_merge_adjacent(s, z);231} else if (zprev != NULL && zprev->range.end + 1 == start) {232/* We can extend the preceding range forwards. */233zprev->range.end = end;234235/*236* If this closes a gap we now need to merge237* consecutive nodes.238*/239uint_set_merge_adjacent(s, z);240} else {241/*242* The new interval is between intervals without overlapping or243* touching them, so insert between, preserving sort.244*/245x = create_set_item(start, end);246if (x == NULL)247return 0;248ossl_list_uint_set_insert_before(s, z, x);249}250break;251}252}253254return 1;255}256257int ossl_uint_set_remove(UINT_SET *s, const UINT_RANGE *range)258{259UINT_SET_ITEM *z, *zprev, *y;260uint64_t start = range->start, end = range->end;261262if (!ossl_assert(start <= end))263return 0;264265/* Walk backwards since we will most often be removing at the end. */266for (z = ossl_list_uint_set_tail(s); z != NULL; z = zprev) {267zprev = ossl_list_uint_set_prev(z);268269if (start > z->range.end)270/* No overlapping ranges can exist beyond this point, so stop. */271break;272273if (start <= z->range.start && end >= z->range.end) {274/*275* The range being removed dwarfs this range, so it should be276* removed.277*/278ossl_list_uint_set_remove(s, z);279OPENSSL_free(z);280} else if (start <= z->range.start && end >= z->range.start) {281/*282* The range being removed includes start of this range, but does283* not cover the entire range (as this would be caught by the case284* above). Shorten the range.285*/286assert(end < z->range.end);287z->range.start = end + 1;288} else if (end >= z->range.end) {289/*290* The range being removed includes the end of this range, but does291* not cover the entire range (as this would be caught by the case292* above). Shorten the range. We can also stop iterating.293*/294assert(start > z->range.start);295assert(start > 0);296z->range.end = start - 1;297break;298} else if (start > z->range.start && end < z->range.end) {299/*300* The range being removed falls entirely in this range, so cut it301* into two. Cases where a zero-length range would be created are302* handled by the above cases.303*/304y = create_set_item(end + 1, z->range.end);305ossl_list_uint_set_insert_after(s, z, y);306z->range.end = start - 1;307break;308} else {309/* Assert no partial overlap; all cases should be covered above. */310assert(!uint_range_overlaps(&z->range, range));311}312}313314return 1;315}316317int ossl_uint_set_query(const UINT_SET *s, uint64_t v)318{319UINT_SET_ITEM *x;320321if (ossl_list_uint_set_is_empty(s))322return 0;323324for (x = ossl_list_uint_set_tail(s); x != NULL; x = ossl_list_uint_set_prev(x))325if (x->range.start <= v && x->range.end >= v)326return 1;327else if (x->range.end < v)328return 0;329330return 0;331}332333334