Packages
hackney
3.0.2
4.7.2
4.7.1
4.7.0
4.6.1
4.6.0
4.5.2
4.5.1
4.5.0
4.4.5
4.4.3
4.4.2
4.4.1
4.4.0
4.3.0
4.2.3
4.2.2
4.2.1
4.2.0
4.1.0
4.0.3
4.0.2
4.0.1
4.0.0
3.2.1
3.2.0
3.1.2
3.1.1
3.1.0
3.0.3
3.0.2
3.0.1
3.0.0
retired
2.0.1
2.0.0
2.0.0-beta.1
1.25.0
1.24.1
1.24.0
1.23.0
1.22.0
1.21.0
1.20.1
1.20.0
1.19.1
1.19.0
1.18.2
1.18.1
1.18.0
1.17.4
1.17.3
1.17.2
1.17.1
1.17.0
1.16.0
1.15.2
1.15.1
1.15.0
1.14.3
1.14.2
1.14.0
1.13.0
1.12.1
1.12.0
1.11.0
1.10.1
1.10.0
1.9.0
1.8.6
1.8.5
1.8.4
1.8.3
1.8.2
1.8.0
1.7.1
1.7.0
1.6.6
retired
1.6.5
1.6.4
retired
1.6.3
1.6.2
1.6.1
1.6.0
1.5.7
1.5.6
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.10
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.0
1.1.0
1.0.6
1.0.5
1.0.2
1.0.1
0.15.2
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.1
Simple HTTP client with HTTP/1.1, HTTP/2, and HTTP/3 support
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
c_src/lsquic/src/liblsquic/lsquic_hpi.c
/* Copyright (c) 2017 - 2022 LiteSpeed Technologies Inc. See LICENSE. */
/*
* lsquic_hpi.c - implementation of (Extensible) HTTP Priority Iterator.
*/
#include <assert.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include <sys/types.h>
#ifdef WIN32
#include <vc_compat.h>
#endif
#include "lsquic.h"
#include "lsquic_types.h"
#include "lsquic_int_types.h"
#include "lsquic_sfcw.h"
#include "lsquic_varint.h"
#include "lsquic_hq.h"
#include "lsquic_hash.h"
#include "lsquic_stream.h"
#include "lsquic_conn_flow.h"
#include "lsquic_rtt.h"
#include "lsquic_conn_public.h"
#include "lsquic_min_heap.h"
#include "lsquic_mm.h"
#include "lsquic_hpi.h"
#define LSQUIC_LOGGER_MODULE LSQLM_HPI
#define LSQUIC_LOG_CONN_ID lsquic_conn_log_cid(iter->hpi_conn_pub->lconn)
#include "lsquic_logger.h"
#define HPI_DEBUG(fmt, ...) LSQ_DEBUG("%s: " fmt, iter->hpi_name, __VA_ARGS__)
#define NEXT_STREAM(stream, off) \
(* (struct lsquic_stream **) ((unsigned char *) (stream) + (off)))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
static void
add_stream_to_hpi (struct http_prio_iter *iter,
struct lsquic_stream *new_stream)
{
unsigned prio, incr;
if (lsquic_stream_is_critical(new_stream))
{
prio = 0;
incr = 1; /* Place in incremental bucket: these do not need to be
* ordered by stream ID.
*/
}
else
{
prio = 1 + MIN(new_stream->sm_priority, LSQUIC_MAX_HTTP_URGENCY);
incr = !!(new_stream->sm_bflags & SMBF_INCREMENTAL);
}
if (!(iter->hpi_set[incr] & (1u << prio)))
{
iter->hpi_set[incr] |= 1u << prio;
if (0 == incr)
iter->hpi_counts[prio] = 0;
TAILQ_INIT(&iter->hpi_streams[incr][prio]);
}
if (0 == incr)
++iter->hpi_counts[prio];
TAILQ_INSERT_TAIL(&iter->hpi_streams[incr][prio],
new_stream, next_prio_stream);
}
void
lsquic_hpi_init (void *iter_p, struct lsquic_stream *first,
struct lsquic_stream *last, uintptr_t next_ptr_offset,
struct lsquic_conn_public *conn_pub, const char *name,
int (*filter)(void *filter_ctx, struct lsquic_stream *),
void *filter_ctx)
{
struct http_prio_iter *const iter = iter_p;
struct lsquic_stream *stream;
unsigned count;
iter->hpi_conn_pub = conn_pub;
iter->hpi_name = name ? name : "UNSET";
iter->hpi_flags = 0;
iter->hpi_heaped = 0;
iter->hpi_set[0] = 0;
iter->hpi_set[1] = 0;
memset(&iter->hpi_min_heap, 0, sizeof(iter->hpi_min_heap));
stream = first;
count = 0;
if (filter)
while (1)
{
if (filter(filter_ctx, stream))
{
add_stream_to_hpi(iter, stream);
++count;
}
if (stream == last)
break;
stream = NEXT_STREAM(stream, next_ptr_offset);
}
else
while (1)
{
HPI_DEBUG("add stream %"PRIu64, stream->id);
add_stream_to_hpi(iter, stream);
++count;
if (stream == last)
break;
stream = NEXT_STREAM(stream, next_ptr_offset);
}
if (count > 2)
HPI_DEBUG("initialized; # elems: %u; sets: [ %08X, %08X ]",
count, iter->hpi_set[0], iter->hpi_set[1]);
}
/* Number of trailing zeroes */
static const unsigned char ntz[] = {
9, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0,
1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0,
2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0,
1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0,
3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0,
1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0,
2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0,
1, 0, 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0,
1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0,
2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0,
1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0,
3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0,
1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0,
2, 0, 1, 0, 8, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0,
1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0,
1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0,
2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0,
1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0,
3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0,
1, 0, 2, 0, 1, 0, 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0,
2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0,
1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0,
1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0,
2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0,
1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0,
3, 0, 1, 0, 2, 0, 1, 0,
};
/* Sets prio_ and incr_ */
#define calc_next_prio_and_incr(iter_, prio_, incr_) do { \
prio_ = ntz[iter_->hpi_set[0]]; \
if (prio_ <= ntz[iter_->hpi_set[1]]) \
incr_ = 0; \
else \
{ \
prio_ = ntz[iter_->hpi_set[1]]; \
incr_ = 1; \
} \
} while (0)
struct lsquic_stream *
lsquic_hpi_first (void *iter_p)
{
struct http_prio_iter *const iter = iter_p;
assert(!(iter->hpi_set[0] & ~((1 << N_HPI_PRIORITIES) - 1)));
assert(!(iter->hpi_set[1] & ~((1 << N_HPI_PRIORITIES) - 1)));
return lsquic_hpi_next(iter);
}
static struct lsquic_stream *
next_incr (struct http_prio_iter *iter, unsigned prio)
{
struct lsquic_stream *stream;
stream = TAILQ_FIRST(&iter->hpi_streams[1][prio]);
TAILQ_REMOVE(&iter->hpi_streams[1][prio], stream, next_prio_stream);
if (TAILQ_EMPTY(&iter->hpi_streams[1][prio]))
iter->hpi_set[1] &= ~(1u << prio);
return stream;
}
static void
free_heap_elems (struct http_prio_iter *iter)
{
if (0 == (iter->hpi_flags & (HPI_MH_4K|HPI_MH_MALLOC)))
/* Expected condition: nothing to do */ ;
else if (iter->hpi_flags & HPI_MH_4K)
{
lsquic_mm_put_4k(iter->hpi_conn_pub->mm, iter->hpi_min_heap.mh_elems);
iter->hpi_flags &= ~HPI_MH_4K;
}
else
{
assert(iter->hpi_flags & HPI_MH_MALLOC);
iter->hpi_flags &= ~HPI_MH_MALLOC;
free(iter->hpi_min_heap.mh_elems);
}
iter->hpi_min_heap.mh_elems = NULL;
}
#ifndef NDEBUG
static int lsquic_hpi_heap_test = (
LSQUIC_HPI_HEAP_TEST_STACK_OK | LSQUIC_HPI_HEAP_TEST_4K_OK);
void
lsquic_hpi_set_heap_test (int val)
{
lsquic_hpi_heap_test = val;
}
#endif
static int
heap_nonincr_bucket (struct http_prio_iter *iter, unsigned prio)
{
struct lsquic_stream *stream;
size_t need;
if (iter->hpi_counts[prio] <= sizeof(iter->hpi_min_heap_els)
/ sizeof(iter->hpi_min_heap_els[0])
#ifndef NDEBUG
&& (lsquic_hpi_heap_test & LSQUIC_HPI_HEAP_TEST_STACK_OK)
#endif
)
iter->hpi_min_heap.mh_elems = iter->hpi_min_heap_els;
else if (need = iter->hpi_counts[prio] * sizeof(struct min_heap_elem),
need <= 0x1000
#ifndef NDEBUG
&& (lsquic_hpi_heap_test & LSQUIC_HPI_HEAP_TEST_4K_OK)
#endif
)
{
iter->hpi_min_heap.mh_elems = lsquic_mm_get_4k(iter->hpi_conn_pub->mm);
if (!iter->hpi_min_heap.mh_elems)
return -1;
iter->hpi_flags |= HPI_MH_4K;
}
else
{
iter->hpi_min_heap.mh_elems = malloc(need);
if (!iter->hpi_min_heap.mh_elems)
return -1;
iter->hpi_flags |= HPI_MH_MALLOC;
}
iter->hpi_min_heap.mh_nalloc = iter->hpi_counts[prio];
TAILQ_FOREACH(stream, &iter->hpi_streams[0][prio], next_prio_stream)
lsquic_mh_insert(&iter->hpi_min_heap, stream, stream->id);
iter->hpi_heaped |= 1u << prio;
return 0;
}
static struct lsquic_stream *
next_nonincr (struct http_prio_iter *iter, unsigned prio)
{
struct lsquic_stream *stream;
if (iter->hpi_heaped & (1u << prio))
{
pop_stream:
stream = lsquic_mh_pop(&iter->hpi_min_heap);
if (lsquic_mh_count(&iter->hpi_min_heap) == 0)
{
free_heap_elems(iter);
iter->hpi_set[0] &= ~(1u << prio);
}
}
else if (iter->hpi_counts[prio] > 1)
{
if (0 == heap_nonincr_bucket(iter, prio))
goto pop_stream;
/* Handle memory allocation failure by abandoning attempts to order
* the streams:
*/
iter->hpi_counts[prio] = 1;
goto first_stream;
}
else
{
first_stream:
stream = TAILQ_FIRST(&iter->hpi_streams[0][prio]);
TAILQ_REMOVE(&iter->hpi_streams[0][prio], stream, next_prio_stream);
if (TAILQ_EMPTY(&iter->hpi_streams[0][prio]))
iter->hpi_set[0] &= ~(1u << prio);
}
return stream;
}
struct lsquic_stream *
lsquic_hpi_next (void *iter_p)
{
struct http_prio_iter *const iter = iter_p;
struct lsquic_stream *stream;
unsigned prio, incr;
calc_next_prio_and_incr(iter, prio, incr);
if (prio >= N_HPI_PRIORITIES)
return NULL;
if (incr)
stream = next_incr(iter, prio);
else
stream = next_nonincr(iter, prio);
if (LSQ_LOG_ENABLED(LSQ_LOG_DEBUG))
HPI_DEBUG("%s: return stream %"PRIu64", incr: %u, priority %u",
__func__, stream->id, incr, prio);
return stream;
}
#if __GNUC__
# define popcount __builtin_popcount
#else
static int
popcount (unsigned v)
{
int count;
unsigned i;
for (i = 0, count = 0; i < sizeof(v) * 8; ++i)
if (v & (1 << i))
++count;
return count;
}
#endif
static void
hpi_drop_high_or_non_high (void *iter_p, int drop_high)
{
struct http_prio_iter *const iter = iter_p;
unsigned prio, incr;
/* Nothing to drop if there is only one bucket */
if (popcount(iter->hpi_set[0]) + popcount(iter->hpi_set[1]) < 2)
return;
calc_next_prio_and_incr(iter, prio, incr);
if (drop_high)
iter->hpi_set[incr] &= ~(1u << prio);
else
{
iter->hpi_set[incr] = 1u << prio;
iter->hpi_set[!incr] = 0;
}
}
void
lsquic_hpi_drop_high (void *iter_p)
{
hpi_drop_high_or_non_high(iter_p, 1);
}
void
lsquic_hpi_drop_non_high (void *iter_p)
{
hpi_drop_high_or_non_high(iter_p, 0);
}
void
lsquic_hpi_cleanup (void *iter_p)
{
struct http_prio_iter *const iter = iter_p;
if (iter->hpi_min_heap.mh_elems)
free_heap_elems(iter);
}