/****************************************************************************
 * net/procfs/netdev_statistics.c
 *
 *   Copyright (C) 2015 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

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

#include <nuttx/config.h>

#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <debug.h>

#include <netinet/ether.h>

#include <nuttx/net/netdev.h>

#include "netdev/netdev.h"
#include "utils/utils.h"
#include "procfs/procfs.h"

#if !defined(CONFIG_DISABLE_MOUNTPOINT) && defined(CONFIG_FS_PROCFS) && \
    !defined(CONFIG_FS_PROCFS_EXCLUDE_NET)

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

static int netprocfs_linklayer(FAR struct netprocfs_file_s *netfile);
static int netprocfs_ipaddresses(FAR struct netprocfs_file_s *netfile);

#ifdef CONFIG_NETDEV_STATISTICS
static int netprocfs_rxstatistics_header(FAR struct netprocfs_file_s *netfile);
static int netprocfs_rxstatistics(FAR struct netprocfs_file_s *netfile);
static int netprocfs_rxpackets_header(FAR struct netprocfs_file_s *netfile);
static int netprocfs_rxpackets(FAR struct netprocfs_file_s *netfile);
static int netprocfs_txstatistics_header(FAR struct netprocfs_file_s *netfile);
static int netprocfs_txstatistics(FAR struct netprocfs_file_s *netfile);
static int netprocfs_errors(FAR struct netprocfs_file_s *netfile);
#endif /* CONFIG_NETDEV_STATISTICS */

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

/* Line generating functions */

static const linegen_t g_linegen[] =
{
  netprocfs_linklayer,
  netprocfs_ipaddresses
#ifdef CONFIG_NETDEV_STATISTICS
  , netprocfs_rxstatistics_header,
  netprocfs_rxstatistics,
  netprocfs_rxpackets_header,
  netprocfs_rxpackets,
  netprocfs_txstatistics_header,
  netprocfs_txstatistics,
  netprocfs_errors
#endif /* CONFIG_NETDEV_STATISTICS */
};

#define NSTAT_LINES (sizeof(g_linegen) / sizeof(linegen_t))

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: netprocfs_linklayer
 ****************************************************************************/

static int netprocfs_linklayer(FAR struct netprocfs_file_s *netfile)
{
  FAR struct net_driver_s *dev;
  FAR const char *status;
  int len = 0;

  DEBUGASSERT(netfile != NULL && netfile->dev != NULL);
  dev = netfile->dev;

  /* Get the interface status:  RUNNING, UP, or DOWN */

  if ((dev->d_flags & IFF_RUNNING) != 0)
    {
      status = "RUNNING";
    }
  else if ((dev->d_flags & IFF_UP) != 0)
    {
      status = "UP";
    }
  else
    {
      status = "DOWN";
    }

#if defined(CONFIG_NET_MULTILINK)
  /* If there are multiple link types being supported, then selected the
   * output appropriate for the link type associated with this device.
   */

  switch (dev->d_lltype)
    {
#ifdef CONFIG_NET_ETHERNET
      case NET_LL_ETHERNET:
        len += snprintf(&netfile->line[len], NET_LINELEN - len,
                        "%s\tLink encap:Ethernet HWaddr %s",
                        dev->d_ifname, ether_ntoa(&dev->d_mac));
        break;
#endif

#ifdef CONFIG_NET_LOOPBACK
      case NET_LL_LOOPBACK:
        len += snprintf(&netfile->line[len], NET_LINELEN - len,
                        "%s\tLink encap:Local Loopback",
                        dev->d_ifname);
        break;
#endif

#ifdef CONFIG_NET_SLIP
      case NET_LL_SLIP:
        len += snprintf(&netfile->line[len], NET_LINELEN - len,
                        "%s\tLink encap:SLIP", dev->d_ifname);
        break;
#endif

#ifdef CONFIG_NET_PPP
      case NET_LL_PPP:
        len += snprintf(&netfile->line[len], NET_LINELEN - len,
                        "%s\tLink encap:P-t-P", dev->d_ifname);
        break;
#endif

#ifdef CONFIG_NET_TUN
      case NET_LL_TUN:
        len += snprintf(&netfile->line[len], NET_LINELEN - len,
                        "%s\tLink encap:TUN", dev->d_ifname);
        break;
#endif

      default:
        len += snprintf(&netfile->line[len], NET_LINELEN - len,
                        "%s\tLink encap:UNSPEC", dev->d_ifname);
    }

  len += snprintf(&netfile->line[len], NET_LINELEN - len,
                  " at %s\n", status);

#elif defined(CONFIG_NET_ETHERNET)
  len += snprintf(&netfile->line[len], NET_LINELEN - len,
                  "%s\tLink encap:Ethernet HWaddr %s at %s\n",
                  dev->d_ifname, ether_ntoa(&dev->d_mac), status);

#elif defined(CONFIG_NET_LOOPBACK)
  len += snprintf(&netfile->line[len], NET_LINELEN - len,
                  "%s\tLink encap:Local Loopback at %s\n",
                  dev->d_ifname, status);

#elif defined(CONFIG_NET_SLIP)
  len += snprintf(&netfile->line[len], NET_LINELEN - len,
                  "%s\tLink encap:SLIP at %s\n",
                  dev->d_ifname, status);

#elif defined(CONFIG_NET_PPP)
  len += snprintf(&netfile->line[len], NET_LINELEN - len,
                  "%s\tLink encap:P-t-P at %s\n",
                  dev->d_ifname, status);

#elif defined(CONFIG_NET_TUN)
  len += snprintf(&netfile->line[len], NET_LINELEN - len,
                  "%s\tLink encap:TUN at %s\n",
                  dev->d_ifname, status);
#endif

  return len;
}

