+/* --- @f2tv@ --- *
+ *
+ * Arguments: @struct timeval *tv@ = where to write the timeval
+ * @double t@ = a time as a floating point number
+ *
+ * Returns: ---
+ *
+ * Use: Converts a floating-point time into a timeval.
+ */
+
+static void f2tv(struct timeval *tv, double t)
+{
+ tv->tv_sec = t;
+ tv->tv_usec = (t - tv->tv_sec)*MILLION;
+}
+
+/* --- @wobble@ --- *
+ *
+ * Arguments: @double t@ = a time interval
+ *
+ * Returns: The same time interval, with a random error applied.
+ */
+
+static double wobble(double t)
+{
+ uint32 r = rand_global.ops->word(&rand_global);
+ double w = (r/F_2P32) - 0.5;
+ return (t + t*w*T_WOBBLE);
+}
+
+/* --- @rs_time@ --- *
+ *
+ * Arguments: @retry *rs@ = current retry state
+ * @struct timeval *tv@ = where to write the result
+ * @const struct timeval *now@ = current time, or null
+ *
+ * Returns: ---
+ *
+ * Use: Computes a time at which to retry sending a key-exchange
+ * packet. This algorithm is subject to change, but it's
+ * currently a capped exponential backoff, slightly randomized
+ * to try to keep clients from hammering a server that's only
+ * just woken up.
+ *
+ * If @now@ is null then the function works out the time for
+ * itself.
+ */
+
+static void rs_time(retry *rs, struct timeval *tv, const struct timeval *now)
+{
+ double t;
+ struct timeval rtv;
+
+ if (!rs->t)
+ t = SEC(2);
+ else {
+ t = (rs->t * 5)/4;
+ if (t > MIN(5)) t = MIN(5);
+ }
+ rs->t = t;
+
+ if (!now) {
+ now = tv;
+ gettimeofday(tv, 0);
+ }
+ f2tv(&rtv, wobble(t));
+ TV_ADD(tv, now, &rtv);
+}
+
+/* --- @retry_reset@ --- *
+ *
+ * Arguments: @retry *rs@ = retry state
+ *
+ * Returns: --
+ *
+ * Use: Resets a retry state to indicate that progress has been
+ * made. Also useful for initializing the state in the first
+ * place.
+ */
+
+static void rs_reset(retry *rs) { rs->t = 0; }
+
+/* --- @notice_message@ --- *
+ *
+ * Arguments: @keyexch *kx@ = pointer to key-exchange block
+ *
+ * Returns: Zero if OK; @-1@ if the public key is in a bad state.
+ *
+ * Use: Updates the key-exchange state following a received message.
+ * Specifically, if there's no currently active key-exchange in
+ * progress, and we're not in the cooling-off period, then
+ * commence a new one; reset the retry timers; and if we're
+ * corked then pop the cork so that we can reply.
+ */
+
+static int checkpub(keyexch *kx);
+static void stop(keyexch *kx);
+static void start(keyexch *kx, time_t now);
+
+static int notice_message(keyexch *kx)
+{
+ struct timeval now, tv;
+
+ gettimeofday(&now, 0);
+ rs_reset(&kx->rs);
+ if (kx->f & KXF_CORK) {
+ start(kx, now.tv_sec);
+ rs_time(&kx->rs, &tv, &now);
+ settimer(kx, &tv);
+ a_notify("KXSTART", "?PEER", kx->p, A_END);
+ }
+ if (checkpub(kx)) return (-1);
+ if (!VALIDP(kx, now.tv_sec)) {
+ stop(kx);
+ start(kx, now.tv_sec);
+ }
+ return (0);
+}
+
+/* --- @update_stats_tx@, @update_stats_rx@ --- *
+ *
+ * Arguments: @keyexch *kx@ = pointer to key-exchange block
+ * @int ok@ = nonzero if the message was valid (for @rx@)
+ * @size_t sz@ = size of sent message
+ *
+ * Returns: ---
+ *
+ * Use: Records that a key-exchange message was sent to, or received
+ * from, the peer.
+ */
+
+static void update_stats_tx(keyexch *kx, size_t sz)
+ { stats *st = p_stats(kx->p); st->n_kxout++; st->sz_kxout += sz; }
+
+static void update_stats_rx(keyexch *kx, int ok, size_t sz)
+{
+ stats *st = p_stats(kx->p);
+
+ if (!ok) st->n_reject++;
+ else { st->n_kxin++; st->sz_kxin += sz; }
+}
+