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/BitVector.cpp b/CommonLibs/BitVector.cpp
index 54a3edc..7487834 100644
--- a/CommonLibs/BitVector.cpp
+++ b/CommonLibs/BitVector.cpp
@@ -29,6 +29,7 @@
 #include "BitVector.h"
 #include <iostream>
 #include <stdio.h>
+#include <sstream>
 
 using namespace std;
 
@@ -274,9 +275,6 @@
 
 
 
-
-
-
 ostream& operator<<(ostream& os, const BitVector& hv)
 {
 	for (size_t i=0; i<hv.size(); i++) {
@@ -527,6 +525,22 @@
 
 
 
+// (pat) Added 6-22-2012
+float SoftVector::getEnergy(float *plow) const
+{
+	const SoftVector &vec = *this;
+	int len = vec.size();
+	float avg = 0; float low = 1;
+	for (int i = 0; i < len; i++) {
+		float bit = vec[i];
+		float energy = 2*((bit < 0.5) ? (0.5-bit) : (bit-0.5));
+		if (energy < low) low = energy;
+		avg += energy/len;
+	}
+	if (plow) { *plow = low; }
+	return avg;
+}
+
 
 ostream& operator<<(ostream& os, const SoftVector& sv)
 {
@@ -578,6 +592,14 @@
 	os << std::dec;
 }
 
+std::string BitVector::hexstr() const
+{
+	std::ostringstream ss;
+	hex(ss);
+	return ss.str();
+}
+
+
 bool BitVector::unhex(const char* src)
 {
 	// Assumes MSB-first packing.
diff --git a/CommonLibs/BitVector.h b/CommonLibs/BitVector.h
index 572e6b4..e244be7 100644
--- a/CommonLibs/BitVector.h
+++ b/CommonLibs/BitVector.h
@@ -314,6 +314,9 @@
 	void fillFieldReversed(size_t writeIndex, uint64_t value, unsigned length);
 	void writeField(size_t& writeIndex, uint64_t value, unsigned length);
 	void writeFieldReversed(size_t& writeIndex, uint64_t value, unsigned length);
+	void write0(size_t& writeIndex) { writeField(writeIndex,0,1); }
+	void write1(size_t& writeIndex) { writeField(writeIndex,1,1); }
+
 	//@}
 
 	/** Sum of bits. */
@@ -333,11 +336,26 @@
 
 	/** Make a hexdump string. */
 	void hex(std::ostream&) const;
+	std::string hexstr() const;
 
 	/** Unpack from a hexdump string.
 	*  @returns true on success, false on error. */
 	bool unhex(const char*);
 
+	void set(BitVector other)	// That's right.  No ampersand.
+	{
+		clear();
+		mData=other.mData;
+		mStart=other.mStart;
+		mEnd=other.mEnd;
+		other.mData=NULL;
+	}
+
+	void settfb(int i, int j) const
+	{
+		mStart[i] = j;
+	}
+
 };
 
 
@@ -412,6 +430,11 @@
 	/** Decode soft symbols with the GSM rate-1/2 Viterbi decoder. */
 	void decode(ViterbiR2O4 &decoder, BitVector& target) const;
 
+	// (pat) How good is the SoftVector in the sense of the bits being solid?
+	// Result of 1 is perfect and 0 means all the bits were 0.5
+	// If plow is non-NULL, also return the lowest energy bit.
+	float getEnergy(float *low=0) const;
+
 	/** Fill with "unknown" values. */
 	void unknown() { fill(0.5F); }
 
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
diff --git a/CommonLibs/Configuration.h b/CommonLibs/Configuration.h
index bc37a49..cd4838e 100644
--- a/CommonLibs/Configuration.h
+++ b/CommonLibs/Configuration.h
@@ -33,10 +33,14 @@
 
 #include <assert.h>
 #include <stdlib.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <regex.h>
 
 #include <map>
 #include <vector>
 #include <string>
+#include <sstream>
 #include <iostream>
 
 #include <Threads.h>
@@ -165,8 +169,10 @@
 };
 
 
+typedef std::map<std::string, ConfigurationRecord> ConfigurationRecordMap;
 typedef std::map<HashString, ConfigurationRecord> ConfigurationMap;
-
+class ConfigurationKey;
+typedef std::map<std::string, ConfigurationKey> ConfigurationKeyMap;
 
 /**
 	A class for maintaining a configuration key-value table,
@@ -180,20 +186,37 @@
 	sqlite3* mDB;				///< database connection
 	ConfigurationMap mCache;	///< cache of recently access configuration values
 	mutable Mutex mLock;		///< control for multithreaded access to the cache
+	std::vector<std::string> (*mCrossCheck)(const std::string&);	///< cross check callback pointer
 
 	public:
 
+	ConfigurationKeyMap mSchema;///< definition of configuration default values and validation logic
 
-	ConfigurationTable(const char* filename = ":memory:", const char *wCmdName = 0);
+	ConfigurationTable(const char* filename = ":memory:", const char *wCmdName = 0, ConfigurationKeyMap wSchema = ConfigurationKeyMap());
+
+	/** Generate an up-to-date example sql file for new installs. */
+	std::string getDefaultSQL(const std::string& program, const std::string& version);
+
+	/** Generate an up-to-date TeX snippet. */
+	std::string getTeX(const std::string& program, const std::string& version);
 
 	/** Return true if the key is used in the table.  */
 	bool defines(const std::string& key);
 
-	/** Return true if this key is identified as static. */
-	bool isStatic(const std::string& key) const;
+	/** Return true if the application's schema knows about this key. */
+	bool keyDefinedInSchema(const std::string& name);
 
-	/** Return true if this key is identified as required (!optional). */
-	bool isRequired(const std::string& key) const;
+	/** Return true if the provided value validates correctly against the defined schema. */
+	bool isValidValue(const std::string& name, const std::string& val);
+
+	/** Return true if the provided value validates correctly against the defined schema. */
+	bool isValidValue(const std::string& name, const int val) { std::stringstream ss; ss << val; return isValidValue(name, ss.str()); }
+
+	/** Return a map of all similar keys in the defined schema. */
+	ConfigurationKeyMap getSimilarKeys(const std::string& snippet);
+
+	/** Return true if this key is identified as static. */
+	bool isStatic(const std::string& key);
 
 	/**
 		Get a string parameter from the table.
@@ -203,11 +226,10 @@
 
 
 	/**
-		Get a string parameter from the table.
-		Define the parameter to the default value if not found.
+		Get a boolean from the table.
+		Return false if NULL or 0, true otherwise.
 	*/
-	std::string getStr(const std::string& key, const char* defaultValue);
-
+	bool getBool(const std::string& key);
 
 	/**
 		Get a numeric parameter from the table.
@@ -216,28 +238,11 @@
 	long getNum(const std::string& key);
 
 	/**
-		Get a boolean from the table.
-		Return false if NULL or 0, true otherwise.
-	*/
-	bool getBool(const std::string& key);
-
-	/**
-		Get a numeric parameter from the table.
-		Define the parameter to the default value if not found.
-	*/
-	long getNum(const std::string& key, long defaultValue);
-
-	/**
 		Get a vector of strings from the table.
 	*/
 	std::vector<std::string> getVectorOfStrings(const std::string& key);
 
 	/**
-		Get a vector of strings from the table, with a default value..
-	*/
-	std::vector<std::string> getVectorOfStrings(const std::string& key, const char* defaultValue);
-
-	/**
 		Get a float from the table.
 		Throw ConfigurationTableKeyNotFound if not found.
 	*/
@@ -262,14 +267,6 @@
 	bool set(const std::string& key);
 
 	/**
-		Set a corresponding value to NULL.
-		Will not alter required values.
-		@param key The key of the item to be nulled-out.
-		@return true if anything was actually nulled-out.
-	*/
-	bool unset(const std::string& key);
-
-	/**
 		Remove an entry from the table.
 		Will not alter required values.
 		@param key The key of the item to be removed.
@@ -280,9 +277,18 @@
 	/** Search the table, dumping to a stream. */
 	void find(const std::string& pattern, std::ostream&) const;
 
+	/** Return all key/value pairs stored in the ConfigurationTable */
+	ConfigurationRecordMap getAllPairs() const;
+
 	/** Define the callback to purge the cache whenever the database changes. */
 	void setUpdateHook(void(*)(void *,int ,char const *,char const *,sqlite3_int64));
 
+	/** Define the callback for cross checking. */
+	void setCrossCheckHook(std::vector<std::string> (*wCrossCheck)(const std::string&));
+
+	/** Execute the application specific value cross checking logic. */
+	std::vector<std::string> crossCheck(const std::string& key);
+
 	/** purege cache if it exceeds a certain age */
 	void checkCacheAge();
 
@@ -323,6 +329,92 @@
 };
 
 
