/*
** SellaNMS
** $Id: netblock.cxx,v 1.5 2006/01/24 07:08:09 sella Exp $
** Copyright (c) 2001-2005 Digital Genesis Software, LLC. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
*/

static const char rcsid[] = "$Id: netblock.cxx,v 1.5 2006/01/24 07:08:09 sella Exp $";

#include "netblock.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stl/string>

Netblock::Netblock(): is_valid(false), network(0), netmask(0) {
}

/**
	Create a new netblock.  If (char *netblock) is not a valid netblock,
	is valid will remain false;
*/
Netblock::Netblock(const string netblock_in) {
	setNetblock(netblock_in);
}

Netblock::Netblock(Netblock const &src) {
	*this = src;
}

void Netblock::clear() {
	is_valid = false;
	network = 0;
	netmask = 0;
}

RESULT Netblock::setNetblock(const string netblock_in) {
	RESULT result;

	int idx, size;
	char buf[21], address[21], cidr[21];

	network = 0;
	netmask = 0;

	buf[0] = '\0';
	cidr[0] = '\0';
	address[0] = '\0';

	/* Copy a reasonable amount of netblock_in, for examination. */
	strncpy(buf, netblock_in.c_str(), 20);
	buf[20] = '\0';

	size = strlen(buf);
	for (idx = 0; idx <= size; idx++) {
		if (!isdigit(buf[idx]) && buf[idx] != '.') {
			strncpy(address, buf, idx);
			address[idx] = '\0';

			break;
		}
	}

	/* Aquire out network address */
	if ((result = octetToInteger(address, network)) == SUCCESS) {
		if (buf[idx] == '/') { /* There is a netmask specified */
			for (int i = idx + 1; i <= size; i++) {
				if (!isdigit(buf[i])) {
					strncpy(cidr, buf + idx + 1, i - (idx + 1));
					cidr[i - (idx + 1)] = '\0';

					break;
				}
			}

			if (strlen(cidr) < 3) {
				netmask = (strtol(cidr, NULL, 10) <= 0) ? 0 : (0xffffffff << (32 - strtol(cidr, NULL, 10)));
			} else {
				result = FAILURE;
			}
		} else { /* Assume /32 */
			netmask = 0xffffffff;
		}
	}

	if (result == SUCCESS) {
		is_valid = true;
		network &= netmask;
	} else {
		network = 0;
		netmask = 0;
		is_valid = false;
	}

	return result;
}

RESULT Netblock::setNetblock(const string netblock_in, const string netmask_in) {
	RESULT result;

	network = 0;
	netmask = 0;

	if ((result = setNetblock(netblock_in)) == SUCCESS) {
		result = octetToInteger(netmask_in, netmask);
	}

	if (result == SUCCESS) {
		is_valid = true;
		network &= netmask;
	} else {
		is_valid = false;
		network = 0;
		netmask = 0;
	}

	return result;
}

RESULT Netblock::octetToInteger(const string octet, unsigned long &address) {
	RESULT result = SUCCESS;

	int oct = 0, tmp;
	char *buf, *start, *end;

	buf = new char[octet.size() + 2];
	if (buf) {
		buf[0] = '.';
		strncpy(buf + 1, octet.c_str(), octet.size() + 1);
		//ASSERT: We now have ".255.255.255.255"

		start = buf;
		end = buf + strlen(buf);

		address = 0;
		for (char *p = end; p >= start; p--) {
			if (*p == '.') {
				*p = ' ';
				tmp = strtol(p + 1, NULL, 10);
				if (tmp >= 0 && tmp <= 255) {
					address |= tmp << (oct++ * 8);
				} else {
					result = FAILURE;
					address = 0;

					LMSG("Octet out of range: %s", octet.c_str());

					break;
				}
			}
		}
		delete[] buf;
	}

	if (oct != 4) {
		result = FAILURE;
		address = 0;

		LMSG("Were not 4 octets: %s", octet.c_str());
	}

	return result;
}

unsigned long Netblock::isValid() {
	return is_valid;
}

RESULT Netblock::getNetworkAddress(unsigned long &network) {
	RESULT ret_val = SUCCESS;

	network = this->network;

	return ret_val;
}

RESULT Netblock::getNetworkAddress(char* result) {
	RESULT ret_val = FAILURE;

	if (is_valid) {
		if (result == NULL) {
			LMSG("Passed parameter, result, is NULL.");

			ret_val = FAILURE;
		} else {
			char *p = result;
			for (int i = 24; i >= 0; i -= 8) {
				p += sprintf(p, "%lu", ((network & netmask) & (0x000000ff << i)) >> i);
				if (i) {
					*(p++) = '.';
				}
			}
			ret_val = SUCCESS;
		}
	} else {
		LMSG("(*this) is not a valid object.");

		ret_val = FAILURE;
	}

	return ret_val;
}

