//===========================================================================
//  $Name: arts++-1-1-a12 $
//  $Id: Ipv4Network.hh,v 1.2 2004/04/21 23:51:30 kkeys Exp $
//
//  Daniel W. McRobb
//  CAIDA
//  December 1998
//===========================================================================

#ifndef _IPV4NETWORK_HH_
#define _IPV4NETWORK_HH_

extern "C" {
#include "artslocal.h"
#include "caida_t.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
}

#ifdef HAVE_IOSTREAM
  #include <iostream>
#else
  #include <iostream.h>
#endif

//----------------------------------------------------------------------------
//                            class Ipv4Network                            
//----------------------------------------------------------------------------
//!  This class encapsulates an IPv4 network address.  It's really
//!  nothing more than a struct (not the data members are public) with
//!  some constructors and overloaded operators, plus a Matches() member
//!  for testing whether or not a host IP address in inside the
//!  Ipv4Network (where 'inside' means the network portion of the host IP
//!  address matches the Ipv4Network).
//----------------------------------------------------------------------------
class Ipv4Network 
{
public:
  ipv4addr_t   net;
  uint8_t      maskLen;
  
  //--------------------------------------------------------------------------
  //                              Ipv4Network()                              
  //..........................................................................
  //!  constructor
  //--------------------------------------------------------------------------
  Ipv4Network()
  {
    this->net     = 0;
    this->maskLen = 32;
  }

  //--------------------------------------------------------------------------
  //               Ipv4Network(const Ipv4Network & ipv4Network) 
  //..........................................................................
  //!  copy constructor
  //--------------------------------------------------------------------------
  Ipv4Network(const Ipv4Network & ipv4Network)
  {
    this->net     = ipv4Network.net;
    this->maskLen = ipv4Network.maskLen;
  }
  
  //--------------------------------------------------------------------------
  //           Ipv4Network(ipv4addr_t network, uint8_t masklength)           
  //..........................................................................
  //!  Constructor that accepts netaork address and masklength as
  //!  parameters.  'network' must be in network byte order.  It may be
  //!  a host or network address; in either case the constructed object
  //!  will have a network address of (network & netmask), where netmask
  //!  is determined by the masklength.
  //--------------------------------------------------------------------------
  Ipv4Network(ipv4addr_t network, uint8_t masklength)
      : maskLen(masklength)
  {
    ipv4addr_t  netmask = 0xffffffff;

    if (masklength != 32) {
      if (masklength == 0) {
        netmask = 0;
      }
      else {
        netmask = htonl(0xffffffff << (32 - masklength));
      }
    }
    
    this->net = network & netmask;
  }
  
  //--------------------------------------------------------------------------
  //            Ipv4Network & operator = (const Ipv4Network & key)           
  //..........................................................................
  //!  Overloaded = operator.  Deep-copies one Ipv4Network object to
  //!  another Ipv4Network.
  //--------------------------------------------------------------------------
  Ipv4Network & operator = (const Ipv4Network & key)
  {
    this->net     = key.net;
    this->maskLen = key.maskLen;
    return(*this);
  }
  
  //--------------------------------------------------------------------------
  //       bool operator == (const Ipv4Network & key) const 
  //..........................................................................
  //!  == operator.  Returns true if *this is exactly equal to \c key
  //!  (both \c net and \c maskLen match exactly).  Else returns false.
  //--------------------------------------------------------------------------
  bool operator == (const Ipv4Network & key) const
  {
    return(this->net == key.net && this->maskLen == key.maskLen);
  }

  //--------------------------------------------------------------------------
  //             bool operator < (const Ipv4Network & key) const             
  //..........................................................................
  //!  Overloaded < operator.  If the LHS network address is less than
  //!  the RHS network address, or the network addresses are equal but
  //!  the LHS network mask length is less than the RHS network mask
  //!  length, we return true.  Else we return false.  This permits
  //!  semi-standard ordering of objects by network address.
  //--------------------------------------------------------------------------
  bool operator < (const Ipv4Network & key) const
  {
    return((ntohl(this->net) < ntohl(key.net)) ||
           ((this->net == key.net) && (this->maskLen < key.maskLen)));
  }
  
  //--------------------------------------------------------------------------
  //             bool operator > (const Ipv4Network & key) const             
  //..........................................................................
  //!  Overloaded > operator.  If the LHS network address is greater than
  //!  the RHS network address, or the network addresses are equal but
  //!  the LHS network mask length is greater than the RHS network mask
  //!  length, we return true.  Else we return false.  This permits
  //!  semi-standard ordering of objects by network address.
  //--------------------------------------------------------------------------
  bool operator > (const Ipv4Network & key) const
  {
    return((ntohl(this->net) > ntohl(key.net)) ||
           ((this->net == key.net) && (this->maskLen > key.maskLen)));
  }

