/****************************************************************************
 * net/route/net_cacheroute.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/mutex.h>

#include "route/cacheroute.h"
#include "route/route.h"

#if defined(CONFIG_ROUTE_IPv4_CACHEROUTE) || defined(CONFIG_ROUTE_IPv6_CACHEROUTE)

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* Configuration ************************************************************/

#ifndef CONFIG_ROUTE_MAX_IPv4_CACHEROUTES
#  define CONFIG_ROUTE_MAX_IPv4_CACHEROUTES 4
#endif

#ifndef CONFIG_ROUTE_MAX_IPv6_CACHEROUTES
#  define CONFIG_ROUTE_MAX_IPv6_CACHEROUTES 4
#endif

/* Routing table initializer */

#define cacheroute_init(rr) \
  do \
    { \
      (rr)->head = NULL; \
      (rr)->tail = NULL; \
    } \
  while (0)

/****************************************************************************
 * Private Types
 ****************************************************************************/

#ifdef CONFIG_ROUTE_IPv4_CACHEROUTE
/* This structure describes one entry in the routing table cache */

struct net_cache_ipv4_entry_s
{
  struct net_route_ipv4_s entry;
  FAR struct net_cache_ipv4_entry_s *flink;
};

/* This structure describes the head of a routing table cache list */

struct net_cache_ipv4_queue_s
{
  FAR struct net_cache_ipv4_entry_s *head;
  FAR struct net_cache_ipv4_entry_s *tail;
};
#endif

#ifdef CONFIG_ROUTE_IPv6_CACHEROUTE
/* This structure describes one entry in the routing table cache */

struct net_cache_ipv6_entry_s
{
  struct net_route_ipv6_s entry;
  FAR struct net_cache_ipv6_entry_s *flink;
};

/* This structure describes the head of a routing table cache list */

struct net_cache_ipv6_queue_s
{
  FAR struct net_cache_ipv6_entry_s *head;
  FAR struct net_cache_ipv6_entry_s *tail;
};
#endif

/****************************************************************************
 * Private Data
 ****************************************************************************/

/* These are the routing tables */

#if defined(CONFIG_ROUTE_IPv4_CACHEROUTE)
/* The in-memory cache as a singly linked list. */

static struct net_cache_ipv4_queue_s g_ipv4_cache;

/* List of free routing table cache entries */

static struct net_cache_ipv4_queue_s g_free_ipv4cache;

/* Pre-allocated routing table cache entries */

static struct net_cache_ipv4_entry_s
  g_prealloc_ipv4cache[CONFIG_ROUTE_MAX_IPv4_CACHEROUTES];

/* Serializes access to the routing table cache */

static mutex_t g_ipv4_cachelock = NXMUTEX_INITIALIZER;
#endif

#if defined(CONFIG_ROUTE_IPv6_CACHEROUTE)
/* The in-memory routing tables are represented as singly linked lists. */

static struct net_cache_ipv6_queue_s g_ipv6_cache;

/* List of free routing table cache entries */

static struct net_cache_ipv6_queue_s g_free_ipv6cache;

/* Pre-allocated routing table cache entries */

static struct net_cache_ipv6_entry_s
  g_prealloc_ipv6cache[CONFIG_ROUTE_MAX_IPv6_CACHEROUTES];

/* Serializes access to the routing table cache */

static mutex_t g_ipv6_cachelock = NXMUTEX_INITIALIZER;
#endif

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: net_add_newest_ipv4 and net_add_newest_ipv6
 *
 * Description:
 *   Add a new entry to the routing table cache list.  The list is ordered
 *   by "new-ness" so this would be the entry at the head of the list.
 *
 * Input Parameters:
 *   cache - The cache entry to add to the head of the routing table cache
 *           list.
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *   Caller has the routing table cache locked.
 *
 ****************************************************************************/

#ifdef CONFIG_ROUTE_IPv4_CACHEROUTE
static void net_add_newest_ipv4(FAR struct net_cache_ipv4_entry_s *cache)
{
  cache->flink = g_ipv4_cache.head;
  if (!g_ipv4_cache.head)
    {
      g_ipv4_cache.tail = cache;
    }

  g_ipv4_cache.head = cache;
}
#endif

#ifdef CONFIG_ROUTE_IPv6_CACHEROUTE
static void net_add_newest_ipv6(FAR struct net_cache_ipv6_entry_s *cache)
{
  cache->flink = g_ipv6_cache.head;
  if (!g_ipv6_cache.head)
    {
      g_ipv6_cache.tail = cache;
    }

  g_ipv6_cache.head = cache;
}
#endif

