/*
 *  This file provides functions for IP Version 4 based checks and conversions.
 *  Author: Samuel Abels <spam debain org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 Library General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include "lib_ipv4.h"

//#define _DEBUG_

#define BYTE "[1-2]?[0-9]?[0-9]" // Matches an integer representing a byte.
#define PFX  "[1-3]?[0-9]"       // Matches an integer representing a prefixlen.


/* Purpose: Evaluates the logarithm to a variable base.
 */
static double log_base(double base, double val)
{
  return log(val) / log(base);
}


/* Checks the syntax of a human readable (byte notated) IP address for validity.
 * Returns: 0 on success, <0 otherwise:
 *          INVALID_FORMAT if the IP format is incorrect.
 *          OUT_OF_RANGE   if at least one of the IP's integer values is
 *                         out-of-range.
 */
short int ipv4_check_ip(const char* ip)
{
  char       *matches[1+4];
  const char *pattern = "^(" BYTE ")\\.(" BYTE ")\\.(" BYTE ")\\.(" BYTE ")$";
  int         i       = 0;
  if (!regexp_match_grab(ip, pattern, TRUE, matches))
    return ERR_INVALID_FORMAT;
  for (; i <= 3; i++) {
    unsigned int byte = atoi(matches[i]);
    if (byte < 0 || byte > 255) {
      free_regexp_matches(matches);
      return ERR_OUT_OF_RANGE;
    }
  }
  free_regexp_matches(matches);
  return 0;
}


/* Checks the an IP prefixlength for validity.
 * Returns: 0 on success, <0 otherwise:
 *          INVALID_PREFIXLEN if the prefixlength is invalid.
 */
short int ipv4_check_prefixlen(unsigned short int pfxlen)
{
  if (pfxlen > 32)
    return ERR_INVALID_PREFIXLEN;
  return 0;
}


/* Checks the syntax of an IP address prefix for validity.
 * Returns: 0 on success, <0 otherwise:
 *          INVALID_FORMAT    if the IP format is incorrect.
 *          OUT_OF_RANGE      if at least one of the IP's integer values is
 *                            out-of-range.
 *          INVALID_PREFIXLEN if the prefixlength value is out of range.
 */
short int ipv4_check_prefix(const char* prefix)
{
  char       *matches[1+2];
  const char *pattern = "^(" BYTE "\\." BYTE "\\." BYTE "\\." BYTE ")"
                        "/(" PFX ")$";
  int         err     = 0;
  if (!regexp_match_grab(prefix, pattern, TRUE, matches))
    return ERR_INVALID_FORMAT;
  err  = ipv4_check_ip(matches[0]);               // Check the IP address.
  err &= ipv4_check_prefixlen(atoi(matches[1]));  // Check prefixlen.
  free_regexp_matches(matches);
  return err;
}


/* Convert a human readable (byte notated) ip address to a 4-byte integer value.
 * Returns: 0 on success, <0 otherwise:
 *          INVALID_FORMAT if the IP format is incorrect.
 *          OUT_OF_RANGE   if at least one of the IP's integer values is
 *                         out-of-range.
 */
short int ipv4_ip2integer(const char* ip, unsigned long* ip_int)
{
  char       *matches[1+4];
  const char *pattern = "(" BYTE ")\\.(" BYTE ")\\.(" BYTE ")\\.(" BYTE ")";
  int         i       = 0;
  *ip_int = 0;
  if (regexp_match_grab(ip, pattern, TRUE, matches) != 0)
    return ERR_INVALID_FORMAT;
  for (i = 0; i <= 3; i++) {
    unsigned int byte = atoi(matches[i]);
    if (byte < 0 || byte > 255)
      return ERR_OUT_OF_RANGE;
    *ip_int |= byte << (24 - (i * 8));
#ifdef _DEBUG_
    printf("ipv4_ip2integer(): Text: %s, Byte: %i, Sum: %u\n",
           matches[i], byte, *ip_int);
#endif
  }
  free_regexp_matches(matches);
  return 0;
}


/* Converts a 4 byte integer value into a human readable (byte notated) ip
 * address.
 */
short int ipv4_integer2ip(unsigned long ip_int, char* ip)
{
  sprintf(ip, "%i.%i.%i.%i", ((ip_int >> 24) & 0x000000FF),
                             ((ip_int >> 16) & 0x000000FF),
                             ((ip_int >>  8) & 0x000000FF),
                             ( ip_int        & 0x000000FF));
  return 0;
}


