net: if: Lower ram usage for IP address lifetime handling

Instead of having one delayed_work struct / IP address, use
only one delayed_work struct for lifetime timer. This saves
over 20 bytes / allocated address struct.

Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
diff --git a/include/net/net_if.h b/include/net/net_if.h
index 98c88ef..6fbf08f 100644
--- a/include/net/net_if.h
+++ b/include/net/net_if.h
@@ -49,8 +49,16 @@
 	/** IP address */
 	struct net_addr address;
 
-	/** Timer that triggers renewal */
-	struct k_delayed_work lifetime;
+#if defined(CONFIG_NET_IPV6)
+	/** Used for track timers */
+	sys_snode_t node;
+
+	/** Address lifetime timer start time */
+	s64_t lifetime_timer_start;
+
+	/** lifetime timer timeout */
+	u32_t lifetime_timer_timeout;
+#endif
 
 #if defined(CONFIG_NET_IPV6_DAD)
 	/** Duplicate address detection (DAD) timer */
diff --git a/samples/net/rpl_border_router/src/http.c b/samples/net/rpl_border_router/src/http.c
index 87bdada..784f6e0 100644
--- a/samples/net/rpl_border_router/src/http.c
+++ b/samples/net/rpl_border_router/src/http.c
@@ -12,6 +12,7 @@
 
 #include <zephyr.h>
 #include <stdio.h>