/****************************************************************************
 * Name: net_remove_oldest_ipv4 and net_remove_oldest_ipv6
 *
 * Description:
 *   Remove the oldest entry from the routing table cache list.  The list is
 *   ordered
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   On success, a pointer to the oldest routing table cache entry is
 *   returned.  NULL would be returned if the routing table cache is empty.
 *
 * Assumptions:
 *   Caller has the routing table cache locked.
 *
 ****************************************************************************/

#ifdef CONFIG_ROUTE_IPv4_CACHEROUTE
FAR struct net_cache_ipv4_entry_s *net_remove_oldest_ipv4(void)
{
  FAR struct net_cache_ipv4_entry_s *cache;
  FAR struct net_cache_ipv4_entry_s *prev;

  cache = g_ipv4_cache.tail;
  if (cache != NULL)
    {
      if (g_ipv4_cache.head == g_ipv4_cache.tail)
        {
          g_ipv4_cache.head = NULL;
          g_ipv4_cache.tail = NULL;
        }
      else
        {
          for (prev  = g_ipv4_cache.head;
               prev != NULL && prev->flink != cache;
               prev  = prev->flink);

          if (prev != NULL)
            {
              prev->flink       = NULL;
              g_ipv4_cache.tail = prev;
            }
        }

      cache->flink = NULL;
    }

  return cache;
}
#endif

#ifdef CONFIG_ROUTE_IPv6_CACHEROUTE
FAR struct net_cache_ipv6_entry_s *net_remove_oldest_ipv6(void)
{
  FAR struct net_cache_ipv6_entry_s *cache;
  FAR struct net_cache_ipv6_entry_s *prev;

  cache = g_ipv6_cache.tail;
  if (cache != NULL)
    {
      if (g_ipv6_cache.head == g_ipv6_cache.tail)
        {
          g_ipv6_cache.head = NULL;
          g_ipv6_cache.tail = NULL;
        }
      else
        {
          for (prev  = g_ipv6_cache.head;
               prev != NULL && prev->flink != cache;
               prev  = prev->flink);

          if (prev != NULL)
            {
              prev->flink       = NULL;
              g_ipv6_cache.tail = prev;
            }
        }

      cache->flink = NULL;
    }

  return cache;
}
#endif

/****************************************************************************
 * Name: net_alloccache_ipv4 and net_alloccache_ipv6
 *
 * Description:
 *   Allocate one routing table cache entry by removing it from the free
 *   list.  If the free list is empty, then remove the entry at the tail
 *   of the current routing table cache.
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   On success, a pointer to the newly allocated routing table cache entry
 *   is returned.  Should never fail
 *
 * Assumptions:
 *   Caller has the routing table cache locked.
 *
 ****************************************************************************/

#ifdef CONFIG_ROUTE_IPv4_CACHEROUTE
FAR struct net_cache_ipv4_entry_s *net_alloccache_ipv4(void)
{
  FAR struct net_cache_ipv4_entry_s *cache;

  /* Remove the first entry from the free list */

  cache = g_free_ipv4cache.head;
  if (cache != NULL)
    {
      g_free_ipv4cache.head = cache->flink;
      if (g_free_ipv4cache.head == NULL)
        {
          g_free_ipv4cache.tail = NULL;
        }

      cache->flink = NULL;
    }

  /* If the free list is empty, then remove the oldest entry at the tail of
   * the routing table cache.
   */

  else
    {
      /* If the free list is empty, then the cache list cannot be empty */

      cache = net_remove_oldest_ipv4();
      DEBUGASSERT(cache != NULL);
    }

  return cache;
}
#endif

#ifdef CONFIG_ROUTE_IPv6_CACHEROUTE
FAR struct net_cache_ipv6_entry_s *net_alloccache_ipv6(void)
{
  FAR struct net_cache_ipv6_entry_s *cache;

  /* Remove the first entry from the free list */

  cache = g_free_ipv6cache.head;
  if (cache != NULL)
    {
      g_free_ipv6cache.head = cache->flink;
      if (g_free_ipv6cache.head == NULL)
        {
          g_free_ipv6cache.tail = NULL;
        }

      cache->flink = NULL;
    }

  /* If the free list is empty, then remove the oldest entry at the tail of
   * the routing table cache.
   */

  else
    {
      /* If the free list is empty, then the cache list cannot be empty */

      cache = net_remove_oldest_ipv6();
      DEBUGASSERT(cache != NULL);
    }

  return cache;
}
#endif

/****************************************************************************
 * Name: net_reset_ipv4_cache and net_reset_ipv6_cache
 *
 * Description:
 *   Clear the routing table cache and return the entries to the free list.
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef CONFIG_ROUTE_IPv4_CACHEROUTE
static void net_reset_ipv4_cache(void)
{
  int i;

  cacheroute_init(&g_ipv4_cache);
  cacheroute_init(&g_free_ipv4cache);

  /* Add all of the pre-allocated routing table cache entries to
   * a free list
   */

  for (i = 0; i < CONFIG_ROUTE_MAX_IPv4_CACHEROUTES; i++)
    {
      net_add_newest_ipv4(&g_prealloc_ipv4cache[i]);
    }
}
#endif

