Coverage Report

Created: 2026-09-01 06:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/splay.c
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
#include "curl_setup.h"
25
26
#include "urldata.h"
27
#include "splay.h"
28
29
30
void Curl_timeouts_init(struct Curl_timeouts *timeouts,
31
                        const struct curltime *ptime_base)
32
3.73k
{
33
3.73k
  timeouts->tree = NULL;
34
3.73k
  timeouts->time_base = ptime_base ? *ptime_base : curlx_now();
35
3.73k
}
36
37
bool Curl_timeouts_has(struct Curl_easy *data)
38
26.0k
{
39
26.0k
  struct Curl_tree *node = data ? &data->state.timeouts.splaynode : NULL;
40
26.0k
  return node && node->registered;
41
26.0k
}
42
43
timediff_t Curl_timeouts_offset_us(struct Curl_timeouts *timeouts,
44
                                   const struct curltime *pts)
45
14.7k
{
46
14.7k
  return curlx_ptimediff_us(pts, &timeouts->time_base);
47
14.7k
}
48
49
int Curl_timeouts_next_ms(struct Curl_timeouts *timeouts,
50
                          const struct curltime *pnow,
51
                          timediff_t *pexpire_offset_us,
52
                          uint32_t *pmid)
53
0
{
54
0
  if(timeouts->tree) { /* splay the lowest key to the root */
55
0
    timeouts->tree = Curl_splay(TIMEDIFF_T_MIN, timeouts->tree);
56
0
  }
57
58
0
  if(timeouts->tree) {
59
0
    timediff_t elapsed_us = Curl_timeouts_offset_us(timeouts, pnow);
60
0
    timediff_t delta_us = timeouts->tree->key - elapsed_us;
61
0
    if(pmid)
62
0
      *pmid = timeouts->tree->id;
63
0
    if(pexpire_offset_us)
64
0
      *pexpire_offset_us = timeouts->tree->key;
65
0
    if(delta_us > 0) { /* expires in the future */
66
0
      timediff_t ms = curlx_us_to_ceil_ms(delta_us);
67
0
      return (ms > INT_MAX) ? INT_MAX : (int)ms;
68
0
    }
69
0
    else /* has expired */
70
0
      return 0;
71
0
  }
72
0
  if(pmid)
73
0
    *pmid = UINT32_MAX;
74
0
  if(pexpire_offset_us)
75
0
    *pexpire_offset_us = 0;
76
0
  return -1;
77
0
}
78
79
bool Curl_timeouts_remove_expired(struct Curl_timeouts *timeouts,
80
                                  const struct curltime *ts,
81
                                  uint32_t *pmid)
82
10.9k
{
83
10.9k
  if(timeouts->tree) {
84
7.26k
    struct Curl_tree *t = NULL;
85
7.26k
    timediff_t elapsed_us = Curl_timeouts_offset_us(timeouts, ts);
86
7.26k
    timeouts->tree = Curl_splaygetbest(elapsed_us, timeouts->tree, &t);
87
7.26k
    if(t) {
88
0
      *pmid = t->id;
89
0
      return TRUE;
90
0
    }
91
7.26k
  }
92
10.9k
  *pmid = UINT32_MAX;
93
10.9k
  return FALSE;
94
10.9k
}
95
96
void Curl_timeouts_add(struct Curl_timeouts *timeouts,
97
                       struct Curl_easy *data,
98
                       timediff_t offset_us)
99
3.68k
{
100
3.68k
  struct Curl_tree *node = &data->state.timeouts.splaynode;
101
3.68k
  DEBUGASSERT(!node->registered);
102
3.68k
  timeouts->tree = Curl_splayinsert(offset_us, timeouts->tree,
103
3.68k
                                    node, data->mid);
104
3.68k
}
105
106
bool Curl_timeouts_remove(struct Curl_timeouts *timeouts,
107
                          struct Curl_easy *data)
108
7.47k
{
109
7.47k
  struct Curl_tree *node = &data->state.timeouts.splaynode;
110
7.47k
  if(node->registered) {
111
3.67k
    int rc = Curl_splayremove(timeouts->tree, node, &timeouts->tree);
112
3.67k
#ifdef DEBUGBUILD
113
3.67k
    if(rc)
114
0
      curl_mfprintf(stderr, "Internal error removing splay node = %d\n", rc);
115
#else
116
    (void)rc;
117
#endif
118
3.67k
    return TRUE;
119
3.67k
  }
120
3.79k
  return FALSE;
121
7.47k
}
122
123
/*
124
 * Splay using the key i (which may or may not be in the tree).
125
 * This rotates the tree, so:
126
 * - root->smaller has all nodes smaller than `key`
127
 * - root->larger has all nodes larger than `key`
128
 * - root->key may equal `key` or not
129
 * <https://en.wikipedia.org/wiki/Splay_tree>
130
 */