/****************************************************************************
 * Name: netprocfs_ipaddresses
 ****************************************************************************/

static int netprocfs_ipaddresses(FAR struct netprocfs_file_s *netfile)
{
  FAR struct net_driver_s *dev;
#ifdef CONFIG_NET_IPv4
  struct in_addr addr;
#endif
#ifdef CONFIG_NET_IPv6
  char addrstr[INET6_ADDRSTRLEN];
  uint8_t preflen;
#endif
  int len = 0;

  DEBUGASSERT(netfile != NULL && netfile->dev != NULL);
  dev = netfile->dev;

#ifdef CONFIG_NET_IPv4
  /* Show the IPv4 address */

  addr.s_addr = dev->d_ipaddr;
  len += snprintf(&netfile->line[len], NET_LINELEN - len,
                  "\tinet addr:%s ", inet_ntoa(addr));

  /* Show the IPv4 default router address */

  addr.s_addr = dev->d_draddr;
  len += snprintf(&netfile->line[len], NET_LINELEN - len,
                  "DRaddr:%s ", inet_ntoa(addr));

  /* Show the IPv4 network mask */

  addr.s_addr = dev->d_netmask;
  len += snprintf(&netfile->line[len], NET_LINELEN - len,
                  "Mask:%s\n", inet_ntoa(addr));
#endif

#ifdef CONFIG_NET_IPv6
  /* Convert the 128 network mask to a human friendly prefix length */

  preflen = net_ipv6_mask2pref(dev->d_ipv6netmask);

  /* Show the assigned IPv6 address */

  if (inet_ntop(AF_INET6, dev->d_ipv6addr, addrstr, INET6_ADDRSTRLEN))
    {
      len += snprintf(&netfile->line[len], NET_LINELEN - len,
                      "\tinet6 addr:%s/%d\n", addrstr, preflen);
    }

  /* REVISIT: Show the IPv6 default router address */

  if (inet_ntop(AF_INET6, dev->d_ipv6draddr, addrstr, INET6_ADDRSTRLEN))
    {
      len += snprintf(&netfile->line[len], NET_LINELEN - len,
                      "\tinet6 DRaddr:%s/%d\n", addrstr, preflen);
    }
#endif

  len += snprintf(&netfile->line[len], NET_LINELEN - len, "\n");
  return len;
}

/****************************************************************************
 * Name: netprocfs_rxstatistics_header
 ****************************************************************************/

#ifdef CONFIG_NETDEV_STATISTICS
static int netprocfs_rxstatistics_header(FAR struct netprocfs_file_s *netfile)
{
  DEBUGASSERT(netfile != NULL);
  return snprintf(netfile->line, NET_LINELEN , "\tRX: %-8s %-8s %-8s\n",
                  "Received", "Fragment", "Errors");
}
#endif /* CONFIG_NETDEV_STATISTICS */

/****************************************************************************
 * Name: netprocfs_rxstatistics
 ****************************************************************************/

#ifdef CONFIG_NETDEV_STATISTICS
static int netprocfs_rxstatistics(FAR struct netprocfs_file_s *netfile)
{
  FAR struct netdev_statistics_s *stats;
  FAR struct net_driver_s *dev;

  DEBUGASSERT(netfile != NULL && netfile->dev != NULL);
  dev = netfile->dev;
  stats = &dev->d_statistics;

  return snprintf(netfile->line, NET_LINELEN, "\t    %08lx %08lx %08lx\n",
                  (unsigned long)stats->rx_packets,
                  (unsigned long)stats->rx_fragments,
                  (unsigned long)stats->rx_errors);
}
#endif /* CONFIG_NETDEV_STATISTICS */

/****************************************************************************
 * Name: netprocfs_rxpackets_header
 ****************************************************************************/