/* Converts a 4 byte integer value into a human readable (byte notated) binary
 * ip address.
 */
short int ipv4_integer2bin(unsigned long ip_int, char* ip_bin)
{
  int i = 31;
  for (; i >= 0; i--) {
    if (i == 23 || i == 15 || i == 7)
      *ip_bin++ = '.';
    unsigned long bit = (ip_int & (1 << i));
    *ip_bin = bit ? '1' : '0';
    ip_bin++;
  }
  *ip_bin = 0;
  return 0;
}


/* Converts a 4 byte integer value into a hexadezimal value.
 */
short int ipv4_integer2hex(unsigned long ip_int, char* ip_hex)
{
  int i = 3;
  for (; i >= 0; i--) {
    sprintf(ip_hex, "%02X", (ip_int >> (i * 8)) & 0xFF);
    ip_hex += strlen(ip_hex);
    if (i != 0)
      *ip_hex++ = '.';
  }
  //sprintf(ip_hex, "%#X", ip_int);
  return 0;
}


/* Convert a prefixlength to an IP mask.
 * Returns: 0 on success, <0 otherwise:
 *          INVALID_PREFIXLEN if the prefixlength is invalid.
 */
short int ipv4_pfxlen2mask(unsigned short int pfxlen, unsigned long *mask)
{
  if (pfxlen == 0) {       // /0 networks.
    *mask = 0;
    return 0;
  }
  int err = 0;
  if ((err = ipv4_check_prefixlen(pfxlen)) < 0)  // Check for validity.
    return err;
  *mask = 0xFFFFFFFF << (32 - pfxlen);
  return err;
}


/* Convert an IP mask address to a prefixlength.
 * Returns 0.
 */
short int ipv4_mask2pfxlen(unsigned long mask, unsigned short int* pfxlen)
{
  if (mask & 0xFFFFFFFF == 0xFFFFFFFF) {       // /32 networks.
    *pfxlen = 32;
    return 0;
  }
  mask    = mask ^ 0xFFFFFFFF;                 // Invert the value.
  *pfxlen = 32 - ((int)log_base(2, mask) + 1); // Calculate the prefix length.
  return 0;
}


/* Given an IP address and a netmask, this function stores the broadcast
 * address in "broadcast". Returns 0.
 */
short int ipv4_get_broadcast(unsigned long ip,
                             unsigned long mask,
                             unsigned long* broadcast)
{
  *broadcast = (ip & mask) | (mask ^ 0xFFFFFFFF);
  return 0;
}


/* Given a prefix length, this function stores the number of
 * host addresses in "num", EXCLUDING network/broadcast. Returns 0.
 */
short int ipv4_get_num_hosts(unsigned short int pfxlen,
                             unsigned int* num)
{
  int hosts = 0;
  switch (pfxlen) {
  case 0:
    hosts = 0xFFFFFFFC;
    break;
    
  case 31:
    hosts = 2;
    break;
    
  case 32:
    hosts = 1;
    break;
    
  default:
    hosts = (unsigned int)pow(2, 32 - pfxlen) - 2;
    break;
  }
  
  *num = hosts;
  return 0;
}


/* Given a prefix length, this function stores the number of
 * host addresses in "num" INCLUDING network/broadcast. Returns 0.
 */
short int ipv4_get_num_hosts_all(unsigned short int pfxlen,
                                 unsigned int* num)
{
  if (pfxlen == 0)
    *num = 0xFFFFFFFF;
  else
    *num = (unsigned int)pow(2, 32 - pfxlen);
  return 0;
}


/* Given a prefix length, this function stores the number of
 * subnet addresses in "num". Returns 0.
 */
short int ipv4_get_num_subnets(unsigned short int pfxlen,
                               unsigned int* num)
{
  *num = (unsigned int)pow(2, pfxlen);
  if (pfxlen == 32)
    *num = 0xFFFFFFFF;
  return 0;
}


/* Checks whether "net2/pfxlen" meets all of the given criterias (arguments).
 * Returns: TRUE if match, FALSE if no match, <0 on an error.
 */
short int ipv4_prefix_match(unsigned long net1,
                            unsigned long mask1,
                            unsigned short int le,
                            unsigned short int ge,
                            unsigned long net2,
                            unsigned long pfxlen)
{
  unsigned long mask2     = 0;
  unsigned long err       = 0;
  short int    match     = FALSE;
  if ((err = ipv4_pfxlen2mask(pfxlen, &mask2)) < 0)
    return err;
  match = (net1 & mask1) == (net2 & mask2);
  return match && (pfxlen <= le) && (pfxlen >= ge);
}


