src/openvpn/reliable.c
6fbf66fa
 /*
  *  OpenVPN -- An application to securely tunnel IP networks
  *             over a single UDP port, with support for SSL/TLS-based
  *             session authentication and key exchange,
  *             packet encryption, packet authentication, and
  *             packet compression.
  *
49979459
  *  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
6fbf66fa
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2
  *  as published by the Free Software Foundation.
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
caa54ac3
  *  You should have received a copy of the GNU General Public License along
  *  with this program; if not, write to the Free Software Foundation, Inc.,
  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
6fbf66fa
  */
 
 /*
  * These routines implement a reliability layer on top of UDP,
  * so that SSL/TLS can be run over UDP.
  */
 
c110b289
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #elif defined(_MSC_VER)
 #include "config-msvc.h"
 #endif
 
1bda73a7
 #include "syshead.h"
6fbf66fa
 
 #include "buffer.h"
 #include "error.h"
 #include "common.h"
 #include "reliable.h"
 
 #include "memdbg.h"
 
a1849f41
 /*
  * verify that test - base < extent while allowing for base or test wraparound
  */
 static inline bool
81d882d5
 reliable_pid_in_range1(const packet_id_type test,
                        const packet_id_type base,
                        const unsigned int extent)
a1849f41
 {
81d882d5
     if (test >= base)
a1849f41
     {
81d882d5
         if (test - base < extent)
         {
             return true;
         }
a1849f41
     }
81d882d5
     else
5fc10872
     {
81d882d5
         if ((test+0x80000000u) - (base+0x80000000u) < extent)
         {
             return true;
         }
5fc10872
     }
 
81d882d5
     return false;
5fc10872
 }
 
 /*
  * verify that test < base + extent while allowing for base or test wraparound
  */
 static inline bool
81d882d5
 reliable_pid_in_range2(const packet_id_type test,
                        const packet_id_type base,
                        const unsigned int extent)
5fc10872
 {
81d882d5
     if (base + extent >= base)
5fc10872
     {
81d882d5
         if (test < base + extent)
         {
             return true;
         }
5fc10872
     }
81d882d5
     else
5fc10872
     {
81d882d5
         if ((test+0x80000000u) < (base+0x80000000u) + extent)
         {
             return true;
         }
a1849f41
     }
 
81d882d5
     return false;
a1849f41
 }
 
 /*
  * verify that p1 < p2  while allowing for p1 or p2 wraparound
  */
 static inline bool
81d882d5
 reliable_pid_min(const packet_id_type p1,
                  const packet_id_type p2)
a1849f41
 {
81d882d5
     return !reliable_pid_in_range1(p1, p2, 0x80000000u);
a1849f41
 }
 
6fbf66fa
 /* check if a particular packet_id is present in ack */
 static inline bool
81d882d5
 reliable_ack_packet_id_present(struct reliable_ack *ack, packet_id_type pid)
6fbf66fa
 {
81d882d5
     int i;
     for (i = 0; i < ack->len; ++i)
4cd4899e
     {
81d882d5
         if (ack->packet_id[i] == pid)
         {
             return true;
         }
4cd4899e
     }
81d882d5
     return false;
6fbf66fa
 }
 
 /* get a packet_id from buf */
 bool
81d882d5
 reliable_ack_read_packet_id(struct buffer *buf, packet_id_type *pid)
6fbf66fa
 {
81d882d5
     packet_id_type net_pid;
6fbf66fa
 
81d882d5
     if (buf_read(buf, &net_pid, sizeof(net_pid)))
6fbf66fa
     {
81d882d5
         *pid = ntohpid(net_pid);
         dmsg(D_REL_DEBUG, "ACK read ID " packet_id_format " (buf->len=%d)",
              (packet_id_print_type)*pid, buf->len);
         return true;
6fbf66fa
     }
 
81d882d5
     dmsg(D_REL_LOW, "ACK read ID FAILED (buf->len=%d)", buf->len);
     return false;
6fbf66fa
 }
 
 /* acknowledge a packet_id by adding it to a struct reliable_ack */
 bool
81d882d5
 reliable_ack_acknowledge_packet_id(struct reliable_ack *ack, packet_id_type pid)
