syncing commonlibs with Many thanks to Michael Iedema for these patches, makes config a lot better.

git-svn-id: http://wush.net/svn/range/software/public/openbts/trunk@5655 19bc5d8c-e614-43d4-8b26-e1612bc8e597
diff --git a/CommonLibs/Configuration.cpp b/CommonLibs/Configuration.cpp
index 5dcc277..bda6865 100644
--- a/CommonLibs/Configuration.cpp
+++ b/CommonLibs/Configuration.cpp
@@ -32,6 +32,12 @@
 #include <iostream>
 #include <string.h>
 
+#ifdef DEBUG_CONFIG
+#define	debugLogEarly gLogEarly
+#else
+#define	debugLogEarly
+#endif
+
 
 using namespace std;
 
@@ -57,7 +63,7 @@
 }
 
 
-ConfigurationTable::ConfigurationTable(const char* filename, const char *wCmdName)
+ConfigurationTable::ConfigurationTable(const char* filename, const char *wCmdName, ConfigurationKeyMap wSchema)
 {
 	gLogEarly(LOG_INFO, "opening configuration table from path %s", filename);
 	// Connect to the database.
@@ -78,35 +84,440 @@
 	if (!sqlite3_command(mDB,createConfigTable)) {
 		gLogEarly(LOG_EMERG, "cannot create configuration table in database at %s, error message: %s", filename, sqlite3_errmsg(mDB));
 	}
+
+	// Build CommonLibs schema
+	ConfigurationKey *tmp;
+	tmp = new ConfigurationKey("Log.Alarms.Max","20",
+		"alarms",
+		ConfigurationKey::CUSTOMER,
+		ConfigurationKey::VALRANGE,
+		"10:20",// educated guess
+		false,
+		"Maximum number of alarms to remember inside the application."
+	);
+	mSchema[tmp->getName()] = *tmp;
+	free(tmp);
+
+	tmp = new ConfigurationKey("Log.File","",
+		"",
+		ConfigurationKey::DEVELOPER,
+		ConfigurationKey::FILEPATH_OPT,// audited
+		"",
+		false,
+		"Path to use for textfile based logging.  "
+			"By default, this feature is disabled.  "
+			"To enable, specify an absolute path to the file you wish to use, eg: /tmp/my-debug.log.  "
+			"To disable again, execute \"unconfig Log.File\"."
+	);
+	mSchema[tmp->getName()] = *tmp;
+	free(tmp);
+
+	tmp = new ConfigurationKey("Log.Level","NOTICE",
+		"",
+		ConfigurationKey::CUSTOMER,
+		ConfigurationKey::CHOICE,
+		"EMERG|EMERGENCY - report serious faults associated with service failure or hardware damage,"
+			"ALERT|ALERT - report likely service disruption caused by misconfiguration or poor connectivity,"
+			"CRIT|CRITICAL - report anomalous events that are likely to degrade service,"
+			"ERR|ERROR - report internal errors of the software that may result in degradation of service in unusual circumstances,"
+			"WARNING|WARNING - report anomalous events that may indicate a degradation of normal service,"
+			"NOTICE|NOTICE - report anomalous events that probably do not affect service but may be of interest to network operators,"
+			"INFO|INFORMATION - report normal events,"
+			"DEBUG|DEBUG - only for use by developers and will degrade system performance",
+		false,
+		"Default logging level when no other level is defined for a file."
+	);
+	mSchema[tmp->getName()] = *tmp;
+	free(tmp);
+
+	// Add application specific schema
+	mSchema.insert(wSchema.begin(), wSchema.end());
+
+	// Init the cross checking callback to something predictable
+	mCrossCheck = NULL;
 }
 
