47#include <freeradius-devel/bio/bio_priv.h>
48#include <freeradius-devel/bio/null.h>
49#include <freeradius-devel/bio/buf.h>
50#include <freeradius-devel/util/rb.h>
52#define _BIO_RETRY_PRIVATE
53#include <freeradius-devel/bio/retry.h>
125 item->cancelled =
true;
133 if (!
item->reserved) {
148 if (
my->partial ==
item)
return;
154 if (
item->reserved)
return;
161 fr_assert(fr_bio_retry_list_num_elements(&
my->free) == 0);
170 my->all_used =
false;
172 if (!
my->info.write_blocked &&
my->cb.write_resume) (void)
my->cb.write_resume(&
my->bio);
175 item->packet_ctx = NULL;
177 fr_bio_retry_list_insert_head(&
my->free,
item);
187 if (
my->info.write_blocked) {
200 my->info.write_blocked =
true;
237 my->info.last_sent = now;
265 if (rcode ==
fr_bio_error(IO_WOULD_BLOCK))
return rcode;
274 if ((
size_t) rcode < item->size) {
291 if (!
my->info.write_blocked)
return 1;
293 my->info.write_blocked =
false;
305 return !
my->info.write_blocked;
335 if (rcode <= 0)
return rcode;
337 my->buffer.read += rcode;
355 if (
item->cancelled) {
356 item->packet_ctx = NULL;
358 fr_bio_retry_list_insert_head(&
my->free,
item);
368 if (rcode <= 0)
return rcode;
387 if (!
my->buffer.start ||
440 if (
my->partial)
return 0;
462 if ((
size_t) rcode ==
item->size)
return rcode;
467 if (rcode == 0)
return 0;
476 if (rcode ==
fr_bio_error(IO_WOULD_BLOCK))
return rcode;
570 return next->
write(next, packet_ctx, NULL, size);
576 if (fr_bio_retry_list_num_elements(&
my->free) == 0) {
596 my->info.write_blocked =
true;
603 if (rcode < 0)
return rcode;
611 fr_assert(fr_bio_retry_list_num_elements(&
my->free) > 0);
621 rcode = next->
write(next, packet_ctx,
buffer, size);
622 if (rcode <= 0)
return rcode;
627 item = fr_bio_retry_list_pop_head(&
my->free);
634 .packet_ctx = packet_ctx,
662 if ((
size_t) rcode < size) {
685 rcode = next->
read(next, packet_ctx,
buffer, size);
686 if (rcode <= 0)
return rcode;
695 if (!
my->response(bio, &
item, packet_ctx,
buffer, size)) {
698 item->retry.replies++;
730 item->retry.replies++;
736 if (
item->reserved)
return rcode;
824 if (!
item->retry.replies)
return 0;
846 if (
item->retry.config)
return 0;
863 if (!
item->retry.config)
return NULL;
911 if (!max_saved)
return NULL;
912 if (max_saved > 65536)
return NULL;
915 if (!
my)
return NULL;
931 fr_bio_retry_list_init(&
my->free);
932 for (i = 0; i < max_saved; i++) {
934 fr_bio_retry_list_insert_tail(&
my->free, &items[i]);
940 if (!
my->next_tl)
goto error;
945 if (!
my->expiry_tl)
goto error;
957 my->rewrite = rewrite;
959 my->response = response;
960 my->release = release;
963 my->info.el = cfg->
el;
995 if (!
my->partial)
return num;
1000 return num + !
my->partial->cancelled;
1012 item = fr_bio_retry_list_pop_head(&
my->free);
1013 if (!
item)
return NULL;
static int const char char buffer[256]
fr_bio_write_t _CONST write
write to the underlying bio
fr_bio_read_t _CONST read
read from the underlying bio
static fr_bio_t * fr_bio_next(fr_bio_t *bio)
static ssize_t fr_bio_retry_read(fr_bio_t *bio, void *packet_ctx, void *buffer, size_t size)
static int8_t _expiry_cmp(void const *one, void const *two)
fr_bio_retry_release_t release
callback to release a request / response pair
int fr_bio_retry_entry_init(UNUSED fr_bio_t *bio, fr_bio_retry_entry_t *item, fr_retry_config_t const *cfg)
Set a per-packet retry config.
fr_bio_t * fr_bio_retry_alloc(TALLOC_CTX *ctx, size_t max_saved, fr_bio_retry_sent_t sent, fr_bio_retry_response_t response, fr_bio_retry_rewrite_t rewrite, fr_bio_retry_release_t release, fr_bio_retry_config_t const *cfg, fr_bio_t *next)
Allocate a fr_bio_retry_t.
static ssize_t fr_bio_retry_save_write(fr_bio_retry_t *my, fr_bio_retry_entry_t *item, ssize_t rcode)
Save a partial packet when the write becomes blocked.
fr_bio_buf_t buffer
to store partial packets
static void fr_bio_retry_release(fr_bio_retry_t *my, fr_bio_retry_entry_t *item, fr_bio_retry_release_reason_t reason)
Release an entry back to the free list.
size_t fr_bio_retry_outstanding(fr_bio_t *bio)
fr_timer_list_t * next_tl
when packets are retried next
static void fr_bio_retry_next_timer(UNUSED fr_timer_list_t *tl, fr_time_t now, void *uctx)
Run a timer event.
int fr_bio_retry_entry_cancel(fr_bio_t *bio, fr_bio_retry_entry_t *item)
Cancel one item.
fr_timer_list_t * expiry_tl
when packets expire, so that we expire packets when the socket is blocked.
struct fr_bio_retry_list_s fr_bio_retry_list_t
ssize_t fr_bio_retry_rewrite(fr_bio_t *bio, fr_bio_retry_entry_t *item, const void *buffer, size_t size)
Resend a packet.
static int8_t _next_retry_cmp(void const *one, void const *two)
static int fr_bio_retry_write_item(fr_bio_retry_t *my, fr_bio_retry_entry_t *item, fr_time_t now)
Write one item.
static int fr_bio_retry_shutdown(fr_bio_t *bio)
Orderly shutdown.
fr_retry_config_t retry_config
static void fr_bio_retry_expiry_timer(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now, void *uctx)
Run an expiry timer event.
fr_bio_retry_entry_t * partial
for partial writes
const fr_retry_t * fr_bio_retry_entry_info(UNUSED fr_bio_t *bio, fr_bio_retry_entry_t *item)
Allow the callbacks / application to know when things are being retried.
fr_bio_retry_sent_t sent
callback for when we successfully sent a packet
fr_bio_retry_response_t response
callback to see if we got a valid response
static ssize_t fr_bio_retry_write(fr_bio_t *bio, void *packet_ctx, void const *buffer, size_t size)
Write a request, and see if we have a reply.
static ssize_t fr_bio_retry_write_fatal(fr_bio_t *bio, UNUSED void *packet_ctx, UNUSED void const *buffer, UNUSED size_t size)
A previous timer write had a fatal error, so we forbid further writes.
bool all_used
blocked due to no free entries
static int fr_bio_retry_write_blocked(fr_bio_t *bio)
Writes are blocked.
static ssize_t fr_bio_retry_write_partial(fr_bio_t *bio, void *packet_ctx, const void *buffer, size_t size)
There's a partial packet written.
fr_bio_retry_rewrite_t rewrite
optional callback which can change a packet on retry
fr_bio_retry_entry_t * fr_bio_retry_item_reserve(fr_bio_t *bio)
Reserve an entry for later use with fr_bio_retry_rewrite()
fr_bio_retry_info_t const * fr_bio_retry_info(fr_bio_t *bio)
static int fr_bio_retry_write_resume(fr_bio_t *bio)
Resume writes.
void * rewrite_ctx
context specifically for rewriting this packet
fr_retry_config_t retry_config
base retry config
void(* fr_bio_retry_release_t)(fr_bio_t *bio, fr_bio_retry_entry_t *retry_ctx, fr_bio_retry_release_reason_t reason)
Callback on release the packet (timeout or have all replies)
fr_retry_t retry
retry timers and counters
fr_bio_retry_release_reason_t
@ FR_BIO_RETRY_WRITE_ERROR
struct fr_bio_retry_entry_s fr_bio_retry_entry_t
uint8_t const * buffer
cached copy of the packet to send
size_t size
size of the cached packet
bool reserved
for application-layer watchdog
void(* fr_bio_retry_sent_t)(fr_bio_t *bio, void *packet_ctx, const void *buffer, size_t size, fr_bio_retry_entry_t *retry_ctx)
Callback for when a packet is sent.
ssize_t(* fr_bio_retry_rewrite_t)(fr_bio_t *bio, fr_bio_retry_entry_t *retry_ctx, const void *buffer, size_t size)
bool(* fr_bio_retry_response_t)(fr_bio_t *bio, fr_bio_retry_entry_t **item_p, void *packet_ctx, const void *buffer, size_t size)
Callback on read to see if a packet is a response.
fr_event_list_t * el
event list
void * packet_ctx
packet_ctx from the write() call
fr_rb_node_t expiry_node
for expiries
bool cancelled
was this item cancelled?
fr_bio_retry_t * my
so we can get to it from the event timer callback
fr_bio_retry_rewrite_t rewrite
per-packet rewrite callback
void * uctx
user-writable context
static void fr_bio_chain(fr_bio_t *first, fr_bio_t *second)
Chain one bio after another.
int fr_bio_buf_alloc(TALLOC_CTX *ctx, fr_bio_buf_t *bio_buf, size_t size)
ssize_t fr_bio_buf_write(fr_bio_buf_t *bio_buf, const void *buffer, size_t size)
static size_t fr_bio_buf_used(fr_bio_buf_t const *bio_buf)
static void fr_bio_buf_reset(fr_bio_buf_t *bio_buf)
static size_t fr_bio_buf_size(fr_bio_buf_t const *bio_buf)
#define FR_DLIST_TYPES(_name)
Define type specific wrapper structs for dlists.
#define FR_DLIST_ENTRY(_name)
Expands to the type name used for the entry wrapper structure.
#define FR_DLIST_FUNCS(_name, _element_type, _element_entry)
Define type specific wrapper functions for dlists.
#define FR_DLIST_HEAD(_name)
Expands to the type name used for the head wrapper structure.
void fr_bio_shutdown & my
int fr_bio_write_blocked(fr_bio_t *bio)
Internal BIO function to tell all BIOs that it's blocked.
int fr_bio_destructor(fr_bio_t *bio)
Free this bio.
static void * item(fr_lst_t const *lst, fr_lst_index_t idx)
ssize_t fr_bio_null_write(UNUSED fr_bio_t *bio, UNUSED void *packet_ctx, UNUSED void const *buffer, UNUSED size_t size)
Always return 0 on write.
static fr_time_t fr_time_add_time_delta(fr_time_t a, fr_time_delta_t b)
static int64_t fr_time_delta_unwrap(fr_time_delta_t time)
#define fr_time_lteq(_a, _b)
#define fr_time_delta_ispos(_a)
#define fr_time_gt(_a, _b)
#define fr_time_lt(_a, _b)
static int8_t fr_time_cmp(fr_time_t a, fr_time_t b)
Compare two fr_time_t values.
int fr_timer_list_disarm(fr_timer_list_t *tl)
Disarm a timer list.
uint64_t fr_timer_list_num_events(fr_timer_list_t *tl)
Return number of pending events.
int fr_timer_uctx_insert(fr_timer_list_t *tl, void *uctx)
Insert a uctx into a shared timer, and update the timer.
fr_timer_list_t * fr_timer_list_shared_alloc(TALLOC_CTX *ctx, fr_timer_list_t *parent, fr_cmp_t cmp, fr_timer_cb_t callback, size_t node_offset, size_t time_offset)
Allocate a new shared event timer list.
int fr_timer_uctx_remove(fr_timer_list_t *tl, void *uctx)
Remove a uctx from a shared timer.
void * fr_timer_uctx_peek(fr_timer_list_t *tl)
int fr_timer_list_arm(fr_timer_list_t *tl)
Arm (or re-arm) a timer list.
fr_retry_state_t fr_retry_next(fr_retry_t *r, fr_time_t now)
Initialize a retransmission counter.
void fr_retry_init(fr_retry_t *r, fr_time_t now, fr_retry_config_t const *config)
Initialize a retransmission counter.
fr_time_delta_t irt
Initial transmission time.
fr_time_t end
when we will end the retransmissions
fr_time_t next
when the next timer should be set