+class ConfigurationKey {
+
+	public:
+
+	enum VisibilityLevel
+	{
+		CUSTOMER,
+		CUSTOMERSITE,
+		CUSTOMERTUNE,
+		CUSTOMERWARN,
+		DEVELOPER,
+		FACTORY
+	};
+
+	enum Type
+	{
+		BOOLEAN,
+		CHOICE_OPT,
+		CHOICE,
+		CIDR_OPT,
+		CIDR,
+		FILEPATH_OPT,
+		FILEPATH,
+		IPADDRESS_OPT,
+		IPADDRESS,
+		IPANDPORT,
+		MIPADDRESS_OPT,
+		MIPADDRESS,
+		PORT_OPT,
+		PORT,
+		REGEX_OPT,
+		REGEX,
+		STRING_OPT,
+		STRING,
+		VALRANGE
+	};
+
+	private:
+
+	std::string mName;
+	std::string mDefaultValue;
+	std::string mUnits;
+	VisibilityLevel mVisibility;
+	Type mType;
+	std::string mValidValues;
+	bool mIsStatic;
+	std::string mDescription;
+
+
+	public:
+
+	ConfigurationKey(const std::string& wName, const std::string& wDefaultValue, const std::string& wUnits, const VisibilityLevel wVisibility, const Type wType, const std::string& wValidValues, bool wIsStatic, const std::string& wDescription):
+		mName(wName),
+		mDefaultValue(wDefaultValue),
+		mUnits(wUnits),
+		mVisibility(wVisibility),
+		mType(wType),
+		mValidValues(wValidValues),
+		mIsStatic(wIsStatic),
+		mDescription(wDescription)
+	{ }
+
+	ConfigurationKey()
+	{ }
+
+	const std::string& getName() const { return mName; }
+	const std::string& getDefaultValue() const { return mDefaultValue; }
+	void updateDefaultValue(const std::string& newValue) { mDefaultValue = newValue; }
+	void updateDefaultValue(const int newValue) { std::stringstream ss; ss << newValue; updateDefaultValue(ss.str()); }
+	const std::string& getUnits() const { return mUnits; }
+	const VisibilityLevel& getVisibility() const { return mVisibility; }
+	const Type& getType() const { return mType; }
+	const std::string& getValidValues() const { return mValidValues; }
+	bool isStatic() const { return mIsStatic; }
+	const std::string& getDescription() const { return mDescription; }
+
+	static bool isValidIP(const std::string& ip);
+	static void getMinMaxStepping(const ConfigurationKey &key, std::string &min, std::string &max, std::string &stepping);
+	template<class T> static bool isInValRange(const ConfigurationKey &key, const std::string& val, const bool isInteger);
+	static const std::string visibilityLevelToString(const VisibilityLevel& visibility);
+	static const std::string typeToString(const ConfigurationKey::Type& type);
+	static void printKey(const ConfigurationKey &key, const std::string& currentValue, std::ostream& os);
+	static void printDescription(const ConfigurationKey &key, std::ostream& os);
+	static const std::string getARFCNsString();
+};
+
 
 #endif
 
diff --git a/CommonLibs/ConfigurationTest.cpp b/CommonLibs/ConfigurationTest.cpp
index 3a2045f..2fd43e9 100644
--- a/CommonLibs/ConfigurationTest.cpp
+++ b/CommonLibs/ConfigurationTest.cpp
@@ -32,7 +32,8 @@
 
 using namespace std;
 
-ConfigurationTable gConfig("exampleconfig.db","test");
+ConfigurationKeyMap getConfigurationKeys();
+ConfigurationTable gConfig("exampleconfig.db","test", getConfigurationKeys());
 
 void purgeConfig(void*,int,char const*, char const*, sqlite3_int64)
 {
@@ -46,7 +47,7 @@
 
 	gConfig.setUpdateHook(purgeConfig);
 
-	const char *keys[5] = {"key1", "key2", "key3", "key4", "key5"};
+	char *keys[5] = {"key1", "key2", "key3", "key4", "key5"};
 
 	for (int i=0; i<5; i++) {
 		gConfig.set(keys[i],i);
@@ -57,7 +58,6 @@
 		cout << "table[" << keys[i] << "]=" << gConfig.getNum(keys[i]) <<  endl;
 	}
 
-	gConfig.unset("key1");
 	for (int i=0; i<5; i++) {
 		cout << "defined table[" << keys[i] << "]=" << gConfig.defines(keys[i]) <<  endl;
 	}
@@ -78,8 +78,8 @@
 	gConfig.set("booltest",0);
 	cout << "bool " << gConfig.getBool("booltest") << endl;
 
-	gConfig.getStr("newstring","new string value");
-	gConfig.getNum("numnumber",42);
+	gConfig.getStr("newstring");
+	gConfig.getNum("numnumber");
 
 
 	SimpleKeyValue pairs;
@@ -94,7 +94,6 @@
 
 	cout << "search fkey:" << endl;
 	gConfig.find("fkey",cout);
-	gConfig.unset("fkey");
 	cout << "search fkey:" << endl;
 	gConfig.find("fkey",cout);
 	gConfig.remove("fkey");
@@ -107,3 +106,44 @@
 		cout << "ConfigurationTableKeyNotFound exception successfully caught." << endl;
 	}
 }
+
+ConfigurationKeyMap getConfigurationKeys()
+{
+	ConfigurationKeyMap map;
+	ConfigurationKey *tmp;
+
+	tmp = new ConfigurationKey("booltest","0",
+		"",
+		ConfigurationKey::DEVELOPER,
+		ConfigurationKey::BOOLEAN,
+		"",
+		false,
+		""
+	);
+	map[tmp->getName()] = *tmp;
+	free(tmp);
+
+	tmp = new ConfigurationKey("numnumber","42",
+		"",
+		ConfigurationKey::DEVELOPER,
+		ConfigurationKey::VALRANGE,
+		"0-100",
+		false,
+		""
+	);
+	map[tmp->getName()] = *tmp;
+	free(tmp);
+
+	tmp = new ConfigurationKey("newstring","new string value",
+		"",
+		ConfigurationKey::DEVELOPER,
+		ConfigurationKey::STRING,
+		"",
+		false,
+		""
+	);
+	map[tmp->getName()] = *tmp;
+	free(tmp);
+
+	return map;
+}
diff --git a/CommonLibs/Interthread.h b/CommonLibs/Interthread.h
index 023ac14..42e6f7f 100644
--- a/CommonLibs/Interthread.h
+++ b/CommonLibs/Interthread.h
@@ -42,15 +42,21 @@
 
 
 /** Pointer FIFO for interthread operations.  */
-template <class T> class InterthreadQueue {
+// (pat) The elements in the queue are type T*, and
+// the Fifo class implements the underlying queue.
+// The default is class PointerFIFO, which does not place any restrictions on the type of T,
+// and is implemented by allocating auxilliary structures for the queue,
+// or SingleLinkedList, which implements the queue using an internal pointer in type T,
+// which must implement the functional interface of class SingleLinkListNode,
+// namely: functions T*next() and void setNext(T*).
+template <class T, class Fifo=PointerFIFO> class InterthreadQueue {
 
 	protected:
 
-	PointerFIFO mQ;	
+	Fifo mQ;	
 	mutable Mutex mLock;
 	mutable Signal mWriteSignal;
 
-
 	public:
 
 	/** Delete contents. */
@@ -78,6 +84,12 @@
 		return mQ.size();
 	}
 
+	size_t totalSize() const		// pat added
+	{
+		ScopedLock lock(mLock);
+		return mQ.totalSize();
+	}
+
 	/**
 		Blocking read.
 		@return Pointer to object (will not be NULL).
@@ -93,6 +105,13 @@
 		return retVal;
 	}
 
+	/** Non-blocking peek at the first element; returns NULL if empty. */
+	T* front()
+	{
+		ScopedLock lock(mLock);
+		return (T*) mQ.front();
+	}
+
 	/**
 		Blocking read with a timeout.
 		@param timeout The read timeout in ms.
@@ -127,7 +146,132 @@
 		mWriteSignal.signal();
 	}
 
+	/** Non-block write to the front of the queue. */
+	void write_front(T* val)	// pat added
+	{
+		ScopedLock lock(mLock);
+		mQ.push_front(val);
+		mWriteSignal.signal();
+	}
+};
 