+string ConfigurationTable::getDefaultSQL(const std::string& program, const std::string& version)
+{
+	stringstream ss;
+	ConfigurationKeyMap::iterator mp;
 
+	ss << "--" << endl;
+	ss << "-- This file was generated using: " << program << " --gensql" << endl;
+	ss << "-- binary version: " << version << endl;
+	ss << "--" << endl;
+	ss << "-- Future changes should not be put in this file directly but" << endl;
+	ss << "-- rather in the program's ConfigurationKey schema." << endl;
+	ss << "--" << endl;
+	ss << "PRAGMA foreign_keys=OFF;" << endl;
+	ss << "BEGIN TRANSACTION;" << endl;
+	ss << "CREATE TABLE CONFIG ( KEYSTRING TEXT UNIQUE NOT NULL, VALUESTRING TEXT, STATIC INTEGER DEFAULT 0, OPTIONAL INTEGER DEFAULT 0, COMMENTS TEXT DEFAULT '');" << endl;
+
+	mp = mSchema.begin();
+	while (mp != mSchema.end()) {
+		ss << "INSERT INTO \"CONFIG\" VALUES(";
+			// name
+			ss << "'" << mp->first << "',";
+			// default
+			ss << "'" << mp->second.getDefaultValue() << "',";
+			// static
+			if (mp->second.isStatic()) {
+				ss << "1";
+			} else {
+				ss << "0";
+			}
+			ss << ",";
+			// optional
+			ss << "0,";
+			// description
+			ss << "'";
+			if (mp->second.getType() == ConfigurationKey::BOOLEAN) {
+				ss << "1=enabled, 0=disabled - ";
+			}
+			ss << mp->second.getDescription();
+			if (mp->second.isStatic()) {
+				ss << "  Static.";
+			}
+			ss << "'";
+		ss << ");" << endl;
+		mp++;
+	}
+
+	ss << "COMMIT;" << endl;
+	ss << endl;
+
+	return ss.str();
+}
+
+string ConfigurationTable::getTeX(const std::string& program, const std::string& version)
+{
+	stringstream ss;
+	ConfigurationKeyMap::iterator mp;
+
+	ss << "% START AUTO-GENERATED CONTENT" << endl;
+	ss << "% -- these sections were generated using: " << program << " --gentex" << endl;
+	ss << "% -- binary version: " << version << endl;
+
+	ss << "\\subsection{Customer Site Parameters}" << endl;
+	ss << "These parameters must be changed to fit your site." << endl;
+	ss << "\\begin{itemize}" << endl;
+	mp = mSchema.begin();
+	while (mp != mSchema.end()) {
+		if (mp->second.getVisibility() == ConfigurationKey::CUSTOMERSITE) {
+			ss << "	\\item ";
+				// name
+				ss << mp->first << " -- ";
+				// description
+				ss << mp->second.getDescription();
+			ss << endl;
+		}
+		mp++;
+	}
+	ss << "\\end{itemize}" << endl;
+	ss << endl;
+
+	ss << "\\subsection{Customer Tuneable Parameters}" << endl;
+	ss << "These parameters can be changed to optimize your site." << endl;
+	ss << "\\begin{itemize}" << endl;
+	mp = mSchema.begin();
+	while (mp != mSchema.end()) {
+		if (mp->second.getVisibility() != ConfigurationKey::CUSTOMERSITE &&
+			(
+				mp->second.getVisibility() == ConfigurationKey::CUSTOMER ||
+				mp->second.getVisibility() == ConfigurationKey::CUSTOMERTUNE ||
+				mp->second.getVisibility() == ConfigurationKey::CUSTOMERWARN
+			)) {
+			ss << "	\\item ";
+				// name
+				ss << mp->first << " -- ";
+				// description
+				ss << mp->second.getDescription();
+			ss << endl;
+		}
+		mp++;
+	}
+	ss << "\\end{itemize}" << endl;
+	ss << endl;
+
+	ss << "\\subsection{Developer/Factory Parameters}" << endl;
+	ss << "These parameters should only be changed by when developing new code." << endl;
+	ss << "\\begin{itemize}" << endl;
+	mp = mSchema.begin();
+	while (mp != mSchema.end()) {
+		if (mp->second.getVisibility() == ConfigurationKey::FACTORY ||
+			mp->second.getVisibility() == ConfigurationKey::DEVELOPER) {
+			ss << "	\\item ";
+				// name
+				ss << mp->first << " -- ";
+				// description
+				ss << mp->second.getDescription();
+			ss << endl;
+		}
+		mp++;
+	}
+	ss << "\\end{itemize}" << endl;
+	ss << "% END AUTO-GENERATED CONTENT" << endl;
+	ss << endl;
+
+	string tmp = Utils::replaceAll(ss.str(), "^", "\\^");
+	return Utils::replaceAll(tmp, "_", "\\_");
+}
 
 bool ConfigurationTable::defines(const string& key)
 {
-	assert(mDB);
-	ScopedLock lock(mLock);
-
-	// Check the cache.
-	checkCacheAge();
-	ConfigurationMap::const_iterator where = mCache.find(key);
-	if (where!=mCache.end()) return where->second.defined();
-
-	// Check the database.
-	char *value = NULL;
-	sqlite3_single_lookup(mDB,"CONFIG","KEYSTRING",key.c_str(),"VALUESTRING",value);
-
-	// Cache the result.
-	if (value) {
-		mCache[key] = ConfigurationRecord(value);
-		free(value);
-		return true;
+	try {
+		ScopedLock lock(mLock);
+		return lookup(key).defined();
+	} catch (ConfigurationTableKeyNotFound) {
+		debugLogEarly(LOG_ALERT, "configuration parameter %s not found", key.c_str());
+		return false;
 	}
-	
-	mCache[key] = ConfigurationRecord(false);
-	return false;
 }
 
+bool ConfigurationTable::keyDefinedInSchema(const std::string& name)
+{
+	return mSchema.find(name) == mSchema.end() ? false : true;
+}
+
+bool ConfigurationTable::isValidValue(const std::string& name, const std::string& val) {
+	bool ret = false;
+
+	ConfigurationKey key = mSchema[name];
+
+	switch (key.getType()) {
+		case ConfigurationKey::BOOLEAN: {
+			if (val == "1" || val == "0") {
+				ret = true;
+			}
+			break;
+		}
+
+		case ConfigurationKey::CHOICE_OPT: {
+			if (val.length() == 0) {
+				ret = true;
+				break;
+			}
+		}
+		case ConfigurationKey::CHOICE: {
+			int startPos = -1;
+			uint endPos = 0;
+
+			std::string tmp = key.getValidValues();
+
+			do {
+				startPos++;
+				if ((endPos = tmp.find('|', startPos)) != std::string::npos) {
+					if (val == tmp.substr(startPos, endPos-startPos)) {
+						ret = true;
+						break;
+					}
+				} else {
+					if (val == tmp.substr(startPos, tmp.find(',', startPos)-startPos)) {
+						ret = true;
+						break;
+					}
+				}
+
+			} while ((startPos = tmp.find(',', startPos)) != (int)std::string::npos);
+			break;
+		}
+
+		case ConfigurationKey::CIDR_OPT: {
+			if (val.length() == 0) {
+				ret = true;
+				break;
+			}
+		}
+		case ConfigurationKey::CIDR: {
+			uint delimiter;
+			std::string ip;
+			int cidr = -1;
+
+			delimiter = val.find('/');
+			if (delimiter != std::string::npos) {
+				ip = val.substr(0, delimiter);
+				std::stringstream(val.substr(delimiter+1)) >> cidr;
+				if (ConfigurationKey::isValidIP(ip) && 0 <= cidr && cidr <= 32) {
+					ret = true;
+				}
+			}
+			break;
+		}
+
+		case ConfigurationKey::FILEPATH_OPT: {
+			if (val.length() == 0) {
+				ret = true;
+				break;
+			}
+		}
+		case ConfigurationKey::FILEPATH: {
+			regex_t r;
+			const char* expression = "^[a-zA-Z0-9/_.-]+$";
+			int result = regcomp(&r, expression, REG_EXTENDED);
+			if (result) {
+				char msg[256];
+				regerror(result,&r,msg,255);
+				break;//abort();
+			}
+			if (regexec(&r, val.c_str(), 0, NULL, 0)==0) {
+				ret = true;
+			}
+			regfree(&r);
+			break;
+		}
+
+		case ConfigurationKey::IPADDRESS_OPT: {
+			if (val.length() == 0) {
+				ret = true;
+				break;
+			}
+		}
+		case ConfigurationKey::IPADDRESS: {
+			ret = ConfigurationKey::isValidIP(val);
+			break;
+		}
+
+		case ConfigurationKey::IPANDPORT: {
+			uint delimiter;
+			std::string ip;
+			int port = -1;
+
+			delimiter = val.find(':');
+			if (delimiter != std::string::npos) {
+				ip = val.substr(0, delimiter);
+				std::stringstream(val.substr(delimiter+1)) >> port;
+				if (ConfigurationKey::isValidIP(ip) && 1 <= port && port <= 65535) {
+					ret = true;
+				}
+			}
+			break;
+		}
+
+		case ConfigurationKey::MIPADDRESS_OPT: {
+			if (val.length() == 0) {
+				ret = true;
+				break;
+			}
+		}
+		case ConfigurationKey::MIPADDRESS: {
+			int startPos = -1;
+			uint endPos = 0;
+
+			do {
+				startPos++;
+				endPos = val.find(' ', startPos);
+				if (ConfigurationKey::isValidIP(val.substr(startPos, endPos-startPos))) {
+					ret = true;
+				} else {
+					ret = false;
+					break;
+				}
+
+			} while ((startPos = endPos) != (int)std::string::npos);
+			break;
+		}
+
+		case ConfigurationKey::PORT_OPT: {
+			if (val.length() == 0) {
+				ret = true;
+				break;
+			}
+		}
+		case ConfigurationKey::PORT: {
+			int intVal;
+
+			std::stringstream(val) >> intVal;
+
+			if (1 <= intVal && intVal <= 65535) {
+				ret = true;
+			}
+			break;
+		}
+
+		case ConfigurationKey::REGEX_OPT: {
+			if (val.length() == 0) {
+				ret = true;
+				break;
+			}
+		}
+		case ConfigurationKey::REGEX: {
+			regex_t r;
+			const char* expression = val.c_str();
+			int result = regcomp(&r, expression, REG_EXTENDED);
+			if (result) {
+				char msg[256];
+				regerror(result,&r,msg,255);
+			} else {
+				ret = true;
+			}
+			regfree(&r);
+			break;
+		}
+
+		case ConfigurationKey::STRING_OPT: {
+			if (val.length() == 0) {
+				ret = true;
+				break;
+			}
+		}
+		case ConfigurationKey::STRING: {
+			regex_t r;
+			const char* expression = key.getValidValues().c_str();
+			int result = regcomp(&r, expression, REG_EXTENDED);
+			if (result) {
+				char msg[256];
+				regerror(result,&r,msg,255);
+				break;//abort();
+			}
+			if (regexec(&r, val.c_str(), 0, NULL, 0)==0) {
+				ret = true;
+			}
+			regfree(&r);
+			break;
+		}
+
+		case ConfigurationKey::VALRANGE: {
+			regex_t r;
+			int result;
+			if (key.getValidValues().find('.') != std::string::npos) {
+				result = regcomp(&r, "^[0-9.-]+$", REG_EXTENDED);
+			} else {
+				result = regcomp(&r, "^[0-9-]+$", REG_EXTENDED);
+			}
+			if (result) {
+				char msg[256];
+				regerror(result,&r,msg,255);
+				break;//abort();
+			}
+			if (regexec(&r, val.c_str(), 0, NULL, 0)!=0) {
+				ret = false;
+			} else if (key.getValidValues().find('.') != std::string::npos) {
+				ret = ConfigurationKey::isInValRange<float>(key, val, false);
+			} else {
+				ret = ConfigurationKey::isInValRange<int>(key, val, true);
+			}
+
+			regfree(&r);
+			break;
+		}
+	}
+
+	return ret;
+}
+
+ConfigurationKeyMap ConfigurationTable::getSimilarKeys(const std::string& snippet) {
+	ConfigurationKeyMap tmp;
+
+	ConfigurationKeyMap::const_iterator mp = mSchema.begin();
+	while (mp != mSchema.end()) {
+		if (mp->first.find(snippet) != std::string::npos) {
+			tmp[mp->first] = mp->second;
+		}
+		mp++;
+	}
+
+	return tmp;
+}
 
 const ConfigurationRecord& ConfigurationTable::lookup(const string& key)
 {
@@ -129,15 +540,18 @@
 	sqlite3_single_lookup(mDB,"CONFIG",
 			"KEYSTRING",key.c_str(),"VALUESTRING",value);
 
-	// Nothing defined?
-	if (!value) {
-		// Cache the failure.
+	// value found, cache the result
+	if (value) {
+		mCache[key] = ConfigurationRecord(value);
+	// key definition found, cache the default
+	} else if (keyDefinedInSchema(key)) {
+		mCache[key] = ConfigurationRecord(mSchema[key].getDefaultValue());
+	// total miss, cache the error
+	} else {
 		mCache[key] = ConfigurationRecord(false);
 		throw ConfigurationTableKeyNotFound(key);
 	}
 
-	// Cache the result.
-	mCache[key] = ConfigurationRecord(value);
 	free(value);
 
 	// Leave mLock locked.  The caller holds it still.
@@ -146,22 +560,13 @@
 
 
 
-bool ConfigurationTable::isStatic(const string& key) const
+bool ConfigurationTable::isStatic(const string& key)
 {
-	assert(mDB);
-	unsigned stat;
-	bool success = sqlite3_single_lookup(mDB,"CONFIG","KEYSTRING",key.c_str(),"STATIC",stat);
-	if (success) return (bool)stat;
-	return false;
-}
-
-bool ConfigurationTable::isRequired(const string& key) const
-{
-	assert(mDB);
-	unsigned optional;
-	bool success = sqlite3_single_lookup(mDB,"CONFIG","KEYSTRING",key.c_str(),"OPTIONAL",optional);
-	if (success) return !((bool)optional);
-	return false;
+	if (keyDefinedInSchema(key)) {
+		return mSchema[key].isStatic();
+	} else {
+		return false;
+	}
 }
 
 
@@ -175,30 +580,20 @@
 		return lookup(key).value();
 	} catch (ConfigurationTableKeyNotFound) {
 		// Raise an alert and re-throw the exception.
-		gLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
+		debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
 		throw ConfigurationTableKeyNotFound(key);
 	}
 }
 
-string ConfigurationTable::getStr(const string& key, const char* defaultValue)
-{
-	try {
-		ScopedLock lock(mLock);
-		return lookup(key).value();
-	} catch (ConfigurationTableKeyNotFound) {
-		gLogEarly(LOG_NOTICE, "deinfing missing parameter %s with value %s", key.c_str(),defaultValue);
-		set(key,defaultValue);
-		return string(defaultValue);
-	}
-}
-
 
 bool ConfigurationTable::getBool(const string& key)
 {
 	try {
 		return getNum(key) != 0;
 	} catch (ConfigurationTableKeyNotFound) {
-		return false;
+		// Raise an alert and re-throw the exception.
+		debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
+		throw ConfigurationTableKeyNotFound(key);
 	}
 }
 
@@ -211,30 +606,22 @@
 		return lookup(key).number();
 	} catch (ConfigurationTableKeyNotFound) {
 		// Raise an alert and re-throw the exception.
-		gLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
+		debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
 		throw ConfigurationTableKeyNotFound(key);
 	}
 }
 
 