6fbf66fa
 {
81d882d5
     if (!reliable_ack_packet_id_present(ack, pid) && ack->len < RELIABLE_ACK_SIZE)
6fbf66fa
     {
81d882d5
         ack->packet_id[ack->len++] = pid;
         dmsg(D_REL_DEBUG, "ACK acknowledge ID " packet_id_format " (ack->len=%d)",
              (packet_id_print_type)pid, ack->len);
         return true;
6fbf66fa
     }
 
81d882d5
     dmsg(D_REL_LOW, "ACK acknowledge ID " packet_id_format " FAILED (ack->len=%d)",
          (packet_id_print_type)pid, ack->len);
     return false;
6fbf66fa
 }
 
 /* read a packet ID acknowledgement record from buf into ack */
 bool
81d882d5
 reliable_ack_read(struct reliable_ack *ack,
                   struct buffer *buf, const struct session_id *sid)
6fbf66fa
 {
81d882d5
     struct gc_arena gc = gc_new();
     int i;
     uint8_t count;
     packet_id_type net_pid;
     packet_id_type pid;
     struct session_id session_id_remote;
 
     if (!buf_read(buf, &count, sizeof(count)))
     {
         goto error;
     }
     for (i = 0; i < count; ++i)
     {
         if (!buf_read(buf, &net_pid, sizeof(net_pid)))
         {
             goto error;
         }
         if (ack->len >= RELIABLE_ACK_SIZE)
         {
             goto error;
         }
         pid = ntohpid(net_pid);
         ack->packet_id[ack->len++] = pid;
     }
     if (count)
     {
         if (!session_id_read(&session_id_remote, buf))
         {
             goto error;
         }
         if (!session_id_defined(&session_id_remote)
             || !session_id_equal(&session_id_remote, sid))
         {
             dmsg(D_REL_LOW,
                  "ACK read BAD SESSION-ID FROM REMOTE, local=%s, remote=%s",
                  session_id_print(sid, &gc), session_id_print(&session_id_remote, &gc));
             goto error;
         }
     }
     gc_free(&gc);
     return true;
6fbf66fa
 
 error:
81d882d5
     gc_free(&gc);
     return false;
6fbf66fa
 }
 
81d882d5
 #define ACK_SIZE(n) (sizeof(uint8_t) + ((n) ? SID_SIZE : 0) + sizeof(packet_id_type) * (n))
6fbf66fa
 
 /* write a packet ID acknowledgement record to buf, */
 /* removing all acknowledged entries from ack */
 bool
81d882d5
 reliable_ack_write(struct reliable_ack *ack,
                    struct buffer *buf,
                    const struct session_id *sid, int max, bool prepend)
6fbf66fa
 {
81d882d5
     int i, j;
     uint8_t n;
     struct buffer sub;
6fbf66fa
 
81d882d5
     n = ack->len;
     if (n > max)
6fbf66fa
     {
81d882d5
         n = max;
6fbf66fa
     }
81d882d5
     sub = buf_sub(buf, ACK_SIZE(n), prepend);
     if (!BDEF(&sub))
6fbf66fa
     {
81d882d5
         goto error;
     }
     ASSERT(buf_write(&sub, &n, sizeof(n)));
     for (i = 0; i < n; ++i)
     {
         packet_id_type pid = ack->packet_id[i];
         packet_id_type net_pid = htonpid(pid);
         ASSERT(buf_write(&sub, &net_pid, sizeof(net_pid)));
         dmsg(D_REL_DEBUG, "ACK write ID " packet_id_format " (ack->len=%d, n=%d)", (packet_id_print_type)pid, ack->len, n);
     }
     if (n)
     {
         ASSERT(session_id_defined(sid));
         ASSERT(session_id_write(sid, &sub));
         for (i = 0, j = n; j < ack->len; )
4cd4899e
         {
81d882d5
             ack->packet_id[i++] = ack->packet_id[j++];
4cd4899e
         }
81d882d5
         ack->len = i;
6fbf66fa
     }
 
81d882d5
     return true;
6fbf66fa
 
 error:
81d882d5
     return false;
6fbf66fa
 }
 
 /* add to extra_frame the maximum number of bytes we will need for reliable_ack_write */
 void
81d882d5
 reliable_ack_adjust_frame_parameters(struct frame *frame, int max)
6fbf66fa
 {
81d882d5
     frame_add_to_extra_frame(frame, ACK_SIZE(max));
6fbf66fa
 }
 
 /* print a reliable ACK record coming off the wire */
 const char *
81d882d5
 reliable_ack_print(struct buffer *buf, bool verbose, struct gc_arena *gc)