#ifdef CONFIG_NETDEV_STATISTICS
static int netprocfs_rxpackets_header(FAR struct netprocfs_file_s *netfile)
{
  FAR char *fmt;

  DEBUGASSERT(netfile != NULL);

  fmt = "\t    "
#ifdef CONFIG_NET_IPv4
        "%-8s "
#endif
#ifdef CONFIG_NET_IPv6
        "%-8s "
#endif
#ifdef CONFIG_NET_ARP
        "%-8s "
#endif
        "%-8s\n";

  return snprintf(netfile->line, NET_LINELEN, fmt
#ifdef CONFIG_NET_IPv4
        , "IPv4"
#endif
#ifdef CONFIG_NET_IPv6
        , "IPv6"
#endif
#ifdef CONFIG_NET_ARP
        , "ARP"
#endif
        , "Dropped");
}
#endif /* CONFIG_NETDEV_STATISTICS */

/****************************************************************************
 * Name: netprocfs_rxpackets
 ****************************************************************************/

#ifdef CONFIG_NETDEV_STATISTICS
static int netprocfs_rxpackets(FAR struct netprocfs_file_s *netfile)
{
  FAR struct netdev_statistics_s *stats;
  FAR struct net_driver_s *dev;
  FAR char *fmt;

  DEBUGASSERT(netfile != NULL && netfile->dev != NULL);
  dev = netfile->dev;
  stats = &dev->d_statistics;

  fmt = "\t    "
#ifdef CONFIG_NET_IPv4
        "%08lx "
#endif
#ifdef CONFIG_NET_IPv6
        "%08lx "
#endif
#ifdef CONFIG_NET_ARP
        "%08lx "
#endif
        "%08lx\n";

  return snprintf(netfile->line, NET_LINELEN, fmt
#ifdef CONFIG_NET_IPv4
        , (unsigned long)stats->rx_ipv4
#endif
#ifdef CONFIG_NET_IPv6
        , (unsigned long)stats->rx_ipv6
#endif
#ifdef CONFIG_NET_ARP
        , (unsigned long)stats->rx_arp
#endif
        , (unsigned long)stats->rx_dropped);
}
#endif /* CONFIG_NETDEV_STATISTICS */

/****************************************************************************
 * Name: netprocfs_txstatistics_header
 ****************************************************************************/

#ifdef CONFIG_NETDEV_STATISTICS
static int netprocfs_txstatistics_header(FAR struct netprocfs_file_s *netfile)
{
  DEBUGASSERT(netfile != NULL);

  return snprintf(netfile->line, NET_LINELEN, "\tTX: %-8s %-8s %-8s %-8s\n",
                 "Queued", "Sent", "Erorts", "Timeouts");
}
#endif /* CONFIG_NETDEV_STATISTICS */

/****************************************************************************
 * Name: netprocfs_txstatistics
 ****************************************************************************/

#ifdef CONFIG_NETDEV_STATISTICS
static int netprocfs_txstatistics(FAR struct netprocfs_file_s *netfile)
{
  FAR struct netdev_statistics_s *stats;
  FAR struct net_driver_s *dev;

  DEBUGASSERT(netfile != NULL && netfile->dev != NULL);
  dev = netfile->dev;
  stats = &dev->d_statistics;

  return snprintf(netfile->line, NET_LINELEN, "\t    %08lx %08lx %08lx %08lx\n",
                  (unsigned long)stats->tx_packets,
                  (unsigned long)stats->tx_done,
                  (unsigned long)stats->tx_errors,
                  (unsigned long)stats->tx_timeouts);
}
#endif /* CONFIG_NETDEV_STATISTICS */

/****************************************************************************
 * Name: netprocfs_errors
 ****************************************************************************/

#ifdef CONFIG_NETDEV_STATISTICS
static int netprocfs_errors(FAR struct netprocfs_file_s *netfile)
{
  FAR struct netdev_statistics_s *stats;
  FAR struct net_driver_s *dev;

  DEBUGASSERT(netfile != NULL && netfile->dev != NULL);
  dev = netfile->dev;
  stats = &dev->d_statistics;

  return snprintf(netfile->line, NET_LINELEN , "\tTotal Errors: %08x\n\n",
                  (unsigned long)stats->errors);
}
#endif /* CONFIG_NETDEV_STATISTICS */

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

/****************************************************************************
 * Name: netprocfs_read_devstats
 *
 * Description:
 *   Read and format network device statistics.
 *
 * Input Parameters:
 *   priv - A reference to the network procfs file structure
 *   buffer - The user-provided buffer into which device status will be
 *            returned.
 *   bulen  - The size in bytes of the user provided buffer.
 *
 * Returned Value:
 *   Zero (OK) is returned on success; a negated errno value is returned
 *   on failure.
 *
 ****************************************************************************/

ssize_t netprocfs_read_devstats(FAR struct netprocfs_file_s *priv,
                                FAR char *buffer, size_t buflen)
{
  return netprocfs_read_linegen(priv, buffer, buflen, g_linegen, NSTAT_LINES);
}

#endif /* !CONFIG_DISABLE_MOUNTPOINT && CONFIG_FS_PROCFS &&
        * !CONFIG_FS_PROCFS_EXCLUDE_NET */
