/*
     This file is part of GNUnet.
     Copyright (C) 2011-2014 GNUnet e.V.

     GNUnet is free software: you can redistribute it and/or modify it
     under the terms of the GNU Affero General Public License as published
     by the Free Software Foundation, either version 3 of the License,
     or (at your option) any later version.

     GNUnet is distributed in the hope that it will be useful, but
     WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     Affero General Public License for more details.

     You should have received a copy of the GNU Affero General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.

     SPDX-License-Identifier: AGPL3.0-or-later
 */

/**
 * @file core/gnunet-service-core_typemap.c
 * @brief management of map that specifies which message types this peer supports
 * @author Christian Grothoff
 */
#include "platform.h"
#include "gnunet_util_lib.h"
#include "gnunet-service-core.h"
#include "gnunet-service-core_sessions.h"
#include "gnunet-service-core_typemap.h"
#include <zlib.h>


/**
 * A type map describing which messages a given neighbour is able
 * to process.
 */
struct GSC_TypeMap
{
  uint32_t bits[(UINT16_MAX + 1) / 32];
};

/**
 * Bitmap of message types this peer is able to handle.
 */
static struct GSC_TypeMap my_type_map;

/**
 * Counters for message types this peer is able to handle.
 */
static uint8_t map_counters[UINT16_MAX + 1];

/**
 * Current hash of our (uncompressed) type map.
 * Lazily computed when needed.
 */
static struct GNUNET_HashCode my_tm_hash;

/**
 * Is #my_tm_hash() current with respect to our type map?
 */
static int hash_current;


/**
 * Our type map changed, recompute its hash.
 */
static void
rehash_typemap ()
{
  hash_current = GNUNET_NO;
}


/**
 * Hash the contents of a type map.
 *
 * @param tm map to hash
 * @param hc where to store the hash code
 */
void
GSC_TYPEMAP_hash (const struct GSC_TypeMap *tm, struct GNUNET_HashCode *hc)
{
  GNUNET_CRYPTO_hash (tm, sizeof(struct GSC_TypeMap), hc);
}


/**
 * Check if the given hash matches our current type map.
 *
 * @param hc hash code to check if it matches our type map
 * @return #GNUNET_YES if the hash matches, #GNUNET_NO if not
 */
int
GSC_TYPEMAP_check_hash (const struct GNUNET_HashCode *hc)
{
  if (GNUNET_NO == hash_current)
  {
    GSC_TYPEMAP_hash (&my_type_map, &my_tm_hash);
    hash_current = GNUNET_YES;
  }
  return (0 == memcmp (hc, &my_tm_hash, sizeof(struct GNUNET_HashCode)))
         ? GNUNET_YES
         : GNUNET_NO;
}


/**
 * Compute a type map message for this peer.
 *
 * @return this peers current type map message.
 */
struct GNUNET_MessageHeader *
GSC_TYPEMAP_compute_type_map_message ()
{
  char *tmp;
  uLongf dlen;
  struct GNUNET_MessageHeader *hdr;

#ifdef compressBound
  dlen = compressBound (sizeof(my_type_map));
#else
  dlen = sizeof(my_type_map) + (sizeof(my_type_map) / 100) + 20;
  /* documentation says 100.1% oldSize + 12 bytes, but we
   * should be able to overshoot by more to be safe */
#endif
  hdr = GNUNET_malloc (dlen + sizeof(struct GNUNET_MessageHeader));
  tmp = (char *) &hdr[1];
  if ((Z_OK != compress2 ((Bytef *) tmp,
                          &dlen,
                          (const Bytef *) &my_type_map,
                          sizeof(my_type_map),
                          9)) ||
      (dlen >= sizeof(my_type_map)))
  {
    /* compression failed, use uncompressed map */
    dlen = sizeof(my_type_map);
    GNUNET_memcpy (tmp, &my_type_map, sizeof(my_type_map));
    hdr->type = htons (GNUNET_MESSAGE_TYPE_CORE_BINARY_TYPE_MAP);
  }
  else
  {
    /* compression worked, use compressed map */
    hdr->type = htons (GNUNET_MESSAGE_TYPE_CORE_COMPRESSED_TYPE_MAP);
  }
  hdr->size = htons ((uint16_t) dlen + sizeof(struct GNUNET_MessageHeader));
  return hdr;
}


/**
 * Extract a type map from a TYPE_MAP message.
 *
 * @param msg a type map message
 * @return NULL on error
 */