RESULT Netblock::getNetworkCidrAddress(char* result) {
	RESULT ret_val = FAILURE;

	char buf[32];
	unsigned long length;

	if (getNetworkAddress(buf) == SUCCESS) {
		getPrefixLength(length);

		snprintf(result, 19, "%s/%lu", buf, length);

		ret_val = SUCCESS;
	}

	return ret_val;
}

RESULT Netblock::getPrefixLength(unsigned long &length) {
	RESULT ret_val = SUCCESS;

	unsigned long prefix_len = 0xffffffff;

	for (int i = 32; i >= 0; i--) {
		if (netmask == prefix_len) {
			length = i;
			break;
		}
		prefix_len = prefix_len << 1;
	}
	
	return ret_val;
}

RESULT Netblock::getNetmask(unsigned long &netmask) {
	RESULT ret_val = SUCCESS;

	netmask = this->netmask;
	
	return ret_val;
}

RESULT Netblock::getNetmask(char *result) {
	RESULT ret_val = FAILURE;

	if (is_valid) {
		if (result == NULL) {
			LMSG("Passed parameter, result, is NULL.");

			ret_val = FAILURE;
		} else {
			char *p = result;
			for (int i = 24; i >= 0; i -= 8) {
				p += sprintf(p, "%lu", ((netmask) & (0x000000ff << i)) >> i);
				if (i) {
					*(p++) = '.';
				}
			}
			ret_val = SUCCESS;
		}
	} else {
		LMSG("(*this) is not a valid object.");

		ret_val = FAILURE;
	}
	
	return ret_val;
}

RESULT Netblock::getBroadcastAddress(char* result) {
	RESULT ret_val = FAILURE;

	if (is_valid) {
		if (result == NULL) {
			LMSG("Passed parameter, result, is NULL.");		

			ret_val = FAILURE;
		} else {
			char *p = result;
			for (int i = 24; i >= 0; i -= 8) {
				p += sprintf(p, "%lu", ((network | ~netmask) & (0x000000ff << i)) >> i);
				if (i) {
					*(p++) = '.';
				}
			}
			ret_val = SUCCESS;
		}
	} else {
		LMSG("(*this) is not a valid object.");

		ret_val = FAILURE;
	}

	return ret_val;
}

RESULT Netblock::compare(const Netblock &n1) {
	RESULT ret_val;
	unsigned long netmask;

	/* Shorter of the two netmasks. */
	netmask = this->netmask & n1.netmask;

	/* Compare the networks */
	if ((this->network & netmask) == (n1.network & netmask)) {
		ret_val = SUCCESS;
	} else {
		ret_val = FAILURE;
	}

	return ret_val;
}

RESULT Netblock::compare(const string netblock) {
	Netblock n1(netblock);

	return compare(n1);
}

RESULT Netblock::getIPAddress(char* result, unsigned long number) {
	RESULT ret_val = FAILURE;

	if (is_valid) {
		if (result == NULL) {
			LMSG("Passed parameter, result, is NULL.");

			ret_val = FAILURE;
		} else if (((network & netmask) + number) > (network | ~netmask)) {
			//LMSG("Passed parameter, number, is invalid (%lu).", number);

			ret_val = FAILURE;
		} else {
			char *p = result;
			unsigned long ip = (network & netmask) + number;
	
			for (int i = 24; i >= 0; i -= 8) {
				p += sprintf(p, "%lu", (ip & (0x000000ff << i)) >> i);
				if (i) {
					*(p++) = '.';
				}
			}
			ret_val = SUCCESS;
		}
	} else {
		LMSG("Passed parameter, result, is NULL.");

		ret_val = FAILURE;
	}

	return ret_val;
}

char* Netblock::operator[](unsigned long number) {
	char *ret_val = new char[16];

	if (getIPAddress(ret_val, number) == FAILURE) {
		SAFE_DELETE_ARRAY(ret_val);
	}

	return ret_val;
}

Netblock const& Netblock::operator=(Netblock const &src) {
	this->network = src.network;
	this->netmask = src.netmask;
	this->is_valid = src.is_valid;
	
	return *this;
}

/*
** Local Variables:
** c-basic-offset: 3
** tab-width: 3
** End:
** vim:
** vim: noet ts=3 sw=3
*/