-long ConfigurationTable::getNum(const string& key, long defaultValue)
-{
-	try {
-		ScopedLock lock(mLock);
-		return lookup(key).number();
-	} catch (ConfigurationTableKeyNotFound) {
-		gLogEarly(LOG_NOTICE, "deinfing missing parameter %s with value %ld", key.c_str(),defaultValue);
-		set(key,defaultValue);
-		return defaultValue;
-	}
-}
-
-
 float ConfigurationTable::getFloat(const string& key)
 {
-	// We need the lock because rec is a reference into the cache.
-	ScopedLock lock(mLock);
-	return lookup(key).floatNumber();
+	try {
+		ScopedLock lock(mLock);
+		return lookup(key).floatNumber();
+	} catch (ConfigurationTableKeyNotFound) {
+		// Raise an alert and re-throw the exception.
+		debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
+		throw ConfigurationTableKeyNotFound(key);
+	}
 }
 
 std::vector<string> ConfigurationTable::getVectorOfStrings(const string& key)
@@ -247,7 +634,7 @@
 		line = strdup(rec.value().c_str());
 	} catch (ConfigurationTableKeyNotFound) {
 		// Raise an alert and re-throw the exception.
-		gLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
+		debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
 		throw ConfigurationTableKeyNotFound(key);
 	}
 