  //--------------------------------------------------------------------------
  //                 bool Matches(ipv4addr_t ipv4addr) const                 
  //..........................................................................
  //!  Given an IP address, return true if it falls within the Ipv4Network.
  //!  Else return false.
  //--------------------------------------------------------------------------
  bool Matches(ipv4addr_t ipv4addr) const
  {
    ipv4addr_t     netmask = htonl(0xffffffff << (32 - this->maskLen));
    
    return((ipv4addr & netmask) == this->net);
  }
  
  //--------------------------------------------------------------------------
  //        friend std::ostream & operator << (std::ostream & os,               
  //                                      const Ipv4Network & ipv4net)  
  //..........................................................................
  //!  Overloaded << operator.  Prints an Ipv4Network to an ostream and
  //!  returns a reference to the ostream.
  //--------------------------------------------------------------------------
  friend std::ostream & operator << (std::ostream & os,
                                const Ipv4Network & ipv4net)
  {
    struct in_addr addrIn;

    addrIn.s_addr = ipv4net.net;
    os << inet_ntoa(addrIn) << "/" << (int)ipv4net.maskLen;
    return(os);
  }

  //--------------------------------------------------------------------------
  //                       std::istream & read(std::istream & is)                      
  //..........................................................................
  //!  Reads an Ipv4Network from an istream.  Returns the istream.
  //--------------------------------------------------------------------------
  std::istream & read(std::istream & is)
  {
    is.read((char*)&this->maskLen,sizeof(this->maskLen));
    uint8_t  octet1  = 0;
    uint8_t  octet2  = 0;
    uint8_t  octet3  = 0;
    uint8_t  netSize = (this->maskLen + 7) / 8;
    
    switch (netSize) {
      case 1:
        is.read((char*)&octet1,sizeof(octet1));
        this->net = htonl((ipv4addr_t)octet1 << 24);
        break;
      case 2:
        is.read((char*)&octet1,sizeof(octet1));
        is.read((char*)&octet2,sizeof(octet2));
        this->net =
          htonl(((ipv4addr_t)octet1 << 24) | ((ipv4addr_t)octet2 << 16));
        break;
      case 3:
        is.read((char*)&octet1,sizeof(octet1));
        is.read((char*)&octet2,sizeof(octet2));
        is.read((char*)&octet3,sizeof(octet3));
        this->net = htonl(((ipv4addr_t)octet1 << 24) |
                          ((ipv4addr_t)octet2 << 16) |
                          ((ipv4addr_t)octet3 << 8));
        break;
      case 4:
        is.read((char*)&this->net,sizeof(this->net));
        break;
      default:
        break;
    }
    return(is);
  }

  //--------------------------------------------------------------------------
  //                             int read(int fd) 
  //..........................................................................
  //!  Reads an Ipv4Network from a file descriptor.  Returns the number of
  //!  bytes read on success, -1 on failure.
  //--------------------------------------------------------------------------
  int read(int fd)
  {
    int   rc;
    int   bytesRead = 0;

    rc = ::read(fd,&this->maskLen,sizeof(this->maskLen));
    if (rc < (int)sizeof(this->maskLen))
      return(-1);
    bytesRead += rc;
    
    uint8_t  octet1  = 0;
    uint8_t  octet2  = 0;
    uint8_t  octet3  = 0;
    uint8_t  netSize = (this->maskLen + 7) / 8;
    
    switch (netSize) {
      case 1:
        rc = ::read(fd,&octet1,sizeof(octet1));
        if (rc < (int)sizeof(octet1))
          return(-1);
        bytesRead += rc;
        this->net = htonl((ipv4addr_t)octet1 << 24);
        break;
      case 2:
        rc = ::read(fd,&octet1,sizeof(octet1));
        if (rc < (int)sizeof(octet1))
          return(-1);
        bytesRead += rc;
        rc = ::read(fd,&octet2,sizeof(octet2));
        if (rc < (int)sizeof(octet2))
          return(-1);
        bytesRead += rc;
        this->net =
          htonl(((ipv4addr_t)octet1 << 24) | ((ipv4addr_t)octet2 << 16));
        break;
      case 3:
        rc = ::read(fd,&octet1,sizeof(octet1));
        if (rc < (int)sizeof(octet1))
          return(-1);
        bytesRead += rc;
        rc = ::read(fd,&octet2,sizeof(octet2));
        if (rc < (int)sizeof(octet2))
          return(-1);
        bytesRead += rc;
        rc = ::read(fd,&octet3,sizeof(octet3));
        if (rc < (int)sizeof(octet3))
          return(-1);
        bytesRead += rc;
        this->net = htonl(((ipv4addr_t)octet1 << 24) |
                          ((ipv4addr_t)octet2 << 16) |
                          ((ipv4addr_t)octet3 << 8));
        break;
      case 4:
        rc = ::read(fd,&this->net,sizeof(this->net));
        if (rc < (int)sizeof(this->net))
          return(-1);
        bytesRead += rc;
        break;
      default:
        break;
    }
    return(bytesRead);
  }
  
  
  //--------------------------------------------------------------------------
  //                   std::ostream & write(std::ostream & os) const                   
  //..........................................................................
  //!  Writes an Ipv4Network to an ostream.  Returns the ostream.
  //--------------------------------------------------------------------------
  std::ostream & write(std::ostream & os) const
  {
    //  first we write the netmask length
    os.write((char*)&this->maskLen,sizeof(this->maskLen));
    
    uint8_t  octet1 = 0;
    uint8_t  octet2 = 0;
    uint8_t  octet3 = 0;
    ipv4addr_t  netaddr;
    uint8_t  netSize = (this->maskLen + 7) / 8;

    // and then write the network prefix
    switch (netSize) {
      case 1:
        octet1 = ntohl(this->net) >> 24;
        os.write((char*)&octet1,sizeof(octet1));
        break;
      case 2:
        netaddr = ntohl(this->net);
        octet1 = (ipv4addr_t)(netaddr >> 24) & 0xff;
        octet2 = (ipv4addr_t)(netaddr >> 16) & 0xff;
        os.write((char*)&octet1,sizeof(octet1));
        os.write((char*)&octet2,sizeof(octet2));
        break;
      case 3:
        netaddr = ntohl(this->net);
        octet1 = (ipv4addr_t)(netaddr >> 24) & 0xff;
        octet2 = (ipv4addr_t)(netaddr >> 16) & 0xff;
        octet3 = (ipv4addr_t)(netaddr >> 8) & 0xff;
        os.write((char*)&octet1,sizeof(octet1));
        os.write((char*)&octet2,sizeof(octet2));
        os.write((char*)&octet3,sizeof(octet3));
        break;
      case 4:
        os.write((char*)&this->net,sizeof(this->net));
        break;
      default:
        break;
    }
    return(os);
  }