#ifdef CONFIG_ROUTE_IPv6_CACHEROUTE
static void net_reset_ipv6_cache(void)
{
  int i;

  cacheroute_init(&g_ipv6_cache);
  cacheroute_init(&g_free_ipv6cache);

  /* Add all of the pre-allocated routing table entries to a free list */

  for (i = 0; i < CONFIG_ROUTE_MAX_IPv6_CACHEROUTES; i++)
    {
      net_add_newest_ipv6(&g_prealloc_ipv6cache[i]);
    }
}
#endif

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: net_init_cacheroute
 *
 * Description:
 *   Initialize the in-memory, routing table cache
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *   Called early in initialization so that no special protection is needed.
 *
 ****************************************************************************/

void net_init_cacheroute(void)
{
  /* Initialize the routing table cash and the free list */

#ifdef CONFIG_ROUTE_IPv4_CACHEROUTE
  net_reset_ipv4_cache();
#endif

#ifdef CONFIG_ROUTE_IPv6_CACHEROUTE
  net_reset_ipv6_cache();
#endif
}

/****************************************************************************
 * Name: net_addcache_ipv4 and net_addcache_ipv6
 *
 * Description:
 *   Add one route to the routing table cache
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   Zero (OK) is returned on success; a negated errno value is returned
 *   on any failure.
 *
 ****************************************************************************/

#ifdef CONFIG_ROUTE_IPv4_CACHEROUTE
int net_addcache_ipv4(FAR struct net_route_ipv4_s *route)
{
  FAR struct net_cache_ipv4_entry_s *cache;
  FAR struct net_cache_ipv4_entry_s *prev;
  int ret;

  DEBUGASSERT(route != NULL);

  /* Get exclusive access to the cache */

  ret = nxmutex_lock(&g_ipv4_cachelock);
  if (ret < 0)
    {
      return ret;
    }

  /* First, check if the route already exists in the cache.
   *
   * Visit each entry in the cache
   */

  for (prev = NULL, cache = g_ipv4_cache.head;
       cache != NULL;
       prev = cache, cache = cache->flink)
    {
      /* To match, the masked target addresses and netmasks must be the
       * same.
       */

      if (net_ipv4addr_cmp(route->target, cache->entry.target) &&
          net_ipv4addr_cmp(route->netmask, cache->entry.netmask))
        {
          /* If the route already exists and is already at the head of the
           * list, then do nothing.  It is already the most recently used.
           */

          if (prev == NULL)
            {
              nxmutex_unlock(&g_ipv4_cachelock);
              return OK;
            }

          /* Otherwise, remove the cache entry from the middle or end of
           * the list.
           */

          prev->flink = cache->flink;
          if (g_ipv4_cache.tail == cache)
            {
              g_ipv4_cache.tail = prev;
            }

          cache->flink = NULL;
          break;
        }
    }

  /* If we did not find the entry in the list, then we will have to
   * allocate a new entry.
   */

  if (cache == NULL)
    {
      /* Allocate a new cache entry (should never fail) */

      cache = net_alloccache_ipv4();
      DEBUGASSERT(cache != NULL);

      /* Copy the routing table entry into the allocated cache entry. */

      memcpy(&cache->entry, route, sizeof(struct net_route_ipv4_s));
    }

  /* Then add the new cache entry as the newest entry in the table */

  net_add_newest_ipv4(cache);
  nxmutex_unlock(&g_ipv4_cachelock);
  return OK;
}
#endif