@@ -268,17 +655,6 @@
 }
 
 
-std::vector<string> ConfigurationTable::getVectorOfStrings(const string& key, const char* defaultValue){
-	try {
-		return getVectorOfStrings(key);
-	} catch (ConfigurationTableKeyNotFound) {
-		set(key,defaultValue);
-		return getVectorOfStrings(key);
-	}
-}
-
-
-
 std::vector<unsigned> ConfigurationTable::getVector(const string& key)
 {
 	// Look up the string.
@@ -289,7 +665,7 @@
 		line = strdup(rec.value().c_str());
 	} catch (ConfigurationTableKeyNotFound) {
 		// Raise an alert and re-throw the exception.
-		gLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
+		debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
 		throw ConfigurationTableKeyNotFound(key);
 	}
 
@@ -310,25 +686,9 @@
 }
 
 
-bool ConfigurationTable::unset(const string& key)
-{
-	assert(mDB);
-	if (!defines(key)) return true;
-	if (isRequired(key)) return false;
-
-	ScopedLock lock(mLock);
-	// Clear the cache entry and the database.
-	ConfigurationMap::iterator where = mCache.find(key);
-	if (where!=mCache.end()) mCache.erase(where);
-	// Don't delete it; just set VALUESTRING to NULL.
-	string cmd = "UPDATE CONFIG SET VALUESTRING=NULL WHERE KEYSTRING=='"+key+"'";
-	return sqlite3_command(mDB,cmd.c_str());
-}
-
 bool ConfigurationTable::remove(const string& key)
 {
 	assert(mDB);
-	if (isRequired(key)) return false;
 
 	ScopedLock lock(mLock);
 	// Clear the cache entry and the database.
@@ -352,14 +712,43 @@
 	while (src==SQLITE_ROW) {
 		const char* value = (const char*)sqlite3_column_text(stmt,1);
 		os << sqlite3_column_text(stmt,0) << " ";
-		if (value) os << value << endl;
-		else os << "(null)" << endl;
+		int len = 0;
+		if (value) {
+			len = strlen(value);
+		}
+		if (len && value) os << value << endl;
+		else os << "(disabled)" << endl;
 		src = sqlite3_run_query(mDB,stmt);
 	}
 	sqlite3_finalize(stmt);
 }
 
 