  //--------------------------------------------------------------------------
  //                         int write(int fd) const 
  //..........................................................................
  //!  Writes an Ipv4Network to a file descriptor.  Returns the number
  //!  of bytes written on success, -1 on failure.
  //--------------------------------------------------------------------------
  int write(int fd) const
  {
    int   rc;
    int   bytesWritten = 0;
    
    rc = ::write(fd,&this->maskLen,sizeof(this->maskLen));
    if (rc < (int)sizeof(this->maskLen))
      return(-1);
    bytesWritten += rc;

    uint8_t  octet1 = 0;
    uint8_t  octet2 = 0;
    uint8_t  octet3 = 0;
    ipv4addr_t  netaddr;
    uint8_t  netSize = (this->maskLen + 7) / 8;
    
    switch (netSize) {
      case 1:
        octet1 = ntohl(this->net) >> 24;
        rc = ::write(fd,&octet1,sizeof(octet1));
        if (rc < (int)sizeof(octet1))
          return(-1);
        bytesWritten += rc;
        break;
      case 2:
        netaddr = ntohl(this->net);
        octet1 = (ipv4addr_t)(netaddr >> 24) & 0xff;
        octet2 = (ipv4addr_t)(netaddr >> 16) & 0xff;
        rc = ::write(fd,&octet1,sizeof(octet1));
        if (rc < (int)sizeof(octet1))
          return(-1);
        bytesWritten += rc;
        rc = ::write(fd,&octet2,sizeof(octet2));
        if (rc < (int)sizeof(octet2))
          return(-1);
        bytesWritten += rc;
        break;
      case 3:
        netaddr = ntohl(this->net);
        octet1 = (ipv4addr_t)(netaddr >> 24) & 0xff;
        octet2 = (ipv4addr_t)(netaddr >> 16) & 0xff;
        octet3 = (ipv4addr_t)(netaddr >> 8) & 0xff;
        rc = ::write(fd,&octet1,sizeof(octet1));
        if (rc < (int)sizeof(octet1))
          return(-1);
        bytesWritten += rc;
        rc = ::write(fd,&octet2,sizeof(octet2));
        if (rc < (int)sizeof(octet2))
          return(-1);
        bytesWritten += rc;
        rc = ::write(fd,&octet3,sizeof(octet3));
        if (rc < (int)sizeof(octet3))
          return(-1);
        bytesWritten += rc;
        break;
      case 4:
        rc = ::write(fd,&this->net,sizeof(this->net));
        if (rc < (int)sizeof(this->net))
          return(-1);
        bytesWritten += rc;
        break;
      default:
        break;
    }
    return(bytesWritten);
  }

  //--------------------------------------------------------------------------
  //                         uint32_t Length() const                         
  //..........................................................................
  //!  Returns the number of bytes required to store the Ipv4Network on
  //!  disk.
  //--------------------------------------------------------------------------
  uint32_t Length() const
  {
    uint8_t  netSize = (this->maskLen + 7) / 8;
    return(sizeof(this->maskLen) + netSize);
  }
  
};


#endif  // _IPV4NETWORK_HH_
