text: /*
   * Copyright (c) 2015, SICS Swedish ICT.
   * All rights reserved.
   *
   * 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 of the Institute 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 INSTITUTE 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 INSTITUTE 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.
   *
   *
   * Authors: Simon Duquennoy <simonduq@sics.se>
   */
  
  #include "contiki.h"
  #include "sys/clock.h"
  #include "net/packetbuf.h"
  #include "net/nbr-table.h"
  #include "net/link-stats.h"
  #include <stdio.h>
  
  /* Log configuration */
  #include "sys/log.h"
  #define LOG_MODULE "Link Stats"
  #define LOG_LEVEL LOG_LEVEL_MAC
  
  /* Maximum value for the Tx count counter */
  #define TX_COUNT_MAX                    32
  
  /* Statistics with no update in FRESHNESS_EXPIRATION_TIMEOUT is not fresh */
  #define FRESHNESS_EXPIRATION_TIME       (10 * 60 * (clock_time_t)CLOCK_SECOND)
  /* Half time for the freshness counter */
  #define FRESHNESS_HALF_LIFE             (15 * 60 * (clock_time_t)CLOCK_SECOND)
  /* Statistics are fresh if the freshness counter is FRESHNESS_TARGET or more */
  #define FRESHNESS_TARGET                 4
  /* Maximum value for the freshness counter */
  #define FRESHNESS_MAX                   16
  
  /* EWMA (exponential moving average) used to maintain statistics over time */
  #define EWMA_SCALE                     100
  #define EWMA_ALPHA                      10
  #define EWMA_BOOTSTRAP_ALPHA            25
  
  /* ETX fixed point divisor. 128 is the value used by RPL (RFC 6551 and RFC 6719) */
  #define ETX_DIVISOR                     LINK_STATS_ETX_DIVISOR
  /* In case of no-ACK, add ETX_NOACK_PENALTY to the real Tx count, as a penalty */
  #define ETX_NOACK_PENALTY               12
  /* Initial ETX value */
  #define ETX_DEFAULT                      2
  
  /* Per-neighbor link statistics table */
  NBR_TABLE(struct link_stats, link_stats);
  
  /* Called at a period of FRESHNESS_HALF_LIFE */
  struct ctimer periodic_timer;
  
  /*---------------------------------------------------------------------------*/
  /* Returns the neighbor's link stats */
  const struct link_stats *
  link_stats_from_lladdr(const linkaddr_t *lladdr)
  {
    return nbr_table_get_from_lladdr(link_stats, lladdr);
  }
  /*---------------------------------------------------------------------------*/
  /* Returns the neighbor's address given a link stats item */
  const linkaddr_t *
  link_stats_get_lladdr(const struct link_stats *stat)
  {
    return nbr_table_get_lladdr(link_stats, stat);
  }
  /*---------------------------------------------------------------------------*/
  /* Are the statistics fresh? */
  int
  link_stats_is_fresh(const struct link_stats *stats)
  {
    return (stats != NULL)
        && clock_time() - stats->last_tx_time < FRESHNESS_EXPIRATION_TIME
        && stats->freshness >= FRESHNESS_TARGET;
  }
  /*---------------------------------------------------------------------------*/
  #if LINK_STATS_INIT_ETX_FROM_RSSI
  uint16_t
  guess_etx_from_rssi(const struct link_stats *stats)
  {
    if(stats != NULL) {
      if(stats->rssi == 0) {
        return ETX_DEFAULT * ETX_DIVISOR;
      } else {
        /* A rough estimate of PRR from RSSI, as a linear function where:
         *      RSSI >= -60 results in PRR of 1
         *      RSSI <= -90 results in PRR of 0
         * prr = (bounded_rssi - RSSI_LOW) / (RSSI_DIFF)
         * etx = ETX_DIVOSOR / ((bounded_rssi - RSSI_LOW) / RSSI_DIFF)
         * etx = (RSSI_DIFF * ETX_DIVOSOR) / (bounded_rssi - RSSI_LOW)
         * */
  #define ETX_INIT_MAX 3
  #define RSSI_HIGH -60
  #define RSSI_LOW  -90
  #define RSSI_DIFF (RSSI_HIGH - RSSI_LOW)
        uint16_t etx;
        int16_t bounded_rssi = stats->rssi;
        bounded_rssi = MIN(bounded_rssi, RSSI_HIGH);
        bounded_rssi = MAX(bounded_rssi, RSSI_LOW + 1);
        etx = RSSI_DIFF * ETX_DIVISOR / (bounded_rssi - RSSI_LOW);
        return MIN(etx, ETX_INIT_MAX * ETX_DIVISOR);
      }
    }
    return 0xffff;
  }
  #endif /* LINK_STATS_INIT_ETX_FROM_RSSI */
  /*---------------------------------------------------------------------------*/
  /* Packet sent callback. Updates stats for transmissions to lladdr */
  void
  link_stats_packet_sent(const linkaddr_t *lladdr, int status, int numtx)
  {
    struct link_stats *stats;
  #if !LINK_STATS_ETX_FROM_PACKET_COUNT
    uint16_t packet_etx;
    uint8_t ewma_alpha;
  #endif /* !LINK_STATS_ETX_FROM_PACKET_COUNT */
  
    if(status != MAC_TX_OK && status != MAC_TX_NOACK) {
      /* Do not penalize the ETX when collisions or transmission errors occur. */
      return;
    }
  
    stats = nbr_table_get_from_lladdr(link_stats, lladdr);
    if(stats == NULL) {
      /* Add the neighbor */
      stats = nbr_table_add_lladdr(link_stats, lladdr, NBR_TABLE_REASON_LINK_STATS, NULL);
      if(stats != NULL) {
  #if LINK_STATS_INIT_ETX_FROM_RSSI
        stats->etx = guess_etx_from_rssi(stats);
  #else /* LINK_STATS_INIT_ETX_FROM_RSSI */
        stats->etx = ETX_DEFAULT * ETX_DIVISOR;
  #endif /* LINK_STATS_INIT_ETX_FROM_RSSI */
      } else {
        return; /* No space left, return */
      }
    }
  
    /* Update last timestamp and freshness */
    stats->last_tx_time = clock_time();
    stats->freshness = MIN(stats->freshness + numtx, FRESHNESS_MAX);
  
  #if LINK_STATS_PACKET_COUNTERS
    /* Update paket counters */
    stats->cnt_current.num_packets_tx += numtx;
    if(status == MAC_TX_OK) {
      stats->cnt_current.num_packets_acked++;
    }
  #endif
  
    /* Add penalty in case of no-ACK */
    if(status == MAC_TX_NOACK) {
      numtx += ETX_NOACK_PENALTY;
    }
  
  #if LINK_STATS_ETX_FROM_PACKET_COUNT
    /* Compute ETX from packet and ACK count */
    /* Halve both counter after TX_COUNT_MAX */
    if(stats->tx_count + numtx > TX_COUNT_MAX) {
      stats->tx_count /= 2;
      stats->ack_count /= 2;
    }
    /* Update tx_count and ack_count */
    stats->tx_count += numtx;
    if(status == MAC_TX_OK) {
      stats->ack_count++;
    }
    /* Compute ETX */
    if(stats->ack_count > 0) {
      stats->etx = ((uint16_t)stats->tx_count * ETX_DIVISOR) / stats->ack_count;
    } else {
      stats->etx = (uint16_t)MAX(ETX_NOACK_PENALTY, stats->tx_count) * ETX_DIVISOR;
    }
  #else /* LINK_STATS_ETX_FROM_PACKET_COUNT */
    /* Compute ETX using an EWMA */
  
    /* ETX used for this update */
    packet_etx = numtx * ETX_DIVISOR;
    /* ETX alpha used for this update */
    ewma_alpha = link_stats_is_fresh(stats) ? EWMA_ALPHA : EWMA_BOOTSTRAP_ALPHA;
  
    /* Compute EWMA and update ETX */
    stats->etx = ((uint32_t)stats->etx * (EWMA_SCALE - ewma_alpha) +
        (uint32_t)packet_etx * ewma_alpha) / EWMA_SCALE;
  #endif /* LINK_STATS_ETX_FROM_PACKET_COUNT */
  }
  /*---------------------------------------------------------------------------*/
  /* Packet input callback. Updates statistics for receptions on a given link */
  void
  link_stats_input_callback(const linkaddr_t *lladdr)
  {
    struct link_stats *stats;
    int16_t packet_rssi = packetbuf_attr(PACKETBUF_ATTR_RSSI);
  
    stats = nbr_table_get_from_lladdr(link_stats, lladdr);
    if(stats == NULL) {
      /* Add the neighbor */
      stats = nbr_table_add_lladdr(link_stats, lladdr, NBR_TABLE_REASON_LINK_STATS, NULL);
      if(stats != NULL) {
        /* Initialize */
        stats->rssi = packet_rssi;
  #if LINK_STATS_INIT_ETX_FROM_RSSI
        stats->etx = guess_etx_from_rssi(stats);
  #else /* LINK_STATS_INIT_ETX_FROM_RSSI */
        stats->etx = ETX_DEFAULT * ETX_DIVISOR;
  #endif /* LINK_STATS_INIT_ETX_FROM_RSSI */
  #if LINK_STATS_PACKET_COUNTERS
        stats->cnt_current.num_packets_rx = 1;
  #endif
      }
      return;
    }
  
    /* Update RSSI EWMA */
    stats->rssi = ((int32_t)stats->rssi * (EWMA_SCALE - EWMA_ALPHA) +
        (int32_t)packet_rssi * EWMA_ALPHA) / EWMA_SCALE;
  
  #if LINK_STATS_PACKET_COUNTERS
    stats->cnt_current.num_packets_rx++;
  #endif
  }
  /*---------------------------------------------------------------------------*/
  #if LINK_STATS_PACKET_COUNTERS
  /*---------------------------------------------------------------------------*/
  static void
  print_and_update_counters(void)
  {
    struct link_stats *stats;
  
    for(stats = nbr_table_head(link_stats); stats != NULL;
        stats = nbr_table_next(link_stats, stats)) {
  
      struct link_packet_counter *c = &stats->cnt_current;
  
      LOG_INFO("num packets: tx=%u ack=%u rx=%u to=",
               c->num_packets_tx, c->num_packets_acked, c->num_packets_rx);
      LOG_INFO_LLADDR(link_stats_get_lladdr(stats));
      LOG_INFO_("\n");
  
      stats->cnt_total.num_packets_tx += stats->cnt_current.num_packets_tx;
      stats->cnt_total.num_packets_acked += stats->cnt_current.num_packets_acked;
      stats->cnt_total.num_packets_rx += stats->cnt_current.num_packets_rx;
      memset(&stats->cnt_current, 0, sizeof(stats->cnt_current));
    }
  }
  /*---------------------------------------------------------------------------*/
  #endif /* LINK_STATS_PACKET_COUNTERS */
  /*---------------------------------------------------------------------------*/
  /* Periodic timer called at a period of FRESHNESS_HALF_LIFE */
  static void
  periodic(void *ptr)
  {
    /* Age (by halving) freshness counter of all neighbors */
    struct link_stats *stats;
    ctimer_reset(&periodic_timer);
    for(stats = nbr_table_head(link_stats); stats != NULL; stats = nbr_table_next(link_stats, stats)) {
      stats->freshness >>= 1;
    }
  
  #if LINK_STATS_PACKET_COUNTERS
    print_and_update_counters();
  #endif
  }
  /*---------------------------------------------------------------------------*/
  /* Resets link-stats module */
  void
  link_stats_reset(void)
  {
    struct link_stats *stats;
    stats = nbr_table_head(link_stats);
    while(stats != NULL) {
      nbr_table_remove(link_stats, stats);
      stats = nbr_table_next(link_stats, stats);
    }
  }
  /*---------------------------------------------------------------------------*/
  /* Initializes link-stats module */
  void
  link_stats_init(void)
  {
    nbr_table_register(link_stats, NULL);
    ctimer_set(&periodic_timer, FRESHNESS_HALF_LIFE, periodic, NULL);
  }
  