struct GSC_TypeMap *
GSC_TYPEMAP_get_from_message (const struct GNUNET_MessageHeader *msg)
{
  struct GSC_TypeMap *ret;
  uint16_t size;
  uLongf dlen;

  size = ntohs (msg->size);
  switch (ntohs (msg->type))
  {
  case GNUNET_MESSAGE_TYPE_CORE_BINARY_TYPE_MAP:
    GNUNET_STATISTICS_update (GSC_stats,
                              gettext_noop ("# type maps received"),
                              1,
                              GNUNET_NO);
    if (size != sizeof(struct GSC_TypeMap))
    {
      GNUNET_break_op (0);
      return NULL;
    }
    ret = GNUNET_new (struct GSC_TypeMap);
    GNUNET_memcpy (ret, &msg[1], sizeof(struct GSC_TypeMap));
    return ret;

  case GNUNET_MESSAGE_TYPE_CORE_COMPRESSED_TYPE_MAP:
    GNUNET_STATISTICS_update (GSC_stats,
                              gettext_noop ("# type maps received"),
                              1,
                              GNUNET_NO);
    ret = GNUNET_new (struct GSC_TypeMap);
    dlen = sizeof(struct GSC_TypeMap);
    if ((Z_OK != uncompress ((Bytef *) ret,
                             &dlen,
                             (const Bytef *) &msg[1],
                             (uLong) size)) ||
        (dlen != sizeof(struct GSC_TypeMap)))
    {
      GNUNET_break_op (0);
      GNUNET_free (ret);
      return NULL;
    }
    return ret;

  default:
    GNUNET_break (0);
    return NULL;
  }
}


/**
 * Send my type map to all connected peers (it got changed).
 */
static void
broadcast_my_type_map ()
{
  struct GNUNET_MessageHeader *hdr;

  hdr = GSC_TYPEMAP_compute_type_map_message ();
  GNUNET_STATISTICS_update (GSC_stats,
                            gettext_noop ("# updates to my type map"),
                            1,
                            GNUNET_NO);
  GSC_SESSIONS_broadcast_typemap (hdr);
  GNUNET_free (hdr);
}


/**
 * Add a set of types to our type map.
 *
 * @param types array of message types supported by this peer
 * @param tlen number of entries in @a types
 */
void
GSC_TYPEMAP_add (const uint16_t *types, unsigned int tlen)
{
  unsigned int i;
  int changed;

  changed = GNUNET_NO;
  for (i = 0; i < tlen; i++)
  {
    if (0 == map_counters[types[i]]++)
    {
      my_type_map.bits[types[i] / 32] |= (1 << (types[i] % 32));
      changed = GNUNET_YES;
    }
  }
  if (GNUNET_YES == changed)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Typemap changed, broadcasting!\n");
    rehash_typemap ();
    broadcast_my_type_map ();
  }
}


void
GSC_TYPEMAP_remove (const uint16_t *types, unsigned int tlen)
{
  int changed;

  changed = GNUNET_NO;
  for (unsigned int i = 0; i < tlen; i++)
  {
    if (0 == --map_counters[types[i]])
    {
      my_type_map.bits[types[i] / 32] &= ~(1 << (types[i] % 32));
      changed = GNUNET_YES;
    }
  }
  if (GNUNET_YES == changed)
  {
    rehash_typemap ();
    broadcast_my_type_map ();
  }
}


/**
 * Test if any of the types from the types array is in the
 * given type map.
 *
 * @param tmap map to test
 * @param types array of types
 * @param tcnt number of entries in @a types
 * @return #GNUNET_YES if a type is in the map, #GNUNET_NO if not
 */
int
GSC_TYPEMAP_test_match (const struct GSC_TypeMap *tmap,
                        const uint16_t *types,
                        unsigned int tcnt)
{
  if (NULL == tmap)
    return GNUNET_NO;
  if (0 == tcnt)
    return GNUNET_YES; /* matches all */
  for (unsigned int i = 0; i < tcnt; i++)
    if (0 != (tmap->bits[types[i] / 32] & (1 << (types[i] % 32))))
      return GNUNET_YES;
  return GNUNET_NO;
}


/**
 * Add additional types to a given typemap.
 *
 * @param tmap map to extend (not changed)
 * @param types array of types to add
 * @param tcnt number of entries in @a types
 * @return updated type map (fresh copy)
 */
struct GSC_TypeMap *
GSC_TYPEMAP_extend (const struct GSC_TypeMap *tmap,
                    const uint16_t *types,
                    unsigned int tcnt)
{
  struct GSC_TypeMap *ret;

  ret = GNUNET_new (struct GSC_TypeMap);
  if (NULL != tmap)
    GNUNET_memcpy (ret, tmap, sizeof(struct GSC_TypeMap));
  for (unsigned int i = 0; i < tcnt; i++)
    ret->bits[types[i] / 32] |= (1 << (types[i] % 32));
  return ret;
}


/**
 * Create an empty type map.
 *
 * @return an empty type map
 */
struct GSC_TypeMap *
GSC_TYPEMAP_create ()
{
  return GNUNET_new (struct GSC_TypeMap);
}


/**
 * Free the given type map.
 *
 * @param tmap a type map
 */
void
GSC_TYPEMAP_destroy (struct GSC_TypeMap *tmap)
{
  GNUNET_free (tmap);
}


/**
 * Initialize typemap subsystem.
 */
void
GSC_TYPEMAP_init ()
{
  /* nothing to do */
}


/**
 * Shutdown typemap subsystem.
 */
void
GSC_TYPEMAP_done ()
{
  /* nothing to do */
}


/* end of gnunet-service-core_typemap.c */