+// (pat) Identical to above but with the threading problem fixed.
+template <class T, class Fifo=PointerFIFO> class InterthreadQueue2 {
+
+	protected:
+
+	Fifo mQ;	
+	mutable Mutex mLock;
+	mutable Signal mWriteSignal;
+
+	public:
+
+	/** Delete contents. */
+	void clear()
+	{
+		ScopedLock lock(mLock);
+		while (mQ.size()>0) delete (T*)mQ.get();
+	}
+
+	/** Empty the queue, but don't delete. */
+	void flushNoDelete()
+	{
+		ScopedLock lock(mLock);
+		while (mQ.size()>0) mQ.get();
+	}
+
+
+	~InterthreadQueue2()
+		{ clear(); }
+
+
+	size_t size() const
+	{
+		ScopedLock lock(mLock);
+		return mQ.size();
+	}
+
+	size_t totalSize() const		// pat added
+	{
+		ScopedLock lock(mLock);
+		return mQ.totalSize();
+	}
+
+	/**
+		Blocking read.
+		@return Pointer to object (will not be NULL).
+	*/
+	T* read()
+	{
+		ScopedLock lock(mLock);
+		T* retVal = (T*)mQ.get();
+		while (retVal==NULL) {
+			mWriteSignal.wait(mLock);
+			retVal = (T*)mQ.get();
+		}
+		return retVal;
+	}
+
+	/** Non-blocking peek at the first element; returns NULL if empty. */
+	T* front()
+	{
+		ScopedLock lock(mLock);
+		return (T*) mQ.front();
+	}
+
+	/**
+		Blocking read with a timeout.
+		@param timeout The read timeout in ms.
+		@return Pointer to object or NULL on timeout.
+	*/
+	T* read(unsigned timeout)
+	{
+		if (timeout==0) return readNoBlock();
+		Timeval waitTime(timeout);
+		ScopedLock lock(mLock);
+		while ((mQ.size()==0) && (!waitTime.passed()))
+			mWriteSignal.wait(mLock,waitTime.remaining());
+		T* retVal = (T*)mQ.get();
+		return retVal;
+	}
+
+	/**
+		Non-blocking read.
+		@return Pointer to object or NULL if FIFO is empty.
+	*/
+	T* readNoBlock()
+	{
+		ScopedLock lock(mLock);
+		return (T*)mQ.get();
+	}
+
+	/** Non-blocking write. */
+	void write(T* val)
+	{
+		// (pat) The Mutex mLock must be released before signaling the mWriteSignal condition.
+		// This is an implicit requirement of pthread_cond_wait() called from signal().
+		// If you do not do that, the InterthreadQueue read() function cannot start
+		// because the mutex is still locked by the thread calling the write(),
+		// so the read() thread yields its immediate execution opportunity.
+		// This recurs (and the InterthreadQueue fills up with data)
+		// until the read thread's accumulated temporary priority causes it to
+		// get a second pre-emptive activation over the writing thread,
+		// resulting in bursts of activity by the read thread. 
+		{ ScopedLock lock(mLock);
+		  mQ.put(val);
+		}
+		mWriteSignal.signal();
+	}
+
+	/** Non-block write to the front of the queue. */
+	void write_front(T* val)	// pat added
+	{
+		// (pat) See comments above.
+		{ ScopedLock lock(mLock);
+		  mQ.push_front(val);
+		}
+		mWriteSignal.signal();
+	}
 };
 
 
@@ -214,12 +358,17 @@
 	/** Non-blocking write. */
 	void write(T* val)
 	{
+		// (pat) 8-14: Taking out the threading problem fix temporarily for David to use in the field.
 		ScopedLock lock(mLock);
 		mQ.put(val);
 		mWriteSignal.signal();
 	}
 
 	/** Wait until the queue falls below a low water mark. */
+	// (pat) This function suffers from the same problem as documented
+	// at InterthreadQueue.write(), but I am not fixing it because I cannot test it.
+	// The caller of this function will eventually get to run, just not immediately
+	// after the mReadSignal condition is fulfilled.
 	void wait(size_t sz=0)
 	{
 		ScopedLock lock(mLock);
@@ -484,6 +633,7 @@
 	/** Non-blocking write. */
 	void write(T* val)
 	{
+		// (pat) 8-14: Taking out the threading problem fix temporarily for David to use in the field.
 		ScopedLock lock(mLock);
 		mQ.push(val);
 		mWriteSignal.signal();
diff --git a/CommonLibs/InterthreadTest.cpp b/CommonLibs/InterthreadTest.cpp
index 1712689..03445d9 100644
--- a/CommonLibs/InterthreadTest.cpp
+++ b/CommonLibs/InterthreadTest.cpp
@@ -82,7 +82,8 @@
 	for (int i=0; i<20; i++) {
 		int *p = gMap.read(i);
 		COUT("map read " << *p);
-		delete p;
+		// InterthreadMap will delete the pointers
+		// delete p;
 	}
 	return NULL;
 }
diff --git a/CommonLibs/LinkedLists.cpp b/CommonLibs/LinkedLists.cpp
index ba0f0cc..35a8541 100644
--- a/CommonLibs/LinkedLists.cpp
+++ b/CommonLibs/LinkedLists.cpp
@@ -29,7 +29,17 @@
 #include "LinkedLists.h"
 
 
-
+void PointerFIFO::push_front(void* val)	// by pat
+{
+	// Pat added this routine for completeness, but never used or tested.
+	// The first person to use this routine should remove this assert.
+	ListNode *node = allocate();
+	node->data(val);
+	node->next(mHead);
+	mHead = node;
+	if (!mTail) mTail=node;
+	mSize++;
+}
 
 void PointerFIFO::put(void* val)
 {
@@ -58,7 +68,6 @@
 }
 
 
-
 ListNode *PointerFIFO::allocate()
 {
 	if (mFreeList==NULL) return new ListNode;
@@ -72,6 +81,3 @@
 	wNode->next(mFreeList);
 	mFreeList = wNode;
 }
-
-
-
diff --git a/CommonLibs/LinkedLists.h b/CommonLibs/LinkedLists.h
index 2cb83ee..31fb9c5 100644
--- a/CommonLibs/LinkedLists.h
+++ b/CommonLibs/LinkedLists.h
@@ -1,6 +1,8 @@
 /*
 * Copyright 2008 Free Software Foundation, Inc.
 *
+* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
+*
 * This software is distributed under the terms of the GNU Affero Public License.
 * See the COPYING file in the main directory for details.
 *
@@ -28,6 +30,7 @@
 #define LINKEDLISTS_H
 
 #include <stdlib.h>
+#include <assert.h>
 
 
 
@@ -54,7 +57,7 @@
 /** A fast FIFO for pointer-based storage. */
 class PointerFIFO {
 
-	private:
+	protected:
 
 	ListNode* mHead;		///< points to next item out
 	ListNode* mTail;		///< points to last item in
@@ -69,9 +72,12 @@
 	{}
 
 	unsigned size() const { return mSize; }
+	unsigned totalSize() const { return 0; }	// Not used in this version.
 
-	/** Put an item into the FIFO. */
+	/** Put an item into the FIFO at the back of the queue. */
 	void put(void* val);
+	/** Push an item on the front of the FIFO. */
+	void push_front(void*val);			// pat added.
 
 	/**
 		Take an item from the FIFO.
@@ -79,6 +85,9 @@
 	*/
 	void* get();
 
+	/** Peek at front item without removal. */
+	void *front() { return mHead ? mHead->data() : 0; }	// pat added
+
 
 	private:
 
@@ -90,6 +99,74 @@
 
 };
 
+// This is the default type for SingleLinkList Node element;
+// You can derive your class directly from this, but then you must add type casts
+// all over the place.
+class SingleLinkListNode
+{	public:
+	SingleLinkListNode *mNext;
+	SingleLinkListNode *next() {return mNext;}
+	void setNext(SingleLinkListNode *item) {mNext=item;}
+	SingleLinkListNode() : mNext(0) {}
+	virtual unsigned size() { return 0; }
+};
+
+// A single-linked lists of elements with internal pointers.
+// The methods must match those from SingleLinkListNode.
+// This class also assumes the Node has a size() method, and accumulates
+// the total size of elements in the list in totalSize().
+template<class Node=SingleLinkListNode>
+class SingleLinkList
+{
+	Node *mHead, *mTail;
+	unsigned mSize;			// Number of elements in list.
+	unsigned mTotalSize;	// Total of size() method of elements in list.
+
+	public:
+	SingleLinkList() : mHead(0), mTail(0), mSize(0), mTotalSize(0) {}
+	unsigned size() const { return mSize; }
+	unsigned totalSize() const { return mTotalSize; }
+
+	Node *pop_back() { assert(0); } // Not efficient with this type of list.
+
+	Node *pop_front()
+	{
+		if (!mHead) return NULL;
+		Node *result = mHead;
+		mHead = mHead->next();
+		if (mTail == result) { mTail = NULL; assert(mHead == NULL); }
+		result->setNext(NULL);	// be neat
+		mSize--;
+		mTotalSize -= result->size();
+		return result;
+	}
+
+	void push_front(Node *item)
+	{
+		item->setNext(mHead);
+		mHead = item;
+		if (!mTail) { mTail = item; }
+		mSize++;
+		mTotalSize += item->size();
+	}
+
+	void push_back(Node *item)
+	{
+		item->setNext(NULL);
+		if (mTail) { mTail->setNext(item); }
+		mTail = item;
+		if (!mHead) mHead = item;
+		mSize++;
+		mTotalSize += item->size();
+	}
+	Node *front() { return mHead; }
+	Node *back() { return mTail; }
+
+	// Interface to InterthreadQueue so it can used SingleLinkList as the Fifo.
+	void put(void *val) { push_back((Node*)val); }
+	void *get() { return pop_front(); }
+};
+
 
 
 
diff --git a/CommonLibs/LogTest.cpp b/CommonLibs/LogTest.cpp
index e9f73b0..20959a0 100644
--- a/CommonLibs/LogTest.cpp
+++ b/CommonLibs/LogTest.cpp
@@ -37,7 +37,7 @@
 {
     std::ostream_iterator<std::string> output( std::cout, "\n" );
     std::list<std::string> alarms = gGetLoggerAlarms();
-    std::cout << "#alarms = " << alarms.size() << std::endl;
+    std::cout << "# alarms = " << alarms.size() << std::endl;
     std::copy( alarms.begin(), alarms.end(), output );
 }
 
@@ -55,7 +55,6 @@
 	LOG(DEBUG) << " testing the logger.";
     std::cout << "\n\n\n";
     std::cout << "testing Alarms\n";
-	LOG(ALERT) << " testing the logger alarm.";
     std::cout << "you should see three lines:" << std::endl;
     printAlarms();
     std::cout << "----------- generating 20 alarms ----------" << std::endl;
diff --git a/CommonLibs/Logger.cpp b/CommonLibs/Logger.cpp
index 36b7696..06e91f6 100644
--- a/CommonLibs/Logger.cpp
+++ b/CommonLibs/Logger.cpp
@@ -1,6 +1,7 @@
 /*
 * Copyright 2009, 2010 Free Software Foundation, Inc.
 * Copyright 2010 Kestrel Signal Processing, Inc.
+* Copyright 2011, 2012 Range Networks, Inc.
 *
 *
 * This software is distributed under the terms of the GNU Affero Public License.
@@ -32,6 +33,7 @@
 
 #include "Configuration.h"
 #include "Logger.h"
+#include "Threads.h"	// pat added
 
 
 using namespace std;
@@ -49,36 +51,71 @@
 
 
 
+// (pat) If Log messages are printed before the classes in this module are inited
+// (which happens when static classes have constructors that do work)
+// the OpenBTS just crashes.
+// Prevent that by setting sLoggerInited to true when this module is inited.
+static bool sLoggerInited = 0;
+static struct CheckLoggerInitStatus {
+	CheckLoggerInitStatus() { sLoggerInited = 1; }
+} sCheckloggerInitStatus;
+
+
 
 /** Names of the logging levels. */
 const char *levelNames[] = {
 	"EMERG", "ALERT", "CRIT", "ERR", "WARNING", "NOTICE", "INFO", "DEBUG"
 };
 int numLevels = 8;
+bool gLogToConsole = 0;
+FILE *gLogToFile = NULL;
+Mutex gLogToLock;
 
 
-/** Given a string, return the corresponding level name. */
-int lookupLevel(const string& name)
+int levelStringToInt(const string& name)
 {
 	// Reverse search, since the numerically larger levels are more common.
 	for (int i=numLevels-1; i>=0; i--) {
 		if (name == levelNames[i]) return i;
 	}
-	// This should never be called with a bogus name.
-	LOG(ERR) << "undefined logging level " << name << "defaulting to ERR";
-	return LOG_ERR;
+
+	// Common substitutions.
+	if (name=="INFORMATION") return 6;
+	if (name=="WARN") return 4;
+	if (name=="ERROR") return 3;
+	if (name=="CRITICAL") return 2;
+	if (name=="EMERGENCY") return 0;
+
+	// Unknown level.
+	return -1;
+}
+
+/** Given a string, return the corresponding level name. */
+int lookupLevel(const string& key)
+{
+	string val = gConfig.getStr(key);
+	int level = levelStringToInt(val);
+
+	if (level == -1) {
+		string defaultLevel = gConfig.mSchema["Log.Level"].getDefaultValue();
+		level = levelStringToInt(defaultLevel);
+		_LOG(CRIT) << "undefined logging level (" << key << " = \"" << val << "\") defaulting to \"" << defaultLevel << ".\" Valid levels are: EMERG, ALERT, CRIT, ERR, WARNING, NOTICE, INFO or DEBUG";
+		gConfig.set(key, defaultLevel);
+	}
+
+	return level;
 }
 
 
 int getLoggingLevel(const char* filename)
 {
 	// Default level?
-	if (!filename) return lookupLevel(gConfig.getStr("Log.Level"));
+	if (!filename) return lookupLevel("Log.Level");
 
 	// This can afford to be inefficient since it is not called that often.
 	const string keyName = string("Log.Level.") + string(filename);
-	if (gConfig.defines(keyName)) return lookupLevel(gConfig.getStr(keyName));
-	return lookupLevel(gConfig.getStr("Log.Level"));
+	if (gConfig.defines(keyName)) return lookupLevel(keyName);
+	return lookupLevel("Log.Level");
 }
 
 
@@ -113,6 +150,7 @@
 	}
 	// Look it up in the config table and cache it.
 	// FIXME: Figure out why unlock and lock below fix the config table deadlock.