/* Given an address range, this function returns a list of prefixes covering
 * all its addresses, and /only/ the range addresses.
 * Returns: 0.
 */
short int ipv4_get_prefixes_from_range(unsigned long from,
                                       unsigned long to,
                                       Prefix** prefixlist)
{
  unsigned long mask   = 0;
  short int     pfxlen = 0;
  int           num    = 0;
  Prefix*       prefix = NULL;
  
  for (pfxlen = 0; pfxlen <= 32; pfxlen++) {
#ifdef _DEBUG_
    printf("ipv4_get_prefixes_from_range(): Prefixlen %i\n", pfxlen);
#endif
    ipv4_pfxlen2mask((unsigned short int)pfxlen, &mask);
    if ((from & mask) < from)
      continue;
#ifdef _DEBUG_
    printf("ipv4_get_prefixes_from_range(): Works for 'from'.\n");
#endif
    // Ending up here, we have found the largest-possible match for "from".
    // But the prefix end may already be risen above "to".
    // Check this.
    if (((from & mask) | (mask ^ 0xFFFFFFFF)) > to)
      continue;
#ifdef _DEBUG_
    printf("ipv4_get_prefixes_from_range(): Works for 'to'.\n");
#endif
    // Found the first prefix, push it in the array.
    prefix            = (Prefix*)malloc(sizeof(Prefix));
    prefix->net       = from & mask;
    prefix->len       = (unsigned short int)pfxlen;
    prefixlist[num++] = prefix;
    // We are done if the broadcast address of the prefix equals the range end.
    if (((from & mask) | (mask ^ 0xFFFFFFFF)) == to)
      break;
#ifdef _DEBUG_
    printf("ipv4_get_prefixes_from_range(): 'to' not equal broadcast.\n");
#endif
    // Increase "from" so that it points to the beginning of the next network.
    from = ((from & mask) | (mask ^ 0xFFFFFFFF)) + 1;
    pfxlen = -1;
  }
  prefixlist[num] = NULL;
  return 0;
}


/* Same as ipv4_get_prefixes_from_range, but uses fixed length subnetting.
 * Returns: 0.
 */
short int ipv4_get_prefixes_from_range_flsm(unsigned long from,
                                            unsigned long to,
                                            unsigned short int pfxlen,
                                            Prefix** prefixlist,
                                            unsigned int maxmatch)
{
  unsigned long inc    = 0;
  unsigned long mask   = 0;
  unsigned int  num    = 0;
  Prefix*       prefix = NULL;
  
  ipv4_pfxlen2mask(pfxlen, &mask);
  inc = mask ^ 0xFFFFFFFF;
#ifdef _DEBUG_
  printf("ipv4_get_prefixes_from_range_flsm(): Prefixlen: %d\n", pfxlen);
  printf("ipv4_get_prefixes_from_range_flsm(): IncrementC: %d\n", inc);
#endif
  
  while (from + inc <= to) {
#ifdef _DEBUG_
  printf("ipv4_get_prefixes_from_range_flsm(): Flag A.\n");
#endif
    if (from & mask < from)
      break;
#ifdef _DEBUG_
  printf("ipv4_get_prefixes_from_range_flsm(): Flag B.\n");
#endif
    if (num > maxmatch)
      break;
#ifdef _DEBUG_
  printf("ipv4_get_prefixes_from_range_flsm(): Flag C.\n");
#endif
    
    // Ending up here, we have found a match. Push it into an array.
    prefix            = (Prefix*)malloc(sizeof(Prefix));
    prefix->net       = (unsigned long)from;
    prefix->len       = (unsigned short int)pfxlen;
    prefixlist[num++] = prefix;
    
    if ((unsigned long)(from + inc + 1) <= from)
      break;
#ifdef _DEBUG_
  printf("ipv4_get_prefixes_from_range_flsm(): Flag D.\n");
#endif
    
    from += inc;
    from++;
  }
  prefixlist[num] = NULL;
  return 0;
}


/* Frees the content of a prefixlist previously returned by
 * ipv4_get_prefixes_from_range().
 */
void ipv4_free_prefixlist(Prefix** prefixlist)
{
  int i = -1;
  while (prefixlist[++i])
    free(prefixlist[i]);
}