6fbf66fa
 {
81d882d5
     int i;
     uint8_t n_ack;
     struct session_id sid_ack;
     packet_id_type pid;
     struct buffer out = alloc_buf_gc(256, gc);
6fbf66fa
 
81d882d5
     buf_printf(&out, "[");
     if (!buf_read(buf, &n_ack, sizeof(n_ack)))
6fbf66fa
     {
81d882d5
         goto done;
6fbf66fa
     }
81d882d5
     for (i = 0; i < n_ack; ++i)
6fbf66fa
     {
81d882d5
         if (!buf_read(buf, &pid, sizeof(pid)))
         {
             goto done;
         }
         pid = ntohpid(pid);
         buf_printf(&out, " " packet_id_format, (packet_id_print_type)pid);
     }
     if (n_ack)
     {
         if (!session_id_read(&sid_ack, buf))
         {
             goto done;
         }
         if (verbose)
         {
             buf_printf(&out, " sid=%s", session_id_print(&sid_ack, gc));
         }
6fbf66fa
     }
 
81d882d5
 done:
     buf_printf(&out, " ]");
     return BSTR(&out);
6fbf66fa
 }
 
 /*
  * struct reliable member functions.
  */
 
 void
81d882d5
 reliable_init(struct reliable *rel, int buf_size, int offset, int array_size, bool hold)
6fbf66fa
 {
81d882d5
     int i;
6fbf66fa
 
81d882d5
     CLEAR(*rel);
     ASSERT(array_size > 0 && array_size <= RELIABLE_CAPACITY);
     rel->hold = hold;
     rel->size = array_size;
     rel->offset = offset;
     for (i = 0; i < rel->size; ++i)
6fbf66fa
     {
81d882d5
         struct reliable_entry *e = &rel->array[i];
         e->buf = alloc_buf(buf_size);
         ASSERT(buf_init(&e->buf, offset));
6fbf66fa
     }
 }
 
 void
81d882d5
 reliable_free(struct reliable *rel)
6fbf66fa
 {
81d882d5
     int i;
     for (i = 0; i < rel->size; ++i)
6fbf66fa
     {
81d882d5
         struct reliable_entry *e = &rel->array[i];
         free_buf(&e->buf);
6fbf66fa
     }
 }
 
 /* no active buffers? */
 bool
81d882d5
 reliable_empty(const struct reliable *rel)
6fbf66fa
 {
81d882d5
     int i;
     for (i = 0; i < rel->size; ++i)
6fbf66fa
     {
81d882d5
         const struct reliable_entry *e = &rel->array[i];
         if (e->active)
         {
             return false;
         }
6fbf66fa
     }
81d882d5
     return true;
6fbf66fa
 }
 
 /* del acknowledged items from send buf */
 void
81d882d5
 reliable_send_purge(struct reliable *rel, struct reliable_ack *ack)
6fbf66fa
 {
81d882d5
     int i, j;
     for (i = 0; i < ack->len; ++i)
     {
         packet_id_type pid = ack->packet_id[i];
         for (j = 0; j < rel->size; ++j)
         {
             struct reliable_entry *e = &rel->array[j];
             if (e->active && e->packet_id == pid)
             {
                 dmsg(D_REL_DEBUG,
                      "ACK received for pid " packet_id_format ", deleting from send buffer",
                      (packet_id_print_type)pid);
6fbf66fa
 #if 0
81d882d5
                 /* DEBUGGING -- how close were we timing out on ACK failure and resending? */
                 {
                     if (e->next_try)
                     {
                         const interval_t wake = e->next_try - now;
                         msg(M_INFO, "ACK " packet_id_format ", wake=%d", pid, wake);
                     }
                 }
6fbf66fa
 #endif
81d882d5
                 e->active = false;
                 break;
             }
         }
6fbf66fa
     }
 }
 
 /* print the current sequence of active packet IDs */
 static const char *
81d882d5
 reliable_print_ids(const struct reliable *rel, struct gc_arena *gc)
6fbf66fa
 {
81d882d5
     struct buffer out = alloc_buf_gc(256, gc);
     int i;
6fbf66fa
 
81d882d5
     buf_printf(&out, "[" packet_id_format "]", (packet_id_print_type)rel->packet_id);
     for (i = 0; i < rel->size; ++i)
6fbf66fa
     {
81d882d5
         const struct reliable_entry *e = &rel->array[i];
         if (e->active)
         {
             buf_printf(&out, " " packet_id_format, (packet_id_print_type)e->packet_id);
         }
6fbf66fa
     }
81d882d5
     return BSTR(&out);
6fbf66fa
 }
 
 /* true if at least one free buffer available */
 bool
