text: /**
   * @file serreg.c  Serial registration mode
   *
   * Copyright (C) 2020 Commend.com - c.spielberger@commend.com
   */
  #include <stdlib.h>
  #include <time.h>
  #include <re.h>
  #include <baresip.h>
  
  
  /**
   * @defgroup serreg serreg
   *
   * Serial registration mode
   *
   *     Accounts optionally have priorities (default prio=0). Accounts with prio
   *     0 are registered on startup. Prio 1 accounts are fallback accounts and
   *     are registered if all of the prio 0 accounts registrations fail. Prio 2
   *     accounts are fallback accounts second stage, and so on.
   *
   *     If a (re-)REGISTER fails, then switches to the next priority UA.
   *
   * Cisco mode
   *
   *     Additionally to the serial mode sends Cisco REGISTER keep-alives to
   *     not-registered UAs in order to poll their availability. This is only a
   *     name for a REGISTER with expires zero, thus a periodic un-REGISTER.
   *
   *     If a (re-)REGISTER with positive expires value fails, serreg switches to
   *     the next available UA.
   *
   *     If a UA with prio lower than the current becomes available again, serreg
   *     switches to the UA with the lower prio.
   */
  
  
  static struct {
  	uint32_t prio;            /**< Current account prio           */
  	uint32_t maxprio;         /**< Maximum account prio           */
  	bool ready;               /**< All UA registered flag         */
  	uint32_t sprio;           /**< Prev successful prio           */
  } sreg;
  
  
  /**
   * @return true if all registrations with current prio failed.
   */
  static bool check_registrations(void)
  {
  	struct le *le;
  	uint32_t n = 0;
  	uint32_t f = 0;
  	uint32_t r = 0;
  
  	for (le = list_head(uag_list()); le; le = le->next) {
  		struct ua *ua = le->data;
  		struct account *acc = ua_account(ua);
  		uint32_t prio = account_prio(acc);
  
  		if (!account_regint(acc))
  			continue;
  
  		if (prio > sreg.maxprio)
  			sreg.maxprio = prio;
  
  		if (prio == sreg.prio)
  			++n;
  
  		if (prio == sreg.prio && ua_regfailed(ua))
  			++f;
  
  		if (prio == sreg.prio && ua_isregistered(ua))
  			++r;
  	}
  
  	debug("serreg: %s:%d n=%u f=%u r=%u\n", __func__, __LINE__, n, f, r);
  	if (n == f)
  		return true;
  
  	if (f)
  		return false;
  
  	if (r < n)
  		return false;
  
  	if (sreg.ready)
  		return false;
  
  	/* We are ready */
  	ui_output(baresip_uis(),
  		  "\x1b[32m%s serreg: %u useragent%s with prio %u "
  		  "registered successfully! \x1b[;m\n",
  		  n==1 ? "" : "All",
  		  n, n==1 ? "" : "s",
  		  sreg.prio);
  
  	sreg.ready = true;
  	return false;
  }
  
  
  static int register_curprio(void)
  {
  	int err = EINVAL;
  	struct le *le;
  	for (le = list_head(uag_list()); le; le = le->next) {
  		struct ua *ua = le->data;
  		uint32_t prio = account_prio(ua_account(ua));
  		uint32_t fbregint = account_fbregint(ua_account(ua));
  
  		if (!account_regint(ua_account(ua)))
  			continue;
  
  		if (prio != sreg.prio)
  			continue;
  
  		if (!fbregint || !ua_regfailed(ua))
  			err = ua_register(ua);
  	}
  
  	return err;
  }
  
  
  static int fallback_update(void)
  {
  	int err = EINVAL;
  	struct le *le;
  
  	for (le = list_head(uag_list()); le; le = le->next) {
  		struct ua *ua = le->data;
  		const struct account *acc = ua_account(ua);
  		uint32_t prio = account_prio(acc);
  
  		if (!account_regint(acc))
  			continue;
  
  		if (prio == sreg.prio)
  			continue;
  
  		err = ua_fallback(ua);
  		if (err)
  			warning("serreg: could not start fallback %s (%m)\n",
  				account_aor(acc), err);
  	}
  
  	return err;
  }
  
  
  static void inc_account_prio(void)
  {
  	struct le *le;
  	uint32_t p = (uint32_t) -1;
  
  	for (le = list_head(uag_list()); le; le = le->next) {
  		struct ua *ua = le->data;
  		uint32_t prio = account_prio(ua_account(ua));
  
  		if (prio <= sreg.prio)
  			continue;
  
  		if (prio < p)
  			p = prio;
  	}
  
  	sreg.prio = p;
  	if (sreg.prio > sreg.maxprio)
  		sreg.prio = 0;
  
  	sreg.ready = false;
  }
  
  
  static void next_account(struct ua *ua)
  {
  	uint32_t prio = sreg.prio;
  
  	if (sreg.sprio == (uint32_t) -1)
  		sreg.sprio = prio;
  
  	while (check_registrations()) {
  		inc_account_prio();
  
  		if (sreg.sprio == sreg.prio) {
  			/* no UA with success */
  			sreg.prio = (uint32_t) -1;
  			break;
  		}
  
  		info("serreg: Register %s fail -> prio %u.\n",
  		     account_aor(ua_account(ua)), sreg.prio);
  		if (!register_curprio())
  			break;
  
  		if (prio == sreg.prio) {
  			/* found none */
  			sreg.prio = (uint32_t) -1;
  			break;
  		}
  	}
  }
  
  
  static void fallback_ok(struct ua *ua)
  {
  	const struct account *acc = ua_account(ua);
  	uint32_t prio = account_prio(acc);
  
  	debug("serreg: fallback prio %u ok %s.\n", prio, account_aor(acc));
  
  	if (prio <= sreg.prio) {
  		info("serreg: Fallback %s ok -> prio %u.\n",
  		     account_aor(acc), prio);
  		sreg.prio = prio;
  		sreg.ready = false;
  		if (!register_curprio())
  			(void)fallback_update();
  	}
  }
  
  
  static void restart(void)
  {
  	struct le *le;
  
  	for (le = list_head(uag_list()); le; le = le->next) {
  		struct ua *ua = le->data;
  		struct account *acc = ua_account(ua);
  		uint32_t prio = account_prio(acc);
  		uint32_t fbregint = account_fbregint(acc);
  
  		if (!account_regint(acc))
  			continue;
  
  		/* fbregint accounts don't need a restart */
  		if (prio || fbregint)
  			continue;
  
  		debug("serreg: restart %s prio 0.\n", account_aor(acc));
  		ua_register(ua);
  		sreg.prio = 0;
  		sreg.sprio = (uint32_t) -1;
  	}
  }
  
  
  static void ua_event_handler(struct ua *ua, enum ua_event ev,
  			     struct call *call, const char *prm, void *arg)
  {
  	(void)call;
  	(void)prm;
  	(void)arg;
  
  	switch (ev) {
  
  	case UA_EVENT_FALLBACK_FAIL:
  		debug("serreg: fallback fail %s.\n",
  		      account_aor(ua_account(ua)));
  		break;
  
  	case UA_EVENT_FALLBACK_OK:
  		fallback_ok(ua);
  		break;
  
  	case UA_EVENT_REGISTER_OK:
  		sreg.prio = account_prio(ua_account(ua));
  		check_registrations();
  		sreg.sprio = sreg.prio;
  		break;
  
  	case UA_EVENT_REGISTER_FAIL:
  		/* did we already increment? */
  		if (account_prio(ua_account(ua)) != sreg.prio)
  			break;
  
  		next_account(ua);
  		if (account_fbregint(ua_account(ua)))
  			(void)ua_fallback(ua);
  		else
  			ua_unregister(ua);
  
  		if (sreg.prio == (uint32_t) -1)
  			restart();
  		break;
  
  	default:
  		break;
  	}
  }
  
  
  static int module_init(void)
  {
  	int err;
  
  	sreg.maxprio = 0;
  	sreg.prio = 0;
  	sreg.ready = false;
  	sreg.sprio = (uint32_t) -1;
  
  	err = uag_event_register(ua_event_handler, NULL);
  	return err;
  }
  
  
  static int module_close(void)
  {
  	uag_event_unregister(ua_event_handler);
  
  	return 0;
  }
  
  
  const struct mod_export DECL_EXPORTS(serreg) = {
  	"serreg",
  	"application",
  	module_init,
  	module_close
  };
  