+	// (pat) Probably because getLoggingLevel may call LOG recursively via lookupLevel().
 	sLogCacheLock.unlock();
 	int level = getLoggingLevel(filename);
 	sLogCacheLock.lock();
@@ -155,12 +193,30 @@
 	// Anything at or above LOG_CRIT is an "alarm".
 	// Save alarms in the local list and echo them to stderr.
 	if (mPriority <= LOG_CRIT) {
-		addAlarm(mStream.str().c_str());
+		if (sLoggerInited) addAlarm(mStream.str().c_str());
 		cerr << mStream.str() << endl;
 	}
 	// Current logging level was already checked by the macro.
 	// So just log.
 	syslog(mPriority, "%s", mStream.str().c_str());
+	// pat added for easy debugging.
+	if (gLogToConsole||gLogToFile) {
+		int mlen = mStream.str().size();
+		int neednl = (mlen==0 || mStream.str()[mlen-1] != '\n');
+		gLogToLock.lock();
+		if (gLogToConsole) {
+			// The COUT() macro prevents messages from stomping each other but adds uninteresting thread numbers,
+			// so just use std::cout.
+			std::cout << mStream.str();
+			if (neednl) std::cout<<"\n";
+		}
+		if (gLogToFile) {
+			fputs(mStream.str().c_str(),gLogToFile);
+			if (neednl) {fputc('\n',gLogToFile);}
+			fflush(gLogToFile);
+		}
+		gLogToLock.unlock();
+	}
 }
 
 