81d882d5
 reliable_can_get(const struct reliable *rel)
6fbf66fa
 {
81d882d5
     struct gc_arena gc = gc_new();
     int i;
     for (i = 0; i < rel->size; ++i)
     {
         const struct reliable_entry *e = &rel->array[i];
         if (!e->active)
         {
             return true;
         }
     }
     dmsg(D_REL_LOW, "ACK no free receive buffer available: %s", reliable_print_ids(rel, &gc));
     gc_free(&gc);
     return false;
6fbf66fa
 }
 
 /* make sure that incoming packet ID isn't a replay */
 bool
81d882d5
 reliable_not_replay(const struct reliable *rel, packet_id_type id)
6fbf66fa
 {
81d882d5
     struct gc_arena gc = gc_new();
     int i;
     if (reliable_pid_min(id, rel->packet_id))
6fbf66fa
     {
81d882d5
         goto bad;
6fbf66fa
     }
81d882d5
     for (i = 0; i < rel->size; ++i)
     {
         const struct reliable_entry *e = &rel->array[i];
         if (e->active && e->packet_id == id)
         {
             goto bad;
         }
     }
     gc_free(&gc);
     return true;
6fbf66fa
 
81d882d5
 bad:
     dmsg(D_REL_DEBUG, "ACK " packet_id_format " is a replay: %s", (packet_id_print_type)id, reliable_print_ids(rel, &gc));
     gc_free(&gc);
     return false;
6fbf66fa
 }
 
 /* make sure that incoming packet ID won't deadlock the receive buffer */
 bool
81d882d5
 reliable_wont_break_sequentiality(const struct reliable *rel, packet_id_type id)
6fbf66fa
 {
81d882d5
     struct gc_arena gc = gc_new();
6fbf66fa
 
81d882d5
     const int ret = reliable_pid_in_range2(id, rel->packet_id, rel->size);
a1849f41
 
81d882d5
     if (!ret)
6fbf66fa
     {
81d882d5
         dmsg(D_REL_LOW, "ACK " packet_id_format " breaks sequentiality: %s",
              (packet_id_print_type)id, reliable_print_ids(rel, &gc));
6fbf66fa
     }
a1849f41
 
81d882d5
     dmsg(D_REL_DEBUG, "ACK RWBS rel->size=%d rel->packet_id=%08x id=%08x ret=%d\n", rel->size, rel->packet_id, id, ret);
a1849f41
 
81d882d5
     gc_free(&gc);
     return ret;
6fbf66fa
 }
 
 /* grab a free buffer */
 struct buffer *
81d882d5
 reliable_get_buf(struct reliable *rel)
6fbf66fa
 {
81d882d5
     int i;
     for (i = 0; i < rel->size; ++i)
6fbf66fa
     {
81d882d5
         struct reliable_entry *e = &rel->array[i];
         if (!e->active)
         {
             ASSERT(buf_init(&e->buf, rel->offset));
             return &e->buf;
         }
6fbf66fa
     }
81d882d5
     return NULL;
6fbf66fa
 }
 
 /* grab a free buffer, fail if buffer clogged by unacknowledged low packet IDs */
 struct buffer *
81d882d5
 reliable_get_buf_output_sequenced(struct reliable *rel)
6fbf66fa
 {
81d882d5
     struct gc_arena gc = gc_new();
     int i;
     packet_id_type min_id = 0;
     bool min_id_defined = false;
     struct buffer *ret = NULL;
6fbf66fa
 
81d882d5
     /* find minimum active packet_id */
     for (i = 0; i < rel->size; ++i)
6fbf66fa
     {
81d882d5
         const struct reliable_entry *e = &rel->array[i];
         if (e->active)
         {
             if (!min_id_defined || reliable_pid_min(e->packet_id, min_id))
             {
                 min_id_defined = true;
                 min_id = e->packet_id;
             }
         }
6fbf66fa
     }
 
81d882d5
     if (!min_id_defined || reliable_pid_in_range1(rel->packet_id, min_id, rel->size))
6fbf66fa
     {
81d882d5
         ret = reliable_get_buf(rel);
6fbf66fa
     }
81d882d5
     else
6fbf66fa
     {
81d882d5
         dmsg(D_REL_LOW, "ACK output sequence broken: %s", reliable_print_ids(rel, &gc));
6fbf66fa
     }
81d882d5
     gc_free(&gc);
     return ret;
6fbf66fa
 }
 
 /* get active buffer for next sequentially increasing key ID */
 struct buffer *