#ifdef CONFIG_ROUTE_IPv6_CACHEROUTE
int net_addcache_ipv6(FAR struct net_route_ipv6_s *route)
{
  FAR struct net_cache_ipv6_entry_s *cache;
  FAR struct net_cache_ipv6_entry_s *prev;
  int ret;

  DEBUGASSERT(route != NULL);

  /* Get exclusive access to the cache */

  ret = nxmutex_lock(&g_ipv6_cachelock);
  if (ret < 0)
    {
      return ret;
    }

  /* First, check if the route already exists in the cache.
   *
   * Visit each entry in the cache.
   */

  for (prev = NULL, cache = g_ipv6_cache.head;
       cache != NULL;
       prev = cache, cache = cache->flink)
    {
      /* To match, the masked target addresses and netmasks must be the
       * same.
       */

      if (net_ipv6addr_cmp(route->target, cache->entry.target) &&
          net_ipv6addr_cmp(route->netmask, cache->entry.netmask))
        {
          /* If the route already exists and is already at the head of the
           * list, then do nothing.  It is already the most recently used.
           */

          if (prev == NULL)
            {
              nxmutex_unlock(&g_ipv6_cachelock);
              return OK;
            }

          /* Otherwise, remove the cache entry from the middle or end of
           * the list.
           */

          prev->flink = cache->flink;
          if (g_ipv6_cache.tail == cache)
            {
              g_ipv6_cache.tail = prev;
            }

          cache->flink = NULL;
          break;
        }
    }

  /* If we did not find the entry in the list, then we will have to
   * allocate a new entry.
   */

  if (cache == NULL)
    {
      /* Allocate a new cache entry (should never fail) */

      cache = net_alloccache_ipv6();
      DEBUGASSERT(cache != NULL);

      /* Copy the routing table entry into the allocated cache entry */

      memcpy(&cache->entry, route, sizeof(struct net_route_ipv6_s));
    }

  /* Then add the new cache entry as the newest entry in the table */

  net_add_newest_ipv6(cache);
  nxmutex_unlock(&g_ipv6_cachelock);
  return OK;
}
#endif

/****************************************************************************
 * Name: net_foreachcache_ipv4/net_foreachcache_ipv6
 *
 * Description:
 *   Traverse the routing table cache
 *
 * Input Parameters:
 *   handler - Will be called for each route in the routing table cache.
 *   arg     - An arbitrary value that will be passed to the handler.
 *
 * Returned Value:
 *   Zero (OK) returned if the entire table was searched.  A negated errno
 *   value will be returned in the event of a failure.  Handlers may also
 *   terminate the search early with any non-zero value.
 *
 ****************************************************************************/

#ifdef CONFIG_NET_IPv4
int net_foreachcache_ipv4(route_handler_ipv4_t handler, FAR void *arg)
{
  FAR struct net_cache_ipv4_entry_s *cache;
  FAR struct net_cache_ipv4_entry_s *next;
  int ret = 0;

  /* Get exclusive access to the cache */

  ret = nxmutex_lock(&g_ipv4_cachelock);
  if (ret < 0)
    {
      return ret;
    }

  /* Visit each entry in the routing table */

  for (cache = g_ipv4_cache.head; ret == 0 && cache != NULL; cache = next)
    {
      /* Get the next entry in the to visit.  We do this BEFORE calling the
       * handler because the handler may delete this entry.
       */

      next = cache->flink;
      ret  = handler(&cache->entry, arg);
    }

  /* Unlock the cache */

  nxmutex_unlock(&g_ipv4_cachelock);
  return ret;
}
#endif

#ifdef CONFIG_NET_IPv6
int net_foreachcache_ipv6(route_handler_ipv6_t handler, FAR void *arg)
{
  FAR struct net_cache_ipv6_entry_s *cache;
  FAR struct net_cache_ipv6_entry_s *next;
  int ret = 0;

  /* Get exclusive access to the cache */

  ret = nxmutex_lock(&g_ipv6_cachelock);
  if (ret < 0)
    {
      return ret;
    }

  /* Visit each entry in the routing table */

  for (cache = g_ipv6_cache.head; ret == 0 && cache != NULL; cache = next)
    {
      /* Get the next entry in the to visit.  We do this BEFORE calling the
       * handler because the handler may delete this entry.
       */

      next = cache->flink;
      ret  = handler(&cache->entry, arg);
    }

  /* Unlock the cache */

  nxmutex_unlock(&g_ipv6_cachelock);
  return ret;
}
#endif

/****************************************************************************
 * Name: net_flushcache_ipv4 and net_flushcache_ipv6
 *
 * Description:
 *   Flush the content of the routing table cache
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef CONFIG_ROUTE_IPv4_CACHEROUTE
void net_flushcache_ipv4(void)
{
  int ret;

  /* Get exclusive access to the cache */

  ret = nxmutex_lock(&g_ipv4_cachelock);
  if (ret >= 0)
    {
      /* Reset the cache */

      net_reset_ipv4_cache();

      /* Unlock the cache */

      nxmutex_unlock(&g_ipv4_cachelock);
    }
}
#endif

#ifdef CONFIG_ROUTE_IPv6_CACHEROUTE
void net_flushcache_ipv6(void)
{
  int ret;

  /* Get exclusive access to the cache */

  ret = nxmutex_lock(&g_ipv6_cachelock);
  if (ret >= 0)
    {
      /* Reset the cache */

      net_reset_ipv6_cache();

      /* Unlock the cache */

      nxmutex_unlock(&g_ipv6_cachelock);
    }
}
#endif

#endif /* CONFIG_ROUTE_IPv4_CACHEROUTE || CONFIG_ROUTE_IPv6_CACHEROUTE */