+#include <stdlib.h>
 
 /* For Basic auth, we need base64 decoder which can be found
  * in mbedtls library.
@@ -497,6 +498,19 @@
 	return ret;
 }
 
+static inline u32_t remaining_lifetime(struct net_if_addr *ifaddr)
+{
+	s32_t remaining;
+
+	if (ifaddr->lifetime_timer_timeout == 0) {
+		return 0;
+	}
+
+	remaining = k_uptime_get() - ifaddr->lifetime_timer_start;
+
+	return abs(remaining) / K_MSEC(1000);
+}
+
 static void append_unicast_addr(struct net_if *iface, struct user_data *data)
 {
 	char addr[NET_IPV6_ADDR_LEN], lifetime[10];
@@ -525,9 +539,8 @@
 		if (unicast->is_infinite) {
 			snprintk(lifetime, sizeof(lifetime), "%s", "infinite");
 		} else {
-			snprintk(lifetime, sizeof(lifetime), "%d",
-				 k_delayed_work_remaining_get(
-					 &unicast->lifetime));
+			snprintk(lifetime, sizeof(lifetime), "%u",
+				 remaining_lifetime(unicast));
 		}
 
 		ret = append_and_send_data(data, false,
diff --git a/subsys/net/ip/ipv6.c b/subsys/net/ip/ipv6.c
index 3a56a09..7397c92 100644
--- a/subsys/net/ip/ipv6.c
+++ b/subsys/net/ip/ipv6.c
@@ -19,6 +19,7 @@
 #endif
 
 #include <errno.h>
+#include <stdlib.h>
 #include <net/net_core.h>
 #include <net/net_pkt.h>
 #include <net/net_stats.h>
@@ -2423,9 +2424,17 @@
 
 #define TWO_HOURS (2 * 60 * 60)
 
-static inline u32_t remaining(struct k_delayed_work *work)
+static inline u32_t remaining_lifetime(struct net_if_addr *ifaddr)
 {
-	return k_delayed_work_remaining_get(work) / K_SECONDS(1);
+	s32_t remaining;
+
+	if (ifaddr->lifetime_timer_timeout == 0) {
+		return 0;
+	}
+
+	remaining = k_uptime_get() - ifaddr->lifetime_timer_start;
+
+	return abs(remaining) / K_MSEC(1000);
 }
 
 static inline void handle_prefix_autonomous(struct net_pkt *pkt,
@@ -2453,7 +2462,7 @@
 		/* RFC 4862 ch 5.5.3 */
 		if ((prefix_info->valid_lifetime > TWO_HOURS) ||
 		    (prefix_info->valid_lifetime >
-		     remaining(&ifaddr->lifetime))) {
+		     remaining_lifetime(ifaddr))) {
 			NET_DBG("Timer updating for address %s "
 				"long lifetime %u secs",
 				net_sprint_ipv6_addr(&addr),
diff --git a/subsys/net/ip/net_if.c b/subsys/net/ip/net_if.c
index 095f236..55735f8 100644
--- a/subsys/net/ip/net_if.c
+++ b/subsys/net/ip/net_if.c
@@ -12,6 +12,7 @@
 #include <init.h>
 #include <kernel.h>
 #include <linker/sections.h>
+#include <stdlib.h>
 #include <string.h>
 #include <net/net_core.h>
 #include <net/net_pkt.h>
@@ -46,6 +47,12 @@
 static struct net_if_router routers[CONFIG_NET_MAX_ROUTERS];
 
 #if defined(CONFIG_NET_IPV6)
+/* Timer that triggers network address renewal */
+static struct k_delayed_work address_lifetime_timer;
+
+/* Track currently active address lifetime timers */
+static sys_slist_t active_address_lifetime_timers;
+
 static struct {
 	struct net_if_ipv6 ipv6;
 	struct net_if *iface;
@@ -748,16 +755,96 @@
 	return NULL;
 }
 
-static void ipv6_addr_expired(struct k_work *work)
+static void address_expired(struct net_if_addr *ifaddr)
 {
-	struct net_if_addr *ifaddr = CONTAINER_OF(work,
-						  struct net_if_addr,
-						  lifetime);
-
 	NET_DBG("IPv6 address %s is deprecated",
 		net_sprint_ipv6_addr(&ifaddr->address.in6_addr));
 
 	ifaddr->addr_state = NET_ADDR_DEPRECATED;
+	ifaddr->lifetime_timer_timeout = 0;
+
+	sys_slist_find_and_remove(&active_address_lifetime_timers,
+				  &ifaddr->node);
+}
+
+static bool address_check_timeout(s64_t start, u32_t time, s64_t timeout)
+{
+	start += time;
+	start = abs(start);
+
+	if (start > timeout) {
+		return false;
+	}
+
+	return true;
+}
+
+static bool address_timedout(struct net_if_addr *ifaddr, s64_t timeout)
+{
+	return address_check_timeout(ifaddr->lifetime_timer_start,
+				     ifaddr->lifetime_timer_timeout,
+				     timeout);
+}
+
+static u32_t address_manage_timeouts(struct net_if_addr *ifaddr, s64_t timeout)
+{
+	s32_t next_timeout;
+
+	if (address_timedout(ifaddr, timeout)) {
+		address_expired(ifaddr);
+		return UINT32_MAX;
+	}
+
+	next_timeout = timeout - (ifaddr->lifetime_timer_start +
+				  ifaddr->lifetime_timer_timeout);
+	return abs(next_timeout);
+}
+
+static void address_lifetime_timeout(struct k_work *work)
+{
+	u32_t timeout_update = UINT32_MAX - 1;
+	s64_t timeout = k_uptime_get();
+	struct net_if_addr *current, *next;
+
+	ARG_UNUSED(work);
+
+	SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&active_address_lifetime_timers,
+					  current, next, node) {
+		u32_t next_timeout;
+
+		next_timeout = address_manage_timeouts(current, timeout);
+		if (next_timeout < timeout_update) {
+			timeout_update = next_timeout;
+		}
+	}
+
+	if (timeout_update < (UINT32_MAX - 1)) {
+		NET_DBG("Waiting for %u ms", timeout_update);
+
+		k_delayed_work_submit(&address_lifetime_timer, timeout_update);
+	}
+}
+
+static void address_submit_work(u32_t timeout)
+{
+	if (!k_delayed_work_remaining_get(&address_lifetime_timer) ||
+	    timeout < k_delayed_work_remaining_get(&address_lifetime_timer)) {
+		k_delayed_work_cancel(&address_lifetime_timer);
+		k_delayed_work_submit(&address_lifetime_timer, timeout);
+
+		NET_DBG("Next wakeup in %d ms",
+			k_delayed_work_remaining_get(&address_lifetime_timer));
+	}
+}
+
+static void address_start_timer(struct net_if_addr *ifaddr, u32_t vlifetime)
+{
+	sys_slist_append(&active_address_lifetime_timers, &ifaddr->node);
+
+	ifaddr->lifetime_timer_start = k_uptime_get();
+	ifaddr->lifetime_timer_timeout = K_SECONDS(vlifetime);
+
+	address_submit_work(ifaddr->lifetime_timer_timeout);
 }
 
 void net_if_ipv6_addr_update_lifetime(struct net_if_addr *ifaddr,
@@ -767,7 +854,7 @@
 		net_sprint_ipv6_addr(&ifaddr->address.in6_addr),
 		vlifetime);
 
-	k_delayed_work_submit(&ifaddr->lifetime, K_SECONDS(vlifetime));
+	address_start_timer(ifaddr, vlifetime);
 }
 
 static struct net_if_addr *ipv6_addr_find(struct net_if *iface,
@@ -810,8 +897,6 @@
 	if (vlifetime) {
 		ifaddr->is_infinite = false;
 
-		k_delayed_work_init(&ifaddr->lifetime, ipv6_addr_expired);
-
 		NET_DBG("Expiring %s in %u secs", net_sprint_ipv6_addr(addr),
 			vlifetime);
 
@@ -948,7 +1033,14 @@
 		}
 
 		if (!ipv6->unicast[i].is_infinite) {
-			k_delayed_work_cancel(&ipv6->unicast[i].lifetime);
+			sys_slist_find_and_remove(
+				&active_address_lifetime_timers,
+				&ipv6->unicast[i].node);
+
+			if (sys_slist_is_empty(
+				    &active_address_lifetime_timers)) {
+				k_delayed_work_cancel(&address_lifetime_timer);
+			}
 		}
 
 		ipv6->unicast[i].is_used = false;
@@ -2591,6 +2683,8 @@
 #endif
 
 #if defined(CONFIG_NET_IPV6)
+	k_delayed_work_init(&address_lifetime_timer, address_lifetime_timeout);
+
 	if (if_count > ARRAY_SIZE(ipv6_addresses)) {
 		NET_WARN("You have %lu IPv6 net_if addresses but %d "
 			 "network interfaces", ARRAY_SIZE(ipv6_addresses),