81d882d5
 reliable_get_buf_sequenced(struct reliable *rel)
6fbf66fa
 {
81d882d5
     int i;
     for (i = 0; i < rel->size; ++i)
6fbf66fa
     {
81d882d5
         struct reliable_entry *e = &rel->array[i];
         if (e->active && e->packet_id == rel->packet_id)
         {
             return &e->buf;
         }
6fbf66fa
     }
81d882d5
     return NULL;
6fbf66fa
 }
 
 /* return true if reliable_send would return a non-NULL result */
 bool
81d882d5
 reliable_can_send(const struct reliable *rel)
6fbf66fa
 {
81d882d5
     struct gc_arena gc = gc_new();
     int i;
     int n_active = 0, n_current = 0;
     for (i = 0; i < rel->size; ++i)
     {
         const struct reliable_entry *e = &rel->array[i];
         if (e->active)
         {
             ++n_active;
             if (now >= e->next_try)
             {
                 ++n_current;
             }
         }
     }
     dmsg(D_REL_DEBUG, "ACK reliable_can_send active=%d current=%d : %s",
          n_active,
          n_current,
          reliable_print_ids(rel, &gc));
 
     gc_free(&gc);
     return n_current > 0 && !rel->hold;
6fbf66fa
 }
 
 /* return next buffer to send to remote */
 struct buffer *
81d882d5
 reliable_send(struct reliable *rel, int *opcode)
6fbf66fa
 {
81d882d5
     int i;
     struct reliable_entry *best = NULL;
     const time_t local_now = now;
6fbf66fa
 
81d882d5
     for (i = 0; i < rel->size; ++i)
6fbf66fa
     {
81d882d5
         struct reliable_entry *e = &rel->array[i];
         if (e->active && local_now >= e->next_try)
         {
             if (!best || reliable_pid_min(e->packet_id, best->packet_id))
             {
                 best = e;
             }
         }
6fbf66fa
     }
81d882d5
     if (best)
6fbf66fa
     {
 #ifdef EXPONENTIAL_BACKOFF
81d882d5
         /* exponential backoff */
2af8853e
         best->next_try = local_now + best->timeout;
81d882d5
         best->timeout *= 2;
6fbf66fa
 #else
81d882d5
         /* constant timeout, no backoff */
         best->next_try = local_now + best->timeout;
6fbf66fa
 #endif
81d882d5
         *opcode = best->opcode;
         dmsg(D_REL_DEBUG, "ACK reliable_send ID " packet_id_format " (size=%d to=%d)",
              (packet_id_print_type)best->packet_id, best->buf.len,
              (int)(best->next_try - local_now));
         return &best->buf;
6fbf66fa
     }
81d882d5
     return NULL;
6fbf66fa
 }
 
 /* schedule all pending packets for immediate retransmit */
 void
81d882d5
 reliable_schedule_now(struct reliable *rel)
6fbf66fa
 {
81d882d5
     int i;
     dmsg(D_REL_DEBUG, "ACK reliable_schedule_now");
     rel->hold = false;
     for (i = 0; i < rel->size; ++i)
     {
         struct reliable_entry *e = &rel->array[i];
         if (e->active)
         {
             e->next_try = now;
             e->timeout = rel->initial_timeout;
         }
6fbf66fa
     }
 }
 
 /* in how many seconds should we wake up to check for timeout */
 /* if we return BIG_TIMEOUT, nothing to wait for */
 interval_t
81d882d5
 reliable_send_timeout(const struct reliable *rel)
6fbf66fa
 {
81d882d5
     struct gc_arena gc = gc_new();
     interval_t ret = BIG_TIMEOUT;
     int i;
     const time_t local_now = now;
 
     for (i = 0; i < rel->size; ++i)
     {
         const struct reliable_entry *e = &rel->array[i];
         if (e->active)
         {
             if (e->next_try <= local_now)
             {
                 ret = 0;
                 break;
             }
             else
             {
                 ret = min_int(ret, e->next_try - local_now);
             }
         }
     }
 
     dmsg(D_REL_DEBUG, "ACK reliable_send_timeout %d %s",
          (int) ret,
          reliable_print_ids(rel, &gc));
 
     gc_free(&gc);
     return ret;
6fbf66fa
 }
 
 /*
  * Enable an incoming buffer previously returned by a get function as active.
  */
 
 void