+ConfigurationRecordMap ConfigurationTable::getAllPairs() const
+{
+	ConfigurationRecordMap tmp;
+
+	// Prepare the statement.
+	string cmd = "SELECT KEYSTRING,VALUESTRING FROM CONFIG";
+	sqlite3_stmt *stmt;
+	if (sqlite3_prepare_statement(mDB,&stmt,cmd.c_str())) return tmp;
+	// Read the result.
+	int src = sqlite3_run_query(mDB,stmt);
+	while (src==SQLITE_ROW) {
+		const char* key = (const char*)sqlite3_column_text(stmt,0);
+		const char* value = (const char*)sqlite3_column_text(stmt,1);
+		if (key && value) {
+			tmp[string(key)] = ConfigurationRecord(value);
+		} else if (key && !value) {
+			tmp[string(key)] = ConfigurationRecord(false);
+		}
+		src = sqlite3_run_query(mDB,stmt);
+	}
+	sqlite3_finalize(stmt);
+
+	return tmp;
+}
+
 bool ConfigurationTable::set(const string& key, const string& value)
 {
 	assert(mDB);
@@ -428,6 +817,21 @@
 }
 
 
+void ConfigurationTable::setCrossCheckHook(vector<string> (*wCrossCheck)(const string&))
+{
+	mCrossCheck = wCrossCheck;
+}
+
+
+vector<string> ConfigurationTable::crossCheck(const string& key) {
+	vector<string> ret;
+
+	if (mCrossCheck != NULL) {
+		ret = mCrossCheck(key);
+	}
+
+	return ret;
+}
 
 void HashString::computeHash()
 {
@@ -474,5 +878,277 @@
 }
 
 