@@ -182,18 +238,26 @@
 
 void gLogInit(const char* name, const char* level, int facility)
 {
-	// Set the level.
+	// Set the level if one has been specified.
 	if (level) {
 		gConfig.set("Log.Level",level);
-	} else {
-		if (!gConfig.defines("Log.Level")) {
-			gConfig.set("Log.Level","WARNING");
-		}
 	}
 
-	// Define other logging parameters in the global config.
-	if (!gConfig.defines("Log.Alarms.Max")) {
-		gConfig.set("Log.Alarms.Max",DEFAULT_MAX_ALARMS);
+	// Pat added, tired of the syslog facility.
+	// Both the transceiver and OpenBTS use this same facility, but only OpenBTS/OpenNodeB may use this log file:
+	string str = gConfig.getStr("Log.File");
+	if (gLogToFile==0 && str.length() && 0==strncmp(gCmdName,"Open",4)) {
+		const char *fn = str.c_str();
+		if (fn && *fn && strlen(fn)>3) {	// strlen because a garbage char is getting in sometimes.
+			gLogToFile = fopen(fn,"w"); // New log file each time we start.
+			if (gLogToFile) {
+				time_t now;
+				time(&now);
+				fprintf(gLogToFile,"Starting at %s",ctime(&now));
+				fflush(gLogToFile);
+				std::cout << "Logging to file: " << fn << "\n";
+			}
+		}
 	}
 
 	// Open the log connection.
diff --git a/CommonLibs/Logger.h b/CommonLibs/Logger.h
index 6b1c44d..58dfa22 100644
--- a/CommonLibs/Logger.h
+++ b/CommonLibs/Logger.h
@@ -23,6 +23,11 @@
 
 */
 
+// (pat) WARNING is stupidly defined in /usr/local/include/osipparser2/osip_const.h.
+// This must be outside the #ifndef LOGGER_H to fix it as long as Logger.h included after the above file.
+#ifdef WARNING
+#undef WARNING
+#endif
 
 #ifndef LOGGER_H
 #define LOGGER_H
@@ -34,21 +39,42 @@
 #include <list>
 #include <map>
 #include <string>
-#include "Threads.h"
-
 
 #define _LOG(level) \
 	Log(LOG_##level).get() << pthread_self() \
-	<< " " __FILE__  ":"  << __LINE__ << ":" << __FUNCTION__ << ": "
+	<< timestr() << " " __FILE__  ":"  << __LINE__ << ":" << __FUNCTION__ << ": "
+
+#define IS_LOG_LEVEL(wLevel) (gGetLoggingLevel(__FILE__)>=LOG_##wLevel)
 
 #ifdef NDEBUG
 #define LOG(wLevel) \
-	if (LOG_##wLevel!=LOG_DEBUG && gGetLoggingLevel(__FILE__)>=LOG_##wLevel) _LOG(wLevel)
+	if (LOG_##wLevel!=LOG_DEBUG && IS_LOG_LEVEL(wLevel)) _LOG(wLevel)
 #else
 #define LOG(wLevel) \
-	if (gGetLoggingLevel(__FILE__)>=LOG_##wLevel) _LOG(wLevel)
+	if (IS_LOG_LEVEL(wLevel)) _LOG(wLevel)
 #endif
 
+// pat: And for your edification here are the 'levels' as defined in syslog.h:
+// LOG_EMERG   0  system is unusable
+// LOG_ALERT   1  action must be taken immediately
+// LOG_CRIT    2  critical conditions
+// LOG_ERR     3  error conditions
+// LOG_WARNING 4  warning conditions
+// LOG_NOTICE  5  normal, but significant, condition
+// LOG_INFO    6  informational message
+// LOG_DEBUG   7  debug-level message
+
+// (pat) added - print out a var and its name.
+// Use like this: int descriptive_name; LOG(INFO)<<LOGVAR(descriptive_name);
+#define LOGVAR2(name,val) " " << name << "=" << (val)
+#define LOGVAR(var) (" " #var "=") << var
+#define LOGHEX(var) (" " #var "=0x") << hex << ((unsigned)var) << dec
+#define LOGHEX2(name,val) " " << name << "=0x" << hex << ((unsigned)(val)) << dec
+// These are kind of cheesy, but you can use for bitvector
+#define LOGBV2(name,val) " " << name << "=(" << val<<" size:"<<val.size()<<")"
+#define LOGBV(bv) LOGBV2(#bv,bv)
+#define LOGVARRANGE(name,cur,lo,hi) " "<<name <<"=("<<(cur) << " range:"<<(lo) << " to "<<(hi) <<")"
+
 
 #define OBJLOG(wLevel) \
 	LOG(wLevel) << "obj: " << this << ' '
@@ -56,8 +82,8 @@
 #define LOG_ASSERT(x) { if (!(x)) LOG(EMERG) << "assertion " #x " failed"; } assert(x);
 
 
-#define DEFAULT_MAX_ALARMS 10
-
+#include "Threads.h"		// must be after defines above, if these files are to be allowed to use LOG()
+#include "Utils.h"
 
 /**
 	A C++ stream-based thread-safe logger.
@@ -90,6 +116,7 @@
 
 	std::ostringstream& get();
 };
+extern bool gLogToConsole;	// Pat added for easy debugging.
 
 
 
diff --git a/CommonLibs/Makefile.am b/CommonLibs/Makefile.am
index 624ba44..26a55ed 100644
--- a/CommonLibs/Makefile.am
+++ b/CommonLibs/Makefile.am
@@ -22,7 +22,11 @@
 include $(top_srcdir)/Makefile.common
 
 AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES)
-AM_CXXFLAGS = -Wall -ldl -O3 -g -lpthread
+AM_CXXFLAGS = -Wall -O3 -g -ldl -lpthread
+
+EXTRA_DIST = \
+	example.config \
+	README.common
 
 noinst_LTLIBRARIES = libcommon.la
 
@@ -32,11 +36,12 @@
 	Sockets.cpp \
 	Threads.cpp \
 	Timeval.cpp \
+	Reporting.cpp \
+	Logger.cpp \
 	Configuration.cpp \
 	sqlite3util.cpp \
-	Logger.cpp \
 	URLEncode.cpp \
-	Reporting.cpp
+	Utils.cpp
 
 noinst_PROGRAMS = \
 	BitVectorTest \
@@ -47,9 +52,10 @@
 	VectorTest \
 	ConfigurationTest \
 	LogTest \
+	URLEncodeTest \
 	F16Test
 
-#	ReportingTest
+#	ReportingTest 
 
 noinst_HEADERS = \
 	BitVector.h \
@@ -60,15 +66,19 @@
 	Timeval.h \
 	Regexp.h \
 	Vector.h \
-	URLEncode.h \
 	Configuration.h \
 	Reporting.h \
 	F16.h \
+	URLEncode.h \
+	Utils.h \
 	Logger.h \
 	sqlite3util.h
 
+URLEncodeTest_SOURCES = URLEncodeTest.cpp
+URLEncodeTest_LDADD = libcommon.la
+
 BitVectorTest_SOURCES = BitVectorTest.cpp
-BitVectorTest_LDADD = libcommon.la
+BitVectorTest_LDADD = libcommon.la $(SQLITE_LA)
 
 InterthreadTest_SOURCES = InterthreadTest.cpp
 InterthreadTest_LDADD = libcommon.la
@@ -82,7 +92,7 @@
 TimevalTest_LDADD = libcommon.la
 
 VectorTest_SOURCES = VectorTest.cpp
-VectorTest_LDADD = libcommon.la
+VectorTest_LDADD = libcommon.la $(SQLITE_LA)
 
 RegexpTest_SOURCES = RegexpTest.cpp
 RegexpTest_LDADD = libcommon.la
@@ -90,8 +100,8 @@
 ConfigurationTest_SOURCES = ConfigurationTest.cpp
 ConfigurationTest_LDADD = libcommon.la 	$(SQLITE_LA)
 
-#ReportingTest_SOURCES = ReportingTest.cpp
-#ReportingTest_LDADD = libcommon.la $(SQLITE_LA)
+# ReportingTest_SOURCES = ReportingTest.cpp
+# ReportingTest_LDADD = libcommon.la $(SQLITE_LA)
 
 LogTest_SOURCES = LogTest.cpp
 LogTest_LDADD = libcommon.la $(SQLITE_LA)
diff --git a/CommonLibs/MemoryLeak.h b/CommonLibs/MemoryLeak.h
new file mode 100644
index 0000000..4948534
--- /dev/null
+++ b/CommonLibs/MemoryLeak.h
@@ -0,0 +1,111 @@
+/*
+* Copyright 2011 Range Networks, Inc.
+* All Rights Reserved.
+*
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+    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.
+*/
+#ifndef _MEMORYLEAK_
+#define _MEMORYLEAK_ 1
+#include <map>
+#include "ScalarTypes.h"
+#include "Logger.h"
+
+namespace Utils {
+
+struct MemStats {
+	// Enumerates the classes that are checked.
+	// Redundancies are ok, for example, we check BitVector and also
+	// several descendants of BitVector.
+	enum MemoryNames {
+		mZeroIsUnused,
+		mVector,
+		mVectorData,
+		mBitVector,
+		mByteVector,
+		mByteVectorData,
+		mRLCRawBlock,
+		mRLCUplinkDataBlock,
+		mRLCMessage,
+		mRLCMsgPacketDownlinkDummyControlBlock,	// Redundant with RLCMessage
+		mTBF,
+		mLlcEngine,
+		mSgsnDownlinkMsg,
+		mRachInfo,
+		mPdpPdu,
+		mFECDispatchInfo,
+		mL3Frame,
+		msignalVector,
+		mSoftVector,
+		mScramblingCode,
+		mURlcDownSdu,
+		mURlcPdu,
+		// Must be last:
+		mMax,
+	};
+	int mMemTotal[mMax];	// In elements, not bytes.
+	int mMemNow[mMax];
+	const char *mMemName[mMax];
+	MemStats();
+	void memChkNew(MemoryNames memIndex, const char *id);
+	void memChkDel(MemoryNames memIndex, const char *id);
+	void text(std::ostream &os);
+	// We would prefer to use an unordered_map, but that requires special compile switches.
+	// What a super great language.
+	typedef std::map<std::string,Int_z> MemMapType;
+	MemMapType mMemMap;
+};
+extern struct MemStats gMemStats;
+extern int gMemLeakDebug;
+
+// This is a memory leak detector.
+// Use by putting RN_MEMCHKNEW and RN_MEMCHKDEL in class constructors/destructors,
+// or use the DEFINE_MEMORY_LEAK_DETECTOR class and add the defined class
+// as an ancestor to the class to be memory leak checked.
+
+struct MemLabel {
+	std::string mccKey;
+	virtual ~MemLabel() {
+		Int_z &tmp = Utils::gMemStats.mMemMap[mccKey]; tmp = tmp - 1;
+	}
+};
+
+#if RN_DISABLE_MEMORY_LEAK_TEST
+#define RN_MEMCHKNEW(type)
+#define RN_MEMCHKDEL(type)
+#define RN_MEMLOG(type,ptr)
+#define DEFINE_MEMORY_LEAK_DETECTOR_CLASS(subClass,checkerClass) \
+	struct checkerClass {};
+#else
+
+#define RN_MEMCHKNEW(type) { Utils::gMemStats.memChkNew(Utils::MemStats::m##type,#type); }
+#define RN_MEMCHKDEL(type) { Utils::gMemStats.memChkDel(Utils::MemStats::m##type,#type); }
+
+#define RN_MEMLOG(type,ptr) { \
+	static std::string key = format("%s_%s:%d",#type,__FILE__,__LINE__); \
+	(ptr)->/* MemCheck##type:: */ mccKey = key; \
+	Utils::gMemStats.mMemMap[key]++; \
+	}
+
+// TODO: The above assumes that checkclass is MemCheck ## subClass
+#define DEFINE_MEMORY_LEAK_DETECTOR_CLASS(subClass,checkerClass) \
+	struct checkerClass : public virtual Utils::MemLabel { \
+	    checkerClass() { RN_MEMCHKNEW(subClass); } \
+		virtual ~checkerClass() { \
+			RN_MEMCHKDEL(subClass); \
+		} \
+	};
+
+#endif
+
+}	// namespace Utils
+
+#endif
diff --git a/CommonLibs/ScalarTypes.h b/CommonLibs/ScalarTypes.h
new file mode 100644
index 0000000..077d889
--- /dev/null
+++ b/CommonLibs/ScalarTypes.h
@@ -0,0 +1,136 @@
+/*
+* Copyright 2011 Range Networks, Inc.
+* All Rights Reserved.
+*
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+    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.
+*/
+
+#ifndef SCALARTYPES_H
+#define SCALARTYPES_H
+#include <iostream>	// For size_t
+#include <stdint.h>
+//#include "GSMCommon.h"	// Was included for Z100Timer
+
+// We dont bother to define *= /= etc.; you'll have to convert: a*=b; to: a=a*b;
+#define _INITIALIZED_SCALAR_BASE_FUNCS(Classname,Basetype,Init) \
+	Classname() : value(Init) {} \
+	Classname(Basetype wvalue) { value = wvalue; } /* Can set from basetype. */ \
+	operator Basetype(void) const { return value; }		/* Converts from basetype. */ \
+	Basetype operator=(Basetype wvalue) { return value = wvalue; } \
+	Basetype* operator&() { return &value; }
+
+#define _INITIALIZED_SCALAR_ARITH_FUNCS(Basetype) \
+	Basetype operator++() { return ++value; } \
+	Basetype operator++(int) { return value++; } \
+	Basetype operator--() { return --value; } \
+	Basetype operator--(int) { return value--; } \
+	Basetype operator+=(Basetype wvalue) { return value = value + wvalue; } \
+	Basetype operator-=(Basetype wvalue) { return value = value - wvalue; }
+
+#define _INITIALIZED_SCALAR_FUNCS(Classname,Basetype,Init) \
+	_INITIALIZED_SCALAR_BASE_FUNCS(Classname,Basetype,Init) \
+	_INITIALIZED_SCALAR_ARITH_FUNCS(Basetype)
+
+
+#define _DECLARE_SCALAR_TYPE(Classname_i,Classname_z,Basetype) \
+	template <Basetype Init> \
+	struct Classname_i { \
+		Basetype value; \
+		_INITIALIZED_SCALAR_FUNCS(Classname_i,Basetype,Init) \
+	}; \
+	typedef Classname_i<0> Classname_z;
+
+
+// Usage:
+// Where 'classname' is one of the types listed below, then:
+// 		classname_z specifies a zero initialized type;
+// 		classname_i<value> initializes the type to the specified value.
+// We also define Float_z.
+_DECLARE_SCALAR_TYPE(Int_i, 	Int_z,  	int)
+_DECLARE_SCALAR_TYPE(Char_i,	Char_z, 	signed char)
+_DECLARE_SCALAR_TYPE(Int16_i,	Int16_z,	int16_t)
+_DECLARE_SCALAR_TYPE(Int32_i,	Int32_z,	int32_t)
+_DECLARE_SCALAR_TYPE(UInt_i,  	UInt_z,  	unsigned)
+_DECLARE_SCALAR_TYPE(UChar_i,  	UChar_z,  	unsigned char)
+_DECLARE_SCALAR_TYPE(UInt16_i,	UInt16_z,	uint16_t)
+_DECLARE_SCALAR_TYPE(UInt32_i,	UInt32_z,	uint32_t)
+_DECLARE_SCALAR_TYPE(Size_t_i,	Size_t_z,	size_t)
+
+// Bool is special because it cannot accept some arithmetic funcs
+//_DECLARE_SCALAR_TYPE(Bool_i,  	Bool_z, 	bool)
+template <bool Init>
+struct Bool_i {
+	bool value;
+	_INITIALIZED_SCALAR_BASE_FUNCS(Bool_i,bool,Init)
+};
+typedef Bool_i<0> Bool_z;
+
+// float is special, because C++ does not permit the template initalization:
+struct Float_z {
+	float value;
+	_INITIALIZED_SCALAR_FUNCS(Float_z,float,0)
+};
+struct Double_z {
+	double value;
+	_INITIALIZED_SCALAR_FUNCS(Double_z,double,0)
+};
+
+
+class ItemWithValueAndWidth {
+	public:
+	virtual unsigned getValue() const = 0;
+	virtual unsigned getWidth() const = 0;
+};
+
+// A Range Networks Field with a specified width.
+// See RLCMessages.h for examples.
+template <int Width=32, unsigned Init=0>
+class Field_i : public ItemWithValueAndWidth
+{
+	public:
+	unsigned value;
+	_INITIALIZED_SCALAR_FUNCS(Field_i,unsigned,Init)
+	unsigned getWidth() const { return Width; }
+	unsigned getValue() const { return value; }
+};
+
+// Synonym for Field_i, but no way to do it.
+template <int Width, unsigned Init=0>
+class Field_z : public ItemWithValueAndWidth
+{
+	public:
+	unsigned value;
+	_INITIALIZED_SCALAR_FUNCS(Field_z,unsigned,Init)
+	unsigned getWidth() const { return Width; }
+	unsigned getValue() const { return value; }
+};
+
+// This is an uninitialized field.
+template <int Width=32, unsigned Init=0>
+class Field : public ItemWithValueAndWidth
+{
+	public:
+	unsigned value;
+	_INITIALIZED_SCALAR_FUNCS(Field,unsigned,Init)
+	unsigned getWidth() const { return Width; }
+	unsigned getValue() const { return value; }
+};
+
+
+// A Z100Timer with an initial value specified.
+//template <int Init>
+//class Z100Timer_i : public GSM::Z100Timer {
+//	public:
+//	Z100Timer_i() : GSM::Z100Timer(Init) {}
+//};
+
+#endif
diff --git a/CommonLibs/Sockets.cpp b/CommonLibs/Sockets.cpp
index dc110c8..dd7527c 100644
--- a/CommonLibs/Sockets.cpp
+++ b/CommonLibs/Sockets.cpp
@@ -41,6 +41,10 @@
 #include <stdlib.h>
 
 
+
+
+
+
 bool resolveAddress(struct sockaddr_in *address, const char *hostAndPort)
 {
 	assert(address);
@@ -255,6 +259,11 @@
 		throw SocketError();
 	}
 
+	// pat added: This lets the socket be reused immediately, which is needed if OpenBTS crashes.
+	int on = 1;
+	setsockopt(mSocketFD, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+
 	// bind
 	struct sockaddr_in address;
 	size_t length = sizeof(address);
diff --git a/CommonLibs/Threads.cpp b/CommonLibs/Threads.cpp
index 7cc8b92..de6520b 100644
--- a/CommonLibs/Threads.cpp
+++ b/CommonLibs/Threads.cpp
@@ -107,8 +107,9 @@
 {
 	assert(mThread==((pthread_t)0));
 	bool res;
-	res = pthread_attr_init(&mAttrib);
-	assert(!res);
+	// (pat) Moved initialization to constructor to avoid crash in destructor.
+	//res = pthread_attr_init(&mAttrib);
+	//assert(!res);
 	res = pthread_attr_setstacksize(&mAttrib, mStackSize);
 	assert(!res);
 	res = pthread_create(&mThread, &mAttrib, task, arg);
diff --git a/CommonLibs/Threads.h b/CommonLibs/Threads.h
index eba8e89..a38a73a 100644
--- a/CommonLibs/Threads.h
+++ b/CommonLibs/Threads.h
@@ -155,12 +155,16 @@
 	public:
 
 	/** Create a thread in a non-running state. */
-	Thread(size_t wStackSize = (65536*4)):mThread((pthread_t)0) { mStackSize=wStackSize;}
+	Thread(size_t wStackSize = (65536*4)):mThread((pthread_t)0) {
+		pthread_attr_init(&mAttrib);	// (pat) moved this here.
+		mStackSize=wStackSize;
+	}
 
 	/**
 		Destroy the Thread.
 		It should be stopped and joined.
 	*/
+	// (pat) If the Thread is destroyed without being started, then mAttrib is undefined.  Oops.
 	~Thread() { pthread_attr_destroy(&mAttrib); }
 
 
@@ -168,7 +172,7 @@
 	void start(void *(*task)(void*), void *arg);
 
 	/** Join a thread that will stop on its own. */
-	void join() { int s = pthread_join(mThread,NULL); assert(!s); }
+	void join() { int s = pthread_join(mThread,NULL); assert(!s); mThread = 0; }
 
 };
 
diff --git a/CommonLibs/Timeval.h b/CommonLibs/Timeval.h
index f528819..c497864 100644
--- a/CommonLibs/Timeval.h
+++ b/CommonLibs/Timeval.h
@@ -32,6 +32,7 @@
 #include <unistd.h>
 
 
+
 /** A wrapper on usleep to sleep for milliseconds. */
 inline void msleep(long v) { usleep(v*1000); }
 
diff --git a/CommonLibs/URLEncode.cpp b/CommonLibs/URLEncode.cpp
index 025ef60..cdf38dd 100644
--- a/CommonLibs/URLEncode.cpp
+++ b/CommonLibs/URLEncode.cpp
@@ -1,27 +1,4 @@
-/*
-* Copyright 2011 Free Software Foundation, Inc.
-*
-*
-* This software is distributed under the terms of the GNU Affero Public License.
-* See the COPYING file in the main directory for details.
-*
-* This use of this software may be subject to additional restrictions.
-* See the LEGAL file in the main directory for details.
-
-	This program 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.
-
-	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 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/>.
-
-*/
+/* Copyright 2011, Range Networks, Inc. */
 
 #include <URLEncode.h>
 #include <string>
diff --git a/CommonLibs/URLEncodeTest.cpp b/CommonLibs/URLEncodeTest.cpp
new file mode 100644
index 0000000..dbc4630
--- /dev/null
+++ b/CommonLibs/URLEncodeTest.cpp
@@ -0,0 +1,17 @@
+
+#include "URLEncode.h"
+#include <string>
+#include <iostream>
+
+
+using namespace std;
+
+
+int main(int argc, char *argv[])
+{
+
+	string test = string("Testing: !@#$%^&*() " __DATE__ " " __TIME__);
+	cout << test << endl;
+	cout << URLEncode(test) << endl;
+}
+
diff --git a/CommonLibs/Utils.cpp b/CommonLibs/Utils.cpp
new file mode 100644
index 0000000..1da95fa
--- /dev/null
+++ b/CommonLibs/Utils.cpp
@@ -0,0 +1,211 @@
+/*
+* Copyright 2011 Range Networks, Inc.
+* All Rights Reserved.
+*
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+    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.
+*/
+
+#include <unistd.h>		// For usleep
+#include <sys/time.h>	// For gettimeofday
+#include <stdio.h>		// For vsnprintf
+#include <ostream>		// For ostream
+#include <sstream>		// For ostringstream
+#include <string.h>		// For strcpy
+//#include "GSMCommon.h"
+#include "Utils.h"
+#include "MemoryLeak.h"
+
+namespace Utils {
+
+MemStats gMemStats;
+int gMemLeakDebug = 0;
+
+MemStats::MemStats()
+{
+	memset(mMemNow,0,sizeof(mMemNow));
+	memset(mMemTotal,0,sizeof(mMemTotal));
+	memset(mMemName,0,sizeof(mMemName));
+}
+
+void MemStats::text(std::ostream &os)
+{
+	os << "Structs current total:\n";
+	for (int i = 0; i < mMax; i++) {
+		os << "\t" << (mMemName[i] ? mMemName[i] : "unknown") << " " << mMemNow[i] << " " << mMemTotal[i] << "\n";
+	}
+}
+
+void MemStats::memChkNew(MemoryNames memIndex, const char *id)
+{
+	/*std::cout << "new " #type "\n";*/
+	mMemNow[memIndex]++;
+	mMemTotal[memIndex]++;
+	mMemName[memIndex] = id;
+}
+
+void MemStats::memChkDel(MemoryNames memIndex, const char *id)
+{
+	/*std::cout << "del " #type "\n";*/
+	mMemNow[memIndex]--;
+	if (mMemNow[memIndex] < 0) {
+		LOG(ERR) << "Memory underflow on type "<<id;
+		if (gMemLeakDebug) assert(0);
+		mMemNow[memIndex] += 100;	// Prevent another message for a while.
+	}
+}
+
+std::ostream& operator<<(std::ostream& os, std::ostringstream& ss)
+{
+	return os << ss.str();
+}
+
+std::ostream &osprintf(std::ostream &os, const char *fmt, ...)
+{
+	va_list ap;
+	char buf[300];
+	va_start(ap,fmt);
+	int n = vsnprintf(buf,300,fmt,ap);
+	va_end(ap);
+	if (n >= (300-4)) { strcpy(&buf[(300-4)],"..."); }
+	os << buf;
+	return os;
+}
+
+std::string format(const char *fmt, ...)
+{
+	va_list ap;
+	char buf[300];
+	va_start(ap,fmt);
+	int n = vsnprintf(buf,300,fmt,ap);
+	va_end(ap);
+	if (n >= (300-4)) { strcpy(&buf[(300-4)],"..."); }
+	return std::string(buf);
+}
+
+// Return time in seconds with high resolution.
+// Note: In the past I found this to be a surprisingly expensive system call in linux.
+double timef()
+{
+	struct timeval tv;
+	gettimeofday(&tv,NULL);
+	return tv.tv_usec / 1000000.0 + tv.tv_sec;
+}
+
+const std::string timestr()
+{
+	struct timeval tv;
+	struct tm tm;
+	gettimeofday(&tv,NULL);
+	localtime_r(&tv.tv_sec,&tm);
+	unsigned tenths = tv.tv_usec / 100000;	// Rounding down is ok.
+	return format(" %02d:%02d:%02d.%1d",tm.tm_hour,tm.tm_min,tm.tm_sec,tenths);
+}
+
+// High resolution sleep for the specified time.
+// Return FALSE if time is already past.
+void sleepf(double howlong)
+{
+	if (howlong <= 0.00001) return;		// Less than 10 usecs, forget it.
+	usleep((useconds_t) (1000000.0 * howlong));
+}
+
+//bool sleepuntil(double untilwhen)
+//{
+	//double now = timef();
+	//double howlong = untilwhen - now;		// Fractional time in seconds.
+	// We are not worrying about overflow because all times should be in the near future.
+	//if (howlong <= 0.00001) return false;		// Less than 10 usecs, forget it.
+	//sleepf(sleeptime);
+//}
+
+std::string Text2Str::str() const
+{
+	std::ostringstream ss;
+	text(ss);
+	return ss.str();
+}
+
+std::ostream& operator<<(std::ostream& os, const Text2Str *val)
+{
+	std::ostringstream ss;
+	if (val) {
+		val->text(ss);
+		os << ss.str(); 
+	} else {
+		os << "(null)";
+	}
+	return os;
+}
+
+// Greatest Common Denominator.
+// This is by Doug Brown.
+int gcd(int x, int y)
+{
+	if (x > y) {
+		return x % y == 0 ? y : gcd(y, x % y);
+	} else {
+		return y % x == 0 ? x : gcd(x, y % x);
+	}
+}
+
+
+// Split a C string into an argc,argv array in place; the input string is modified.
+// Returns argc, and places results in argv, up to maxargc elements.
+// The final argv receives the rest of the input string from maxargc on,
+// even if it contains additional splitchars.
+// The correct idiom for use is to make a copy of your string, like this:
+// char *copy = strcpy((char*)alloca(the_string.length()+1),the_string.c_str());
+// char *argv[2];
+// int argc = cstrSplit(copy,argv,2,NULL);
+// If you want to detect the error of too many arguments, add 1 to argv, like this:
+// char *argv[3];
+// int argc = cstrSplit(copy,argv,3,NULL);
+// if (argc == 3) { error("too many arguments"; }
+int cstrSplit(char *in, char **pargv,int maxargc, const char *splitchars)
+{
+	if (splitchars == NULL) { splitchars = " \t\r\n"; }	// Default is any space.
+	int argc = 0;
+	while (argc < maxargc) {
+		while (*in && strchr(splitchars,*in)) {in++;}	// scan past any splitchars
+		if (! *in) return argc;					// return if finished.
+		pargv[argc++] = in;						// save ptr to start of arg.
+		in = strpbrk(in,splitchars);			// go to end of arg.
+		if (!in) return	argc;					// return if finished.
+		*in++ = 0;								// zero terminate this arg.
+	}
+	return argc;
+}
+
+std::ostream& operator<<(std::ostream& os, const Statistic<int> &stat) { stat.text(os); return os; }
+std::ostream& operator<<(std::ostream& os, const Statistic<unsigned> &stat) { stat.text(os); return os; }
+std::ostream& operator<<(std::ostream& os, const Statistic<float> &stat) { stat.text(os); return os; }
+std::ostream& operator<<(std::ostream& os, const Statistic<double> &stat) { stat.text(os); return os; }
+
+std::string replaceAll(const std::string input, const std::string search, const std::string replace)
+{
+	std::string output = input;
+ 	int index = 0;
+
+	while (true) {
+		index = output.find(search, index);
+		if (index == std::string::npos) {
+			break;
+		}
+
+		output.replace(index, replace.length(), replace);
+		index += replace.length();
+	}
+
+	return output;
+}
+
+};
diff --git a/CommonLibs/Utils.h b/CommonLibs/Utils.h
new file mode 100644
index 0000000..0bc738e
--- /dev/null
+++ b/CommonLibs/Utils.h
@@ -0,0 +1,148 @@
+/*
+* Copyright 2011 Range Networks, Inc.
+* All Rights Reserved.
+*
+* This software is distributed under multiple licenses;
+* see the COPYING file in the main directory for licensing
+* information for this specific distribuion.
+*
+* This use of this software may be subject to additional restrictions.
+* See the LEGAL file in the main directory for details.
+
+    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.
+*/
+
+#ifndef GPRSUTILS_H
+#define GPRSUTILS_H
+#include <stdint.h>
+#include <stdarg.h>
+#include <string>
+#include <string.h>
+#include <math.h>		// for sqrtf
+#include "Logger.h"
+
+
+namespace Utils {
+
+extern double timef();					// high resolution time
+extern const std::string timestr();		// A timestamp to print in messages.
+extern void sleepf(double howlong);	// high resolution sleep
+extern int gcd(int x, int y);
+
+// It is irritating to create a string just to interface to the brain-damaged
+// C++ stream class, but this is only used for debug messages.
+std::string format(const char *fmt, ...) __attribute__((format (printf,1,2)));
+
+int cstrSplit(char *in, char **pargv,int maxargc, const char *splitchars=NULL);
+
+// For classes with a text() function, provide a function to return a String,
+// and also a standard << stream function that takes a pointer to the object.
+// We dont provide the function that takes a reference to the object
+// because it is too highly overloaded and generally doesnt work.
+class Text2Str {
+	public:
+	virtual void text(std::ostream &os) const = 0;
+	std::string str() const;
+};
+std::ostream& operator<<(std::ostream& os, const Text2Str *val);
+
+#if 0
+// Generic Activity Timer.  Lots of controls to make everybody happy.
+class ATimer {
+	double mStart;
+	//bool mActive;
+	double mLimitTime;
+	public:
+	ATimer() : mStart(0), mLimitTime(0) { }
+	ATimer(double wLimitTime) : mStart(0), mLimitTime(wLimitTime) { }
+	void start() { mStart=timef(); }
+	void stop() { mStart=0; }
+	bool active() { return !!mStart; }
+	double elapsed() { return timef() - mStart; }
+	bool expired() { return elapsed() > mLimitTime; }
+};
+#endif
+
+
+struct BitSet {
+	unsigned mBits;
+	void setBit(unsigned whichbit) { mBits |= 1<<whichbit; }
+	void clearBit(unsigned whichbit) { mBits &= ~(1<<whichbit); }
+	unsigned getBit(unsigned whichbit) const { return mBits & (1<<whichbit); }
+	bool isSet(unsigned whichbit) const { return mBits & (1<<whichbit); }
+	unsigned bits() const { return mBits; }
+	operator int(void) const { return mBits; }
+	BitSet() { mBits = 0; }
+};
+
+// Store current, min, max and compute running average and standard deviation.
+template<class Type> struct Statistic {
+	Type mCurrent, mMin, mMax;		// min,max optional initialization so you can print before adding any values.
+	unsigned mCnt;
+	double mSum;
+	//double mSum2;	// sum of squares.
+	// (Type) cast needed in case Type is an enum, stupid language.
+	Statistic() : mCurrent((Type)0), mMin((Type)0), mMax((Type)0), mCnt(0), mSum(0) /*,mSum2(0)*/ {}
+	// Set the current value and add a statisical point.
+	void addPoint(Type val) {
+		mCurrent = val;
+		if (mCnt == 0 || val < mMin) {mMin = val;}
+		if (mCnt == 0 || val > mMax) {mMax = val;}
+		mCnt++;
+		mSum += val;
+		//mSum2 += val * val;
+	}
+	Type getCurrent() const {	// Return current value.
+		return mCnt ? mCurrent : 0;
+	}
+	double getAvg() const { 			// Return average.
+		return mCnt==0 ? 0 : mSum/mCnt; 
+	};
+	//float getSD() const { 	// Return standard deviation.  Use low precision square root function.
+	//	return mCnt==0 ? 0 : sqrtf(mCnt * mSum2 - mSum*mSum) / mCnt;
+	//}
+
+	void text(std::ostream &os) const {	// Print everything in parens.
+		os << "("<<mCurrent;
+		if (mMin != mMax) {	// Not point in printing all this stuff if min == max.
+			os <<LOGVAR2("min",mMin)<<LOGVAR2("max",mMax)<<LOGVAR2("avg",getAvg());
+			if (mCnt <= 999999) {
+				os <<LOGVAR2("N",mCnt);
+			} else { // Shorten this up:
+				char buf[10], *ep;
+				sprintf(buf,"%.3g",round(mCnt));
+				if ((ep = strchr(buf,'e')) && ep[1] == '+') { strcpy(ep+1,ep+2); }
+				os << LOGVAR2("N",buf);
+			}
+			// os<<LOGVAR2("sd",getSD())  standard deviation not interesting
+		}
+		os << ")";
+		// " min="<<mMin <<" max="<<mMax <<format(" avg=%4g sd=%3g)",getAvg(),getSD());
+	}
+	// Not sure if this works:
+	//std::string statStr() const {
+	//	return (std::string)mCurrent + " min=" + (std::string) mMin +" max="+(string)mMax+ format(" avg=%4g sd=%3g",getAvg(),getSD());
+	//}
+};
+
+// This I/O mechanism is so dumb:
+std::ostream& operator<<(std::ostream& os, const Statistic<int> &stat);
+std::ostream& operator<<(std::ostream& os, const Statistic<unsigned> &stat);
+std::ostream& operator<<(std::ostream& os, const Statistic<float> &stat);
+std::ostream& operator<<(std::ostream& os, const Statistic<double> &stat);
+
+
+// Yes, they botched and left this out:
+std::ostream& operator<<(std::ostream& os, std::ostringstream& ss);
+
+std::ostream &osprintf(std::ostream &os, const char *fmt, ...) __attribute__((format (printf,2,3)));
+
+std::string replaceAll(const std::string input, const std::string search, const std::string replace);
+
+};	// namespace
+
+using namespace Utils;
+
+#endif
diff --git a/CommonLibs/Vector.h b/CommonLibs/Vector.h
index 62cb6fb..38dc8d5 100644
--- a/CommonLibs/Vector.h
+++ b/CommonLibs/Vector.h
@@ -32,6 +32,10 @@
 #include <string.h>
 #include <iostream>
 #include <assert.h>
+// We cant use Logger.h in this file...
+extern int gVectorDebug;
+#define BVDEBUG(msg) if (gVectorDebug) {std::cout << msg;}
+
 
 
 /**
@@ -59,6 +63,14 @@
 
 	public:
 
+	/****
+	char *inspect() {
+		static char buf[100];
+		sprintf(buf," mData=%p mStart=%p mEnd=%p ",mData,mStart,mEnd);
+		return buf;
+	}
+	***/
+
 	/** Return the size of the Vector. */
 	size_t size() const
 	{
@@ -246,6 +258,7 @@
 	T* begin() { return mStart; }
 	const T* end() const { return mEnd; }
 	T* end() { return mEnd; }
+	bool isOwner() { return !!mData; }	// Do we own any memory ourselves?
 	//@}
 	
 
diff --git a/CommonLibs/VectorTest.cpp b/CommonLibs/VectorTest.cpp
index 6958889..ad5c473 100644
--- a/CommonLibs/VectorTest.cpp
+++ b/CommonLibs/VectorTest.cpp
@@ -28,6 +28,10 @@
 #include "Vector.h"
 #include <iostream>
 
+// We must have a gConfig now to include Vector.
+#include "Configuration.h"
+ConfigurationTable gConfig;
+
 using namespace std;
 
 typedef Vector<int> TestVector;