81d882d5
 reliable_mark_active_incoming(struct reliable *rel, struct buffer *buf,
                               packet_id_type pid, int opcode)
6fbf66fa
 {
81d882d5
     int i;
     for (i = 0; i < rel->size; ++i)
6fbf66fa
     {
81d882d5
         struct reliable_entry *e = &rel->array[i];
         if (buf == &e->buf)
         {
             e->active = true;
6fbf66fa
 
81d882d5
             /* packets may not arrive in sequential order */
             e->packet_id = pid;
6fbf66fa
 
81d882d5
             /* check for replay */
             ASSERT(!reliable_pid_min(pid, rel->packet_id));
6fbf66fa
 
81d882d5
             e->opcode = opcode;
             e->next_try = 0;
             e->timeout = 0;
             dmsg(D_REL_DEBUG, "ACK mark active incoming ID " packet_id_format, (packet_id_print_type)e->packet_id);
             return;
         }
6fbf66fa
     }
81d882d5
     ASSERT(0);                  /* buf not found in rel */
6fbf66fa
 }
 
 /*
  * Enable an outgoing buffer previously returned by a get function as active.
  */
 
 void
81d882d5
 reliable_mark_active_outgoing(struct reliable *rel, struct buffer *buf, int opcode)
6fbf66fa
 {
81d882d5
     int i;
     for (i = 0; i < rel->size; ++i)
     {
         struct reliable_entry *e = &rel->array[i];
         if (buf == &e->buf)
         {
             /* Write mode, increment packet_id (i.e. sequence number)
              * linearly and prepend id to packet */
             packet_id_type net_pid;
             e->packet_id = rel->packet_id++;
             net_pid = htonpid(e->packet_id);
             ASSERT(buf_write_prepend(buf, &net_pid, sizeof(net_pid)));
             e->active = true;
             e->opcode = opcode;
             e->next_try = 0;
             e->timeout = rel->initial_timeout;
             dmsg(D_REL_DEBUG, "ACK mark active outgoing ID " packet_id_format, (packet_id_print_type)e->packet_id);
             return;
         }
     }
     ASSERT(0);                  /* buf not found in rel */
6fbf66fa
 }
 
 /* delete a buffer previously activated by reliable_mark_active() */
 void
81d882d5
 reliable_mark_deleted(struct reliable *rel, struct buffer *buf, bool inc_pid)
6fbf66fa
 {
81d882d5
     int i;
     for (i = 0; i < rel->size; ++i)
     {
         struct reliable_entry *e = &rel->array[i];
         if (buf == &e->buf)
         {
             e->active = false;
             if (inc_pid)
             {
                 rel->packet_id = e->packet_id + 1;
             }
             return;
         }
     }
     ASSERT(0);
6fbf66fa
 }
 
 #if 0
 
 void
81d882d5
 reliable_ack_debug_print(const struct reliable_ack *ack, char *desc)
6fbf66fa
 {
81d882d5
     int i;
6fbf66fa
 
81d882d5
     printf("********* struct reliable_ack %s\n", desc);
     for (i = 0; i < ack->len; ++i)
6fbf66fa
     {
81d882d5
         printf("  %d: " packet_id_format "\n", i, (packet_id_print_type) ack->packet_id[i]);
6fbf66fa
     }
 }
 
 void
81d882d5
 reliable_debug_print(const struct reliable *rel, char *desc)
6fbf66fa
 {
81d882d5
     int i;
     update_time();
 
     printf("********* struct reliable %s\n", desc);
     printf("  initial_timeout=%d\n", (int)rel->initial_timeout);
     printf("  packet_id=" packet_id_format "\n", rel->packet_id);
06ad53e0
     printf("  now=%"PRIi64"\n", (int64_t)now);
81d882d5
     for (i = 0; i < rel->size; ++i)
     {
         const struct reliable_entry *e = &rel->array[i];
         if (e->active)
         {
             printf("  %d: packet_id=" packet_id_format " len=%d", i, e->packet_id, e->buf.len);
06ad53e0
             printf(" next_try=%"PRIi64, (int64_t)e->next_try);
81d882d5
             printf("\n");
         }
6fbf66fa
     }
 }
 
81d882d5
 #endif /* if 0 */