+bool ConfigurationKey::isValidIP(const std::string& ip) {
+	struct sockaddr_in sa;
+	return inet_pton(AF_INET, ip.c_str(), &(sa.sin_addr)) != 0;
+}
+
+
+void ConfigurationKey::getMinMaxStepping(const ConfigurationKey &key, std::string &min, std::string &max, std::string &stepping) {
+	uint delimiter;
+	int startPos;
+	uint endPos;
+
+	std::string tmp = key.getValidValues();
+	stepping = "1";
+
+	// grab steps if they're defined
+	startPos = tmp.find('(');
+	if (startPos != (int)std::string::npos) {
+		endPos = tmp.find(')');
+		stepping = tmp.substr(startPos+1, endPos-startPos-1);
+		tmp = tmp.substr(0, startPos);
+	}
+	startPos = 0;
+
+	delimiter = tmp.find(':', startPos);
+	min = tmp.substr(startPos, delimiter-startPos);
+	max = tmp.substr(delimiter+1, tmp.find(',', delimiter)-delimiter-1);
+}
+
+
+template<class T> bool ConfigurationKey::isInValRange(const ConfigurationKey &key, const std::string& val, const bool isInteger) {
+	bool ret = false;
+
+	T convVal;
+	T min;
+	T max;
+	T steps;
+	std::string strMin;
+	std::string strMax;
+	std::string strSteps;
+
+	std::stringstream(val) >> convVal;
+
+	ConfigurationKey::getMinMaxStepping(key, strMin, strMax, strSteps);
+	std::stringstream(strMin) >> min;
+	std::stringstream(strMax) >> max;
+	std::stringstream(strSteps) >> steps;
+
+	// TODO : only ranges checked, steps not enforced
+	if (isInteger) {
+		if (val.find('.') == std::string::npos && min <= convVal && convVal <= max) {
+			ret = true;
+		}
+	} else {
+		if (min <= convVal && convVal <= max) {
+			ret = true;
+		}
+	}
+
+	return ret;
+}
+
+const std::string ConfigurationKey::getARFCNsString() {
+	stringstream ss;
+	int i;
+	float downlink;
+	float uplink;
+
+	// 128:251 GSM850
+	downlink = 869.2;
+	uplink = 824.2;
+	for (i = 128; i <= 251; i++) {
+		ss << i << "|GSM850 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,";
+		downlink += 0.2;
+		uplink += 0.2;
+	}
+
+	// 1:124 PGSM900
+	downlink = 935.2;
+	uplink = 890.2;
+	for (i = 1; i <= 124; i++) {
+		ss << i << "|PGSM900 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,";
+		downlink += 0.2;
+		uplink += 0.2;
+	}
+
+	// 512:885 DCS1800
+	downlink = 1805.2;
+	uplink = 1710.2;
+	for (i = 512; i <= 885; i++) {
+		ss << i << "|DCS1800 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,";
+		downlink += 0.2;
+		uplink += 0.2;
+	}
+
+	// 512:810 PCS1900
+	downlink = 1930.2;
+	uplink = 1850.2;
+	for (i = 512; i <= 810; i++) {
+		ss << i << "|PCS1900 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,";
+		downlink += 0.2;
+		uplink += 0.2;
+	}
+
+	ss << endl;
+
+	return ss.str();
+}
+
+const std::string ConfigurationKey::visibilityLevelToString(const ConfigurationKey::VisibilityLevel& visibility) {
+	std::string ret = "UNKNOWN ERROR";
+
+	switch (visibility) {
+		case ConfigurationKey::CUSTOMER:
+			ret = "customer - can be freely changed by the customer without any detriment to their system";
+			break;
+		case ConfigurationKey::CUSTOMERSITE:
+			ret = "customer site - these values are different for each BTS and should not be left default";
+			break;
+		case ConfigurationKey::CUSTOMERTUNE:
+			ret = "customer tune - should only be changed to tune an installation to better suit the physical environment or MS usage pattern";
+			break;
+		case ConfigurationKey::CUSTOMERWARN:
+			ret = "customer warn - a warning will be presented and confirmation required before changing this sensitive setting";
+			break;
+		case ConfigurationKey::DEVELOPER:
+			ret = "developer - should only be changed by developers to debug/optimize the implementation";
+			break;
+		case ConfigurationKey::FACTORY:
+			ret = "factory - set once at the factory, should never be changed";
+			break;
+	}
+
+	return ret;
+}
+
+const std::string ConfigurationKey::typeToString(const ConfigurationKey::Type& type) {
+	std::string ret = "UNKNOWN ERROR";
+
+	switch (type) {
+		case BOOLEAN:
+			ret = "boolean";
+			break;
+		case CHOICE_OPT:
+			ret = "multiple choice (optional)";
+			break;
+		case CHOICE:
+			ret = "multiple choice";
+			break;
+		case CIDR_OPT:
+			ret = "CIDR notation (optional)";
+			break;
+		case CIDR:
+			ret = "CIDR notation";
+			break;
+		case FILEPATH_OPT:
+			ret = "file path (optional)";
+			break;
+		case FILEPATH:
+			ret = "file path";
+			break;
+		case IPADDRESS_OPT:
+			ret = "IP address (optional)";
+			break;
+		case IPADDRESS:
+			ret = "IP address";
+			break;
+		case IPANDPORT:
+			ret = "IP address and port";
+			break;
+		case MIPADDRESS_OPT:
+			ret = "space-separated list of IP addresses (optional)";
+			break;
+		case MIPADDRESS:
+			ret = "space-separated list of IP addresses";
+			break;
+		case PORT_OPT:
+			ret = "IP port (optional)";
+			break;
+		case PORT:
+			ret = "IP port";
+			break;
+		case REGEX_OPT:
+			ret = "regular expression (optional)";
+			break;
+		case REGEX:
+			ret = "regular expression";
+			break;
+		case STRING_OPT:
+			ret = "string (optional)";
+			break;
+		case STRING:
+			ret = "string";
+			break;
+		case VALRANGE:
+			ret = "value range";
+			break;
+	}
+
+	return ret;
+}
+
+void ConfigurationKey::printKey(const ConfigurationKey &key, const std::string& currentValue, ostream& os) {
+	os << key.getName() << " ";
+	if (!currentValue.length()) {
+		os << "(disabled)";
+	} else {
+		os << currentValue;
+	}
+	if (currentValue.compare(key.getDefaultValue()) == 0) {
+		os << "     [default]";
+	}
+	os << endl;
+}
+
+void ConfigurationKey::printDescription(const ConfigurationKey &key, ostream& os) {
+	std::string tmp;
+
+	os << " - description:      " << key.getDescription() << std::endl;
+	if (key.getUnits().length()) {
+		os << " - units:            " << key.getUnits() << std::endl;
+	}
+	os << " - type:             " << ConfigurationKey::typeToString(key.getType()) << std::endl;
+	if (key.getDefaultValue().length()) {
+		os << " - default value:    " << key.getDefaultValue() << std::endl;
+	}
+	os << " - visibility level: " << ConfigurationKey::visibilityLevelToString(key.getVisibility()) << std::endl;
+	os << " - static:           " << key.isStatic() << std::endl;
+
+	tmp = key.getValidValues();
+	if (key.getType() == ConfigurationKey::VALRANGE) {
+		int startPos = tmp.find('(');
+		uint delimiter = 0;
+		if (startPos != (int)std::string::npos) {
+			tmp = tmp.substr(0, startPos);
+		}
+		startPos = -1;
+
+		do {
+			startPos++;
+			delimiter = tmp.find(':', startPos);
+			os << " - valid values:     " << "from " << tmp.substr(startPos, delimiter-startPos) << " to "
+				<< tmp.substr(delimiter+1, tmp.find(',', delimiter)-delimiter-1) << std::endl;
+
+		} while ((startPos = tmp.find(',', startPos)) != (int)std::string::npos);
+
+	} else if (key.getType() == ConfigurationKey::CHOICE) {
+		int startPos = -1;
+		uint endPos = 0;
+
+		do {
+			startPos++;
+			if ((endPos = tmp.find('|', startPos)) != std::string::npos) {
+				os << " - valid values:     " << tmp.substr(startPos, endPos-startPos);
+				os << " = " << tmp.substr(endPos+1, tmp.find(',', endPos)-endPos-1) << std::endl;
+			} else {
+				os << " - valid values:     " << tmp.substr(startPos, tmp.find(',', startPos)-startPos) << std::endl;
+			}
+
+		} while ((startPos = tmp.find(',', startPos)) != (int)std::string::npos);
+
+	} else if (key.getType() == ConfigurationKey::BOOLEAN) {
+		os << " - valid values:     0 = disabled" << std::endl;
+		os << " - valid values:     1 = enabled" << std::endl;
+
+	} else if (key.getType() == ConfigurationKey::STRING) {
+		os << " - valid val regex:  " << tmp << std::endl;
+
+	} else if (key.getValidValues().length()) {
+		os << " - raw valid values: " << tmp << std::endl;
+	}
+}
+
 
 // vim: ts=4 sw=4