131
struct Curl_tree *Curl_splay(timediff_t key,
132
                             struct Curl_tree *root)
133
10.9k
{
134
10.9k
  struct Curl_tree N, *l, *r, *y;
135
136
10.9k
  if(!root)
137
0
    return NULL;
138
10.9k
  N.smaller = N.larger = NULL;
139
10.9k
  l = r = &N;
140
141
10.9k
  for(;;) {
142
10.9k
    if(key < root->key) {
143
      /* key is somewhere in root->smaller branch */
144
7.26k
      if(!root->smaller)  /* which is empty, done */
145
7.26k
        break;
146
0
      if(key < root->smaller->key) {
147
        /* key is somewhere in root->smaller->smaller, make a "Zig step" */
148
0
        y = root->smaller;
149
0
        root->smaller = y->larger;
150
0
        y->larger = root;
151
0
        root = y;
152
0
        if(!root->smaller)
153
0
          break;
154
0
      }
155
      /* Making root->smaller the new root, the old root is no longer
156
       * referenced. Remember it in the N tree's `r`ight/larger side.
157
       * Everything in old root is smaller than what the right side
158
       * of N already has, so it gets added to r->smaller. */
159
0
      r->smaller = root;
160
0
      r = root;
161
0
      root = root->smaller;
162
0
    }
163
3.67k
    else if(key > root->key) {
164
      /* key is somewhere in root->larger branch */
165
0
      if(!root->larger)  /* which is empty, done */
166
0
        break;
167
0
      if(key > root->larger->key) {
168
        /* key is somewhere in root->larger->larger, make a "Zig step" */
169
0
        y = root->larger;
170
0
        root->larger = y->smaller;
171
0
        y->smaller = root;
172
0
        root = y;
173
0
        if(!root->larger)
174
0
          break;
175
0
      }
176
      /* Making root->larger the new root, the old root is no longer
177
       * referenced. Remember it in the N tree's `l`eft/smaller side.
178
       * Everything in old root is larger than what the left side
179
       * of N already has, so it gets added to l->larger. */
180
0
      l->larger = root;
181
0
      l = root;
182
0
      root = root->larger;
183
0
    }
184
3.67k
    else  /* exact match, root is key, done */
185
3.67k
      break;
186
10.9k
  }
187
188
  /* Put it all together again.
189
   * root->smaller has everything larger than current `l`.
190
   * root->larger has everything smaller than current `r`. */
191
10.9k
  l->larger = root->smaller;
192
10.9k
  r->smaller = root->larger;
193
10.9k
  root->smaller = N.larger;
194
10.9k
  root->larger = N.smaller;
195
196
10.9k
  return root;
197
10.9k
}
198
199
/* Insert key i into the tree t. Return a pointer to the resulting tree or
200
 * NULL if something went wrong.
201
 *
202
 * @unittest: 1309
203
 */
204
struct Curl_tree *Curl_splayinsert(timediff_t key,
205
                                   struct Curl_tree *root,
206
                                   struct Curl_tree *node,
207
                                   uint32_t id)
208
3.68k
{
209
3.68k
  DEBUGASSERT(node);
210
211
3.68k
  node->key = key;
212
3.68k
  node->id = id;
213
3.68k
  node->same = NULL;
214
3.68k
  node->registered = TRUE;
215
3.68k
  if(root) {
216
0
    root = Curl_splay(key, root);
217
0
    DEBUGASSERT(root);
218
0
    if(key == root->key) {
219
      /* There already exists a node in the tree with the same key.
220
         Append the new node to the `same` list. */
221
0
      struct Curl_tree **panchor = &root->same;
222
0
      while(*panchor)
223
0
        panchor = &(*panchor)->same;
224
0
      *panchor = node;
225
0
      return root; /* the root node always stays the same */
226
0
    }
227
0
  }
228
229
  /* node becomes the new root. Insert old root as sub-branch. */
230
3.68k
  if(!root) {
231
3.68k
    node->smaller = node->larger = NULL;
232
3.68k
  }
233
0
  else if(key < root->key) {
234
0
    node->smaller = root->smaller;
235
0
    node->larger = root;
236
0
    root->smaller = NULL;
237
0
  }
238
0
  else {
239
0
    node->larger = root->larger;
240
0
    node->smaller = root;
241
0
    root->larger = NULL;
242
0
  }
243
244
3.68k
  return node;
245
3.68k
}
246
247
/* Finds and deletes the best-fit node from the tree. Return a pointer to the
248
   resulting tree. best-fit means the smallest node if it is not larger than
249
   the key */
250
struct Curl_tree *Curl_splaygetbest(timediff_t key,
251
                                    struct Curl_tree *root,
252
                                    struct Curl_tree **removed)
253
7.26k
{
254
7.26k
  struct Curl_tree *x;
255
256
7.26k
  if(!root) {
257
0
    *removed = NULL; /* none removed since there was no root */
258
0
    return NULL;
259
0
  }
260
261
  /* find smallest */
262
7.26k
  root = Curl_splay(TIMEDIFF_T_MIN, root);
263
7.26k
  DEBUGASSERT(root);
264
7.26k
  if(key < root->key) {
265
    /* even the smallest is too big */
266
7.26k
    *removed = NULL;
267
7.26k
    return root;
268
7.26k
  }
269
270
  /* FIRST! Check if there is a list with identical keys */
271
0
  if(root->same) {
272
0
    x = root->same;
273
0
    DEBUGASSERT(x->key == root->key);
274
    /* 'x' becomes the new root node */
275
0
    x->larger = root->larger;
276
0
    x->smaller = root->smaller;
277
0
    root->same = NULL;
278
0
    root->registered = FALSE;
279
0
    *removed = root;
280
0
    return x; /* new root */
281
0
  }
282
283
  /* we splayed the tree to the smallest element, there is no smaller */
284
0
  x = root->larger;
285
0
  root->registered = FALSE;
286
0
  *removed = root;
287
288
0
  return x;
289
0
}
290
291
/* Deletes the node we point out from the tree if it is there. Stores a
292
 * pointer to the new resulting tree in 'newroot'.
293
 *
294
 * Returns zero on success and non-zero on errors!
295
 * When returning error, it does not touch the 'newroot' pointer.
296
 *
297
 * NOTE: when the last node of the tree is removed, there is no tree left so
298
 * 'newroot' will be made to point to NULL.
299
 *
300
 * @unittest: 1309
301
 */
302
int Curl_splayremove(struct Curl_tree *root,
303
                     struct Curl_tree *removenode,
304
                     struct Curl_tree **newroot)
305
3.67k
{
306
3.67k
  struct Curl_tree *x;
307
308
3.67k
  if(!root)
309
0
    return 1;
310
311
3.67k
  DEBUGASSERT(removenode);
312
3.67k
  if(!removenode->registered)
313
0
    return 2;
314
315
3.67k
  root = Curl_splay(removenode->key, root);
316
3.67k
  DEBUGASSERT(root);
317
318
  /* First make sure that we got the same root key as the one we want
319
     to remove, as otherwise we might be trying to remove a node that
320
     is not actually in the tree. */
321
3.67k
  if(root->key != removenode->key) {
322
0
    DEBUGASSERT(0);
323
0
    return 2;
324
0
  }
325
326
3.67k
  if(root != removenode) {
327
    /* Should be in the root->same list then */
328
0
    struct Curl_tree **panchor;
329
0
    for(panchor = &root->same; *panchor; panchor = &(*panchor)->same) {
330
0
      if(*panchor == removenode) {
331
0
        *panchor = removenode->same;
332
0
        removenode->same = NULL;
333
0
        removenode->registered = FALSE;
334
0
        *newroot = root;
335
0
        return 0;
336
0
      }
337
0
    }
338
    /* not found in same list, error */
339
0
    DEBUGASSERT(0);
340
0
    return 2;
341
0
  }
342
  /* removing the root node */
343
3.67k
  if(root->same) {
344
    /* 'x' is the new root node, we make it use the root node's
345
       smaller/larger links */
346
0
    x = root->same;
347
0
    x->larger = root->larger;
348
0
    x->smaller = root->smaller;
349
0
    root->same = NULL;
350
0
  }
351
3.67k
  else {
352
    /* Remove the root node */
353
3.67k
    if(!root->smaller)
354
3.67k
      x = root->larger;
355
0
    else {
356
0
      x = Curl_splay(removenode->key, root->smaller);
357
0
      DEBUGASSERT(x);
358
0
      x->larger = root->larger;
359
0
    }
360
3.67k
  }
361
3.67k
  removenode->registered = FALSE;
362
3.67k
  *newroot = x; /* return new root */
363
3.67k
  return 0;
364
3.67k
}
365
366
/* set and get the custom payload for this tree node */
367
void Curl_splayset(struct Curl_tree *node, uint32_t id)
368
0
{
369
0
  DEBUGASSERT(node);
370
0
  node->id = id;
371
0
}
372
373
uint32_t Curl_splayget(struct Curl_tree *node)
374
0
{
375
0
  DEBUGASSERT(node);
376
0
  return node->id;
377
0
}