diff --git a/A51.cpp b/A51.cpp new file mode 100644 index 0000000..1e9098d --- /dev/null +++ b/A51.cpp @@ -0,0 +1,228 @@ +/* + * A pedagogical implementation of A5/1. + * + * Copyright (C) 1998-1999: Marc Briceno, Ian Goldberg, and David Wagner + * + * The source code below is optimized for instructional value and clarity. + * Performance will be terrible, but that's not the point. + * The algorithm is written in the C programming language to avoid ambiguities + * inherent to the English language. Complain to the 9th Circuit of Appeals + * if you have a problem with that. + * + * This software may be export-controlled by US law. + * + * This software is free for commercial and non-commercial use as long as + * the following conditions are aheared to. + * Copyright remains the authors' and as such any Copyright notices in + * the code are not to be removed. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The license and distribution terms for any publicly available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution license + * [including the GNU Public License.] + * + * Background: The Global System for Mobile communications is the most widely + * deployed cellular telephony system in the world. GSM makes use of + * four core cryptographic algorithms, neither of which has been published by + * the GSM MOU. This failure to subject the algorithms to public review is all + * the more puzzling given that over 100 million GSM + * subscribers are expected to rely on the claimed security of the system. + * + * The four core GSM algorithms are: + * A3 authentication algorithm + * A5/1 "strong" over-the-air voice-privacy algorithm + * A5/2 "weak" over-the-air voice-privacy algorithm + * A8 voice-privacy key generation algorithm + * + * In April of 1998, our group showed that COMP128, the algorithm used by the + * overwhelming majority of GSM providers for both A3 and A8 + * functionality was fatally flawed and allowed for cloning of GSM mobile + * phones. + * Furthermore, we demonstrated that all A8 implementations we could locate, + * including the few that did not use COMP128 for key generation, had been + * deliberately weakened by reducing the keyspace from 64 bits to 54 bits. + * The remaining 10 bits are simply set to zero! + * + * See http://www.scard.org/gsm for additional information. + * + * The question so far unanswered is if A5/1, the "stronger" of the two + * widely deployed voice-privacy algorithm is at least as strong as the + * key. Meaning: "Does A5/1 have a work factor of at least 54 bits"? + * Absent a publicly available A5/1 reference implementation, this question + * could not be answered. We hope that our reference implementation below, + * which has been verified against official A5/1 test vectors, will provide + * the cryptographic community with the base on which to construct the + * answer to this important question. + * + * Initial indications about the strength of A5/1 are not encouraging. + * A variant of A5, while not A5/1 itself, has been estimated to have a + * work factor of well below 54 bits. See http://jya.com/crack-a5.htm for + * background information and references. + * + * With COMP128 broken and A5/1 published below, we will now turn our attention + * to A5/2. The latter has been acknowledged by the GSM community to have + * been specifically designed by intelligence agencies for lack of security. + * + */ + + +#include +#include +#include "A51.h" + +/* Masks for the three shift registers */ +#define R1MASK 0x07FFFF /* 19 bits, numbered 0..18 */ +#define R2MASK 0x3FFFFF /* 22 bits, numbered 0..21 */ +#define R3MASK 0x7FFFFF /* 23 bits, numbered 0..22 */ + +/* Middle bit of each of the three shift registers, for clock control */ +#define R1MID 0x000100 /* bit 8 */ +#define R2MID 0x000400 /* bit 10 */ +#define R3MID 0x000400 /* bit 10 */ + +/* The three shift registers. They're in global variables to make the code + * easier to understand. + * A better implementation would not use global variables. */ +word R1, R2, R3; + +/* Look at the middle bits of R1,R2,R3, take a vote, and + * return the majority value of those 3 bits. */ +bit majority() { + int sum; + sum = ((R1>>8)&1) + ((R2>>10)&1) + ((R3>>10)&1); + if (sum >= 2) + return 1; + else + return 0; +} + +/* Clock two or three of R1,R2,R3, with clock control + * according to their middle bits. + * Specifically, we clock Ri whenever Ri's middle bit + * agrees with the majority value of the three middle bits.*/ +void clock() { + bit maj = majority(); + if (((R1&R1MID)!=0) == maj) + R1 = ((R1<<1) & R1MASK) | (1 & (R1>>18 ^ R1>>17 ^ R1>>16 ^ R1>>13)); + if (((R2&R2MID)!=0) == maj) + R2 = ((R2<<1) & R2MASK) | (1 & (R2>>21 ^ R2>>20)); + if (((R3&R3MID)!=0) == maj) + R3 = ((R3<<1) & R3MASK) | (1 & (R3>>22 ^ R3>>21 ^ R3>>20 ^ R3>>7)); +} + +/* Clock all three of R1,R2,R3, ignoring their middle bits. + * This is only used for key setup. */ +void clockallthree() { + R1 = ((R1<<1) & R1MASK) | (1 & (R1>>18 ^ R1>>17 ^ R1>>16 ^ R1>>13)); + R2 = ((R2<<1) & R2MASK) | (1 & (R2>>21 ^ R2>>20)); + R3 = ((R3<<1) & R3MASK) | (1 & (R3>>22 ^ R3>>21 ^ R3>>20 ^ R3>>7)); +} + +/* Generate an output bit from the current state. + * You grab a bit from each register via the output generation taps; + * then you XOR the resulting three bits. */ +bit getbit() { + return ((R1>>18)^(R2>>21)^(R3>>22))&1; +} + +/* Do the A5/1 key setup. This routine accepts a 64-bit key and + * a 22-bit frame number. */ +void keysetup(byte key[8], word frame) { + int i; + bit keybit, framebit; + + /* Zero out the shift registers. */ + R1 = R2 = R3 = 0; + + /* Load the key into the shift registers, + * LSB of first byte of key array first, + * clocking each register once for every + * key bit loaded. (The usual clock + * control rule is temporarily disabled.) */ + for (i=0; i<64; i++) { + clockallthree(); /* always clock */ + keybit = (key[i/8] >> (i&7)) & 1; /* The i-th bit of the +key */ + R1 ^= keybit; R2 ^= keybit; R3 ^= keybit; + } + + /* Load the frame number into the shift + * registers, LSB first, + * clocking each register once for every + * key bit loaded. (The usual clock + * control rule is still disabled.) */ + for (i=0; i<22; i++) { + clockallthree(); /* always clock */ + framebit = (frame >> i) & 1; /* The i-th bit of the frame # +*/ + R1 ^= framebit; R2 ^= framebit; R3 ^= framebit; + } + + /* Run the shift registers for 100 clocks + * to mix the keying material and frame number + * together with output generation disabled, + * so that there is sufficient avalanche. + * We re-enable the majority-based clock control + * rule from now on. */ + for (i=0; i<100; i++) { + clock(); + } + + /* Now the key is properly set up. */ +} + +/* Generate output. We generate 228 bits of + * keystream output. The first 114 bits is for + * the A->B frame; the next 114 bits is for the + * B->A frame. You allocate a 15-byte buffer + * for each direction, and this function fills + * it in. */ +void run(byte AtoBkeystream[], byte BtoAkeystream[]) { + int i; + + /* Zero out the output buffers. */ + for (i=0; i<=113/8; i++) + AtoBkeystream[i] = BtoAkeystream[i] = 0; + + /* Generate 114 bits of keystream for the + * A->B direction. Store it, MSB first. */ + for (i=0; i<114; i++) { + clock(); + AtoBkeystream[i/8] |= getbit() << (7-(i&7)); + } + + /* Generate 114 bits of keystream for the + * B->A direction. Store it, MSB first. */ + for (i=0; i<114; i++) { + clock(); + BtoAkeystream[i/8] |= getbit() << (7-(i&7)); + } +} + +void A51_GSM( byte *key, int klen, int count, byte *block1, byte *block2 ) +{ + assert(klen == 64); + keysetup(key, count); // TODO - frame and count are not the same + run(block1, block2); +} diff --git a/A51.h b/A51.h new file mode 100644 index 0000000..d1337de --- /dev/null +++ b/A51.h @@ -0,0 +1,11 @@ + + +#include +#include + +typedef unsigned char byte; +typedef unsigned long word; +typedef word bit; + +void A51_GSM( byte *key, int klen, int count, byte *block1, byte *block2 ); + diff --git a/A51Test.cpp b/A51Test.cpp new file mode 100644 index 0000000..57ce467 --- /dev/null +++ b/A51Test.cpp @@ -0,0 +1,162 @@ +/* + * A pedagogical implementation of A5/1. + * + * Copyright (C) 1998-1999: Marc Briceno, Ian Goldberg, and David Wagner + * + * The source code below is optimized for instructional value and clarity. + * Performance will be terrible, but that's not the point. + * The algorithm is written in the C programming language to avoid ambiguities + * inherent to the English language. Complain to the 9th Circuit of Appeals + * if you have a problem with that. + * + * This software may be export-controlled by US law. + * + * This software is free for commercial and non-commercial use as long as + * the following conditions are aheared to. + * Copyright remains the authors' and as such any Copyright notices in + * the code are not to be removed. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The license and distribution terms for any publicly available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution license + * [including the GNU Public License.] + * + * Background: The Global System for Mobile communications is the most widely + * deployed cellular telephony system in the world. GSM makes use of + * four core cryptographic algorithms, neither of which has been published by + * the GSM MOU. This failure to subject the algorithms to public review is all + * the more puzzling given that over 100 million GSM + * subscribers are expected to rely on the claimed security of the system. + * + * The four core GSM algorithms are: + * A3 authentication algorithm + * A5/1 "strong" over-the-air voice-privacy algorithm + * A5/2 "weak" over-the-air voice-privacy algorithm + * A8 voice-privacy key generation algorithm + * + * In April of 1998, our group showed that COMP128, the algorithm used by the + * overwhelming majority of GSM providers for both A3 and A8 + * functionality was fatally flawed and allowed for cloning of GSM mobile + * phones. + * Furthermore, we demonstrated that all A8 implementations we could locate, + * including the few that did not use COMP128 for key generation, had been + * deliberately weakened by reducing the keyspace from 64 bits to 54 bits. + * The remaining 10 bits are simply set to zero! + * + * See http://www.scard.org/gsm for additional information. + * + * The question so far unanswered is if A5/1, the "stronger" of the two + * widely deployed voice-privacy algorithm is at least as strong as the + * key. Meaning: "Does A5/1 have a work factor of at least 54 bits"? + * Absent a publicly available A5/1 reference implementation, this question + * could not be answered. We hope that our reference implementation below, + * which has been verified against official A5/1 test vectors, will provide + * the cryptographic community with the base on which to construct the + * answer to this important question. + * + * Initial indications about the strength of A5/1 are not encouraging. + * A variant of A5, while not A5/1 itself, has been estimated to have a + * work factor of well below 54 bits. See http://jya.com/crack-a5.htm for + * background information and references. + * + * With COMP128 broken and A5/1 published below, we will now turn our attention + * to A5/2. The latter has been acknowledged by the GSM community to have + * been specifically designed by intelligence agencies for lack of security. + * + */ + + +#include +#include +#include +#include "./A51.h" + + +/* Test the code by comparing it against + * a known-good test vector. */ +void test() { + byte key[8] = {0x12, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}; + word frame = 0x134; + byte goodAtoB[15] = { 0x53, 0x4E, 0xAA, 0x58, 0x2F, 0xE8, 0x15, + 0x1A, 0xB6, 0xE1, 0x85, 0x5A, 0x72, 0x8C, 0x00 }; + byte goodBtoA[15] = { 0x24, 0xFD, 0x35, 0xA3, 0x5D, 0x5F, 0xB6, + 0x52, 0x6D, 0x32, 0xF9, 0x06, 0xDF, 0x1A, 0xC0 }; + byte AtoB[15], BtoA[15]; + int i, failed=0; + + A51_GSM(key, 64, frame, AtoB, BtoA); + + /* Compare against the test vector. */ + for (i=0; i<15; i++) + if (AtoB[i] != goodAtoB[i]) + failed = 1; + for (i=0; i<15; i++) + if (BtoA[i] != goodBtoA[i]) + failed = 1; + + /* Print some debugging output. */ + printf("key: 0x"); + for (i=0; i<8; i++) + printf("%02X", key[i]); + printf("\n"); + printf("frame number: 0x%06X\n", (unsigned int)frame); + printf("known good output:\n"); + printf(" A->B: 0x"); + for (i=0; i<15; i++) + printf("%02X", goodAtoB[i]); + printf(" B->A: 0x"); + for (i=0; i<15; i++) + printf("%02X", goodBtoA[i]); + printf("\n"); + printf("observed output:\n"); + printf(" A->B: 0x"); + for (i=0; i<15; i++) + printf("%02X", AtoB[i]); + printf(" B->A: 0x"); + for (i=0; i<15; i++) + printf("%02X", BtoA[i]); + printf("\n"); + + if (!failed) { + printf("Self-check succeeded: everything looks ok.\n"); + } else { + /* Problems! The test vectors didn't compare*/ + printf("\nI don't know why this broke; contact the authors.\n"); + exit(1); + } + + printf("time test\n"); + int n = 10000; + float t = clock(); + for (i = 0; i < n; i++) { + A51_GSM(key, 64, frame, AtoB, BtoA); + } + t = (clock() - t) / (CLOCKS_PER_SEC * (float)n); + printf("A51_GSM takes %g seconds per iteration\n", t); +} + +int main(void) { + test(); + return 0; +} diff --git a/BitVector.h b/BitVector.h index e244be7..63c4e91 100644 --- a/BitVector.h +++ b/BitVector.h @@ -351,9 +351,12 @@ class BitVector : public Vector { other.mData=NULL; } - void settfb(int i, int j) const + /** Set a bit */ + void settfb(size_t index, int value) { - mStart[i] = j; + char *dp = mStart+index; + assert(dp { /** Slice the whole signal into bits. */ BitVector sliced() const; + /** Return a soft bit. */ + float softbit(size_t index) const + { + const float *dp = mStart+index; + assert(dp #include -#ifdef DEBUG_CONFIG -#define debugLogEarly gLogEarly -#else -#define debugLogEarly -#endif - using namespace std; @@ -84,9 +78,24 @@ ConfigurationTable::ConfigurationTable(const char* filename, const char *wCmdNam if (!sqlite3_command(mDB,createConfigTable)) { gLogEarly(LOG_EMERG, "cannot create configuration table in database at %s, error message: %s", filename, sqlite3_errmsg(mDB)); } + // Set high-concurrency WAL mode. + if (!sqlite3_command(mDB,enableWAL)) { + gLogEarly(LOG_EMERG, "cannot enable WAL mode on database at %s, error message: %s", filename, sqlite3_errmsg(mDB)); + } // Build CommonLibs schema ConfigurationKey *tmp; + tmp = new ConfigurationKey("Control.NumSQLTries","3", + "attempts", + ConfigurationKey::DEVELOPER, + ConfigurationKey::VALRANGE, + "1:10",// educated guess + false, + "Number of times to retry SQL queries before declaring a database access failure." + ); + mSchema[tmp->getName()] = *tmp; + delete tmp; + tmp = new ConfigurationKey("Log.Alarms.Max","20", "alarms", ConfigurationKey::CUSTOMER, @@ -96,7 +105,7 @@ ConfigurationTable::ConfigurationTable(const char* filename, const char *wCmdNam "Maximum number of alarms to remember inside the application." ); mSchema[tmp->getName()] = *tmp; - free(tmp); + delete tmp; tmp = new ConfigurationKey("Log.File","", "", @@ -110,7 +119,7 @@ ConfigurationTable::ConfigurationTable(const char* filename, const char *wCmdNam "To disable again, execute \"unconfig Log.File\"." ); mSchema[tmp->getName()] = *tmp; - free(tmp); + delete tmp; tmp = new ConfigurationKey("Log.Level","NOTICE", "", @@ -128,7 +137,7 @@ ConfigurationTable::ConfigurationTable(const char* filename, const char *wCmdNam "Default logging level when no other level is defined for a file." ); mSchema[tmp->getName()] = *tmp; - free(tmp); + delete tmp; // Add application specific schema mSchema.insert(wSchema.begin(), wSchema.end()); @@ -150,12 +159,13 @@ string ConfigurationTable::getDefaultSQL(const std::string& program, const std:: ss << "-- rather in the program's ConfigurationKey schema." << endl; ss << "--" << endl; ss << "PRAGMA foreign_keys=OFF;" << endl; + ss << "PRAGMA journal_mode=WAL;" << 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; + ss << "CREATE TABLE IF NOT EXISTS 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("; + ss << "INSERT OR IGNORE INTO \"CONFIG\" VALUES("; // name ss << "'" << mp->first << "',"; // default @@ -269,7 +279,8 @@ bool ConfigurationTable::defines(const string& key) ScopedLock lock(mLock); return lookup(key).defined(); } catch (ConfigurationTableKeyNotFound) { - debugLogEarly(LOG_ALERT, "configuration parameter %s not found", key.c_str()); + // TODO: re-enable once we figure out why this message is being sent to syslog regardless of log level + //gLogEarly(LOG_DEBUG, "configuration parameter %s not found", key.c_str()); return false; } } @@ -580,7 +591,7 @@ string ConfigurationTable::getStr(const string& key) return lookup(key).value(); } catch (ConfigurationTableKeyNotFound) { // Raise an alert and re-throw the exception. - debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); + gLogEarly(LOG_DEBUG, "configuration parameter %s has no defined value", key.c_str()); throw ConfigurationTableKeyNotFound(key); } } @@ -592,7 +603,7 @@ bool ConfigurationTable::getBool(const string& key) return getNum(key) != 0; } catch (ConfigurationTableKeyNotFound) { // Raise an alert and re-throw the exception. - debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); + gLogEarly(LOG_DEBUG, "configuration parameter %s has no defined value", key.c_str()); throw ConfigurationTableKeyNotFound(key); } } @@ -606,7 +617,7 @@ long ConfigurationTable::getNum(const string& key) return lookup(key).number(); } catch (ConfigurationTableKeyNotFound) { // Raise an alert and re-throw the exception. - debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); + gLogEarly(LOG_DEBUG, "configuration parameter %s has no defined value", key.c_str()); throw ConfigurationTableKeyNotFound(key); } } @@ -619,7 +630,7 @@ float ConfigurationTable::getFloat(const string& key) 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()); + gLogEarly(LOG_DEBUG, "configuration parameter %s has no defined value", key.c_str()); throw ConfigurationTableKeyNotFound(key); } } @@ -634,7 +645,7 @@ std::vector ConfigurationTable::getVectorOfStrings(const string& key) line = strdup(rec.value().c_str()); } catch (ConfigurationTableKeyNotFound) { // Raise an alert and re-throw the exception. - debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); + gLogEarly(LOG_DEBUG, "configuration parameter %s has no defined value", key.c_str()); throw ConfigurationTableKeyNotFound(key); } @@ -665,7 +676,7 @@ std::vector ConfigurationTable::getVector(const string& key) line = strdup(rec.value().c_str()); } catch (ConfigurationTableKeyNotFound) { // Raise an alert and re-throw the exception. - debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); + gLogEarly(LOG_DEBUG, "configuration parameter %s has no defined value", key.c_str()); throw ConfigurationTableKeyNotFound(key); } @@ -753,7 +764,13 @@ bool ConfigurationTable::set(const string& key, const string& value) { assert(mDB); ScopedLock lock(mLock); - string cmd = "INSERT OR REPLACE INTO CONFIG (KEYSTRING,VALUESTRING,OPTIONAL) VALUES (\"" + key + "\",\"" + value + "\",1)"; + string cmd; + if (keyDefinedInSchema(key)) { + cmd = "INSERT OR REPLACE INTO CONFIG (KEYSTRING,VALUESTRING,OPTIONAL,COMMENTS) VALUES (\"" + key + "\",\"" + value + "\",1,\'" + mSchema[key].getDescription() + "\')"; + } else { + cmd = "INSERT OR REPLACE INTO CONFIG (KEYSTRING,VALUESTRING,OPTIONAL) VALUES (\"" + key + "\",\"" + value + "\",1)"; + } + bool success = sqlite3_command(mDB,cmd.c_str()); // Cache the result. if (success) mCache[key] = ConfigurationRecord(value); @@ -767,18 +784,6 @@ bool ConfigurationTable::set(const string& key, long value) return set(key,buffer); } - -bool ConfigurationTable::set(const string& key) -{ - assert(mDB); - ScopedLock lock(mLock); - string cmd = "INSERT OR REPLACE INTO CONFIG (KEYSTRING,VALUESTRING,OPTIONAL) VALUES (\"" + key + "\",NULL,1)"; - bool success = sqlite3_command(mDB,cmd.c_str()); - if (success) mCache[key] = ConfigurationRecord(true); - return success; -} - - void ConfigurationTable::checkCacheAge() { // mLock is set by caller diff --git a/Configuration.h b/Configuration.h index cd4838e..959cdf8 100644 --- a/Configuration.h +++ b/Configuration.h @@ -173,6 +173,7 @@ typedef std::map ConfigurationRecordMap; typedef std::map ConfigurationMap; class ConfigurationKey; typedef std::map ConfigurationKeyMap; +ConfigurationKeyMap getConfigurationKeys(); /** A class for maintaining a configuration key-value table, @@ -263,9 +264,6 @@ class ConfigurationTable { /** Set or change a value in the table. */ bool set(const std::string& key, long value); - /** Create an entry in the table, no value though. */ - bool set(const std::string& key); - /** Remove an entry from the table. Will not alter required values. diff --git a/ConfigurationTest.cpp b/ConfigurationTest.cpp index 2fd43e9..290db64 100644 --- a/ConfigurationTest.cpp +++ b/ConfigurationTest.cpp @@ -121,7 +121,7 @@ ConfigurationKeyMap getConfigurationKeys() "" ); map[tmp->getName()] = *tmp; - free(tmp); + delete tmp; tmp = new ConfigurationKey("numnumber","42", "", @@ -132,7 +132,7 @@ ConfigurationKeyMap getConfigurationKeys() "" ); map[tmp->getName()] = *tmp; - free(tmp); + delete tmp; tmp = new ConfigurationKey("newstring","new string value", "", @@ -143,7 +143,7 @@ ConfigurationKeyMap getConfigurationKeys() "" ); map[tmp->getName()] = *tmp; - free(tmp); + delete tmp; return map; } diff --git a/Makefile.am b/Makefile.am index 26a55ed..3c4e5bf 100644 --- a/Makefile.am +++ b/Makefile.am @@ -41,7 +41,8 @@ libcommon_la_SOURCES = \ Configuration.cpp \ sqlite3util.cpp \ URLEncode.cpp \ - Utils.cpp + Utils.cpp \ + A51.cpp noinst_PROGRAMS = \ BitVectorTest \ @@ -53,7 +54,8 @@ noinst_PROGRAMS = \ ConfigurationTest \ LogTest \ URLEncodeTest \ - F16Test + F16Test \ + A51Test # ReportingTest @@ -72,7 +74,8 @@ noinst_HEADERS = \ URLEncode.h \ Utils.h \ Logger.h \ - sqlite3util.h + sqlite3util.h \ + A51.h URLEncodeTest_SOURCES = URLEncodeTest.cpp URLEncodeTest_LDADD = libcommon.la @@ -108,6 +111,9 @@ LogTest_LDADD = libcommon.la $(SQLITE_LA) F16Test_SOURCES = F16Test.cpp +A51Test_SOURCES = A51Test.cpp +A51Test_LDADD = libcommon.la + MOSTLYCLEANFILES += testSource testDestination diff --git a/Reporting.cpp b/Reporting.cpp index 3ea7eed..6782186 100644 --- a/Reporting.cpp +++ b/Reporting.cpp @@ -1,6 +1,6 @@ /**@file Module for performance-reporting mechanisms. */ /* -* Copyright 2012 Range Networks, Inc. +* Copyright 2012, 2013 Range Networks, Inc. * * This software is distributed under the terms of the GNU Affero Public License. * See the COPYING file in the main directory for details. @@ -53,11 +53,25 @@ ReportingTable::ReportingTable(const char* filename) if (!sqlite3_command(mDB,createReportingTable)) { gLogEarly(LOG_EMERG | mFacility, "cannot create reporting table in database at %s, error message: %s", filename, sqlite3_errmsg(mDB)); } + // Set high-concurrency WAL mode. + if (!sqlite3_command(mDB,enableWAL)) { + gLogEarly(LOG_EMERG | mFacility, "Cannot enable WAL mode on database at %s, error message: %s", filename, sqlite3_errmsg(mDB)); + } + // Start the commit thread + mBatchCommitter.start((void*(*)(void*))reportingBatchCommitter,NULL); } bool ReportingTable::create(const char* paramName) { + // add this report name to the batch map + mLock.lock(); + if (mBatch.find(paramName) == mBatch.end()) { + mBatch[paramName] = 0; + } + mLock.unlock(); + + // and to the database char cmd[200]; sprintf(cmd,"INSERT OR IGNORE INTO REPORTING (NAME,CLEAREDTIME) VALUES (\"%s\",%ld)", paramName, time(NULL)); if (!sqlite3_command(mDB,cmd)) { @@ -71,12 +85,10 @@ bool ReportingTable::create(const char* paramName) bool ReportingTable::incr(const char* paramName) { - char cmd[200]; - sprintf(cmd,"UPDATE REPORTING SET VALUE=VALUE+1, UPDATETIME=%ld WHERE NAME=\"%s\"", time(NULL), paramName); - if (!sqlite3_command(mDB,cmd)) { - gLogEarly(LOG_CRIT|mFacility, "cannot increment reporting parameter %s, error message: %s", paramName, sqlite3_errmsg(mDB)); - return false; - } + mLock.lock(); + mBatch[paramName]++; + mLock.unlock(); + return true; } @@ -106,6 +118,19 @@ bool ReportingTable::clear(const char* paramName) } + +bool ReportingTable::clear() +{ + char cmd[200]; + sprintf(cmd,"UPDATE REPORTING SET VALUE=0, UPDATETIME=0, CLEAREDTIME=%ld", time(NULL)); + if (!sqlite3_command(mDB,cmd)) { + gLogEarly(LOG_CRIT|mFacility, "cannot clear reporting table, error message: %s", sqlite3_errmsg(mDB)); + return false; + } + return true; +} + + bool ReportingTable::create(const char* baseName, unsigned minIndex, unsigned maxIndex) { size_t sz = strlen(baseName); @@ -140,6 +165,53 @@ bool ReportingTable::clear(const char* baseName, unsigned index) return clear(name); } +bool ReportingTable::commit() +{ + ReportBatch oustanding; + ReportBatch::iterator mp; + unsigned oustandingCount = 0; + // copy out to free up access to mBatch as quickly as possible + mLock.lock(); + mp = mBatch.begin(); + while (mp != mBatch.end()) { + if (mp->second > 0) { + oustanding[mp->first] = mp->second; + mBatch[mp->first] = 0; + oustandingCount++; + } + mp++; + } + mLock.unlock(); + // now actually write them into the db if needed + if (oustandingCount > 0) { + Timeval timer; + char cmd[200]; + // TODO : could wrap this in a BEGIN; COMMIT; pair to transactionize these X UPDATEs + mp = oustanding.begin(); + while (mp != oustanding.end()) { + sprintf(cmd,"UPDATE REPORTING SET VALUE=VALUE+%u, UPDATETIME=%ld WHERE NAME=\"%s\"", mp->second, time(NULL), mp->first.c_str()); + if (!sqlite3_command(mDB,cmd)) { + LOG(CRIT) << "could not increment reporting parameter " << mp->first << ", error message: " << sqlite3_errmsg(mDB); + } + mp++; + } + + LOG(INFO) << "wrote " << oustandingCount << " entries in " << timer.elapsed() << "ms"; + } + + return true; +} + +extern ReportingTable gReports; +void* reportingBatchCommitter(void*) +{ + while (true) { + sleep(10); + gReports.commit(); + } + + return NULL; +} diff --git a/Reporting.h b/Reporting.h index 1878618..399727c 100644 --- a/Reporting.h +++ b/Reporting.h @@ -1,6 +1,6 @@ /**@file Module for performance-reporting mechanisms. */ /* -* Copyright 2012 Range Networks, Inc. +* Copyright 2012, 2013 Range Networks, Inc. * * This software is distributed under the terms of the GNU Affero Public License. * See the COPYING file in the main directory for details. @@ -28,7 +28,12 @@ #include #include +#include +#include +#include +#include +typedef std::map ReportBatch; /** Collect performance statistics into a database. @@ -40,6 +45,9 @@ class ReportingTable { sqlite3* mDB; ///< database connection int mFacility; ///< rsyslogd facility + ReportBatch mBatch; ///< batch of report updates, not yet stored in the db + mutable Mutex mLock; ///< control for multithreaded read/write access to the batch + Thread mBatchCommitter; ///< thread responsible for committing batches of report updates to the db @@ -69,6 +77,9 @@ class ReportingTable { /** Take a max of an indexed parameter. */ bool max(const char* paramName, unsigned index, unsigned newVal); + /** Clear the whole table. */ + bool clear(); + /** Clear a value. */ bool clear(const char* paramName); @@ -78,8 +89,13 @@ class ReportingTable { /** Dump the database to a stream. */ void dump(std::ostream&) const; + /** Commit outstanding report updates to the database */ + bool commit(); }; +/** Periodically triggers ReportingTable::commit(). */ +void* reportingBatchCommitter(void*); + #endif diff --git a/ReportingTest.cpp b/ReportingTest.cpp new file mode 100644 index 0000000..a0d874b --- /dev/null +++ b/ReportingTest.cpp @@ -0,0 +1,63 @@ +/* +* Copyright 2012 Range Networks, 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 . + +*/ + + +#include "stdlib.h" +#include +#include + +#include "Reporting.h" + + +int main(int argc, char *argv[]) +{ + + srandom(time(NULL)); + + ReportingTable rpt("./test.db",LOG_LOCAL7); + + rpt.create("count1"); + rpt.create("count2"); + rpt.create("max1"); + rpt.create("max2"); + + rpt.incr("count1"); + rpt.incr("count2"); + rpt.incr("count1"); + + rpt.clear("max1"); + rpt.max("max1",random()); + rpt.max("max2",random()); + + rpt.create("indexed",5,10); + for (int i=0; i<20; i++) { + rpt.incr("indexed",random()%6 + 5); + } + + rpt.create("indexedMax",5,10); + for (int i=5; i<=10; i++) { + rpt.max("indexedMax", i, random()); + } + +} diff --git a/Utils.cpp b/Utils.cpp index 1da95fa..f06420d 100644 --- a/Utils.cpp +++ b/Utils.cpp @@ -193,7 +193,7 @@ std::ostream& operator<<(std::ostream& os, const Statistic &stat) { stat std::string replaceAll(const std::string input, const std::string search, const std::string replace) { std::string output = input; - int index = 0; + unsigned index = 0; while (true) { index = output.find(search, index); diff --git a/example.config b/example.config index ead1ca5..1c98f48 100644 --- a/example.config +++ b/example.config @@ -12,7 +12,7 @@ key5 10 11 13 15 # This file is also used to test the logging system. -Log.Alarms.Max 10 +Log.Alarms.Max 20 Log.Alarms.TargetIP 127.0.0.1 Log.Alarms.TargetPort 10101 -Log.Level INFO +Log.Level NOTICE diff --git a/sqlite3util.cpp b/sqlite3util.cpp index 036ef2c..2500c51 100644 --- a/sqlite3util.cpp +++ b/sqlite3util.cpp @@ -15,15 +15,21 @@ // Wrappers to sqlite operations. // These will eventually get moved to commonlibs. -int sqlite3_prepare_statement(sqlite3* DB, sqlite3_stmt **stmt, const char* query) +const char* enableWAL = { + "PRAGMA journal_mode=WAL" +}; + +int sqlite3_prepare_statement(sqlite3* DB, sqlite3_stmt **stmt, const char* query, unsigned retries) { int src = SQLITE_BUSY; - while (src==SQLITE_BUSY) { - src = sqlite3_prepare_v2(DB,query,strlen(query),stmt,NULL); - if (src==SQLITE_BUSY) { - usleep(100000); - } - } + + for (unsigned i = 0; i < retries; i++) { + src = sqlite3_prepare_v2(DB,query,strlen(query),stmt,NULL); + if (src != SQLITE_BUSY && src != SQLITE_LOCKED) { + break; + } + usleep(200); + } if (src) { fprintf(stderr,"sqlite3_prepare_v2 failed for \"%s\": %s\n",query,sqlite3_errmsg(DB)); sqlite3_finalize(*stmt); @@ -31,15 +37,17 @@ int sqlite3_prepare_statement(sqlite3* DB, sqlite3_stmt **stmt, const char* quer return src; } -int sqlite3_run_query(sqlite3* DB, sqlite3_stmt *stmt) +int sqlite3_run_query(sqlite3* DB, sqlite3_stmt *stmt, unsigned retries) { int src = SQLITE_BUSY; - while (src==SQLITE_BUSY) { - src = sqlite3_step(stmt); - if (src==SQLITE_BUSY) { - usleep(100000); - } - } + + for (unsigned i = 0; i < retries; i++) { + src = sqlite3_step(stmt); + if (src != SQLITE_BUSY && src != SQLITE_LOCKED) { + break; + } + usleep(200); + } if ((src!=SQLITE_DONE) && (src!=SQLITE_ROW)) { fprintf(stderr,"sqlite3_run_query failed: %s: %s\n", sqlite3_sql(stmt), sqlite3_errmsg(DB)); } @@ -48,16 +56,16 @@ int sqlite3_run_query(sqlite3* DB, sqlite3_stmt *stmt) bool sqlite3_exists(sqlite3* DB, const char *tableName, - const char* keyName, const char* keyData) + const char* keyName, const char* keyData, unsigned retries) { size_t stringSize = 100 + strlen(tableName) + strlen(keyName) + strlen(keyData); char query[stringSize]; sprintf(query,"SELECT * FROM %s WHERE %s == \"%s\"",tableName,keyName,keyData); // Prepare the statement. sqlite3_stmt *stmt; - if (sqlite3_prepare_statement(DB,&stmt,query)) return false; + if (sqlite3_prepare_statement(DB,&stmt,query,retries)) return false; // Read the result. - int src = sqlite3_run_query(DB,stmt); + int src = sqlite3_run_query(DB,stmt,retries); sqlite3_finalize(stmt); // Anything there? return (src == SQLITE_ROW); @@ -67,16 +75,16 @@ bool sqlite3_exists(sqlite3* DB, const char *tableName, bool sqlite3_single_lookup(sqlite3* DB, const char *tableName, const char* keyName, const char* keyData, - const char* valueName, unsigned &valueData) + const char* valueName, unsigned &valueData, unsigned retries) { size_t stringSize = 100 + strlen(valueName) + strlen(tableName) + strlen(keyName) + strlen(keyData); char query[stringSize]; sprintf(query,"SELECT %s FROM %s WHERE %s == \"%s\"",valueName,tableName,keyName,keyData); // Prepare the statement. sqlite3_stmt *stmt; - if (sqlite3_prepare_statement(DB,&stmt,query)) return false; + if (sqlite3_prepare_statement(DB,&stmt,query,retries)) return false; // Read the result. - int src = sqlite3_run_query(DB,stmt); + int src = sqlite3_run_query(DB,stmt,retries); bool retVal = false; if (src == SQLITE_ROW) { valueData = (unsigned)sqlite3_column_int64(stmt,0); @@ -90,7 +98,7 @@ bool sqlite3_single_lookup(sqlite3* DB, const char *tableName, // This function returns an allocated string that must be free'd by the caller. bool sqlite3_single_lookup(sqlite3* DB, const char* tableName, const char* keyName, const char* keyData, - const char* valueName, char* &valueData) + const char* valueName, char* &valueData, unsigned retries) { valueData=NULL; size_t stringSize = 100 + strlen(valueName) + strlen(tableName) + strlen(keyName) + strlen(keyData); @@ -98,9 +106,9 @@ bool sqlite3_single_lookup(sqlite3* DB, const char* tableName, sprintf(query,"SELECT %s FROM %s WHERE %s == \"%s\"",valueName,tableName,keyName,keyData); // Prepare the statement. sqlite3_stmt *stmt; - if (sqlite3_prepare_statement(DB,&stmt,query)) return false; + if (sqlite3_prepare_statement(DB,&stmt,query,retries)) return false; // Read the result. - int src = sqlite3_run_query(DB,stmt); + int src = sqlite3_run_query(DB,stmt,retries); bool retVal = false; if (src == SQLITE_ROW) { const char* ptr = (const char*)sqlite3_column_text(stmt,0); @@ -115,7 +123,7 @@ bool sqlite3_single_lookup(sqlite3* DB, const char* tableName, // This function returns an allocated string that must be free'd by tha caller. bool sqlite3_single_lookup(sqlite3* DB, const char* tableName, const char* keyName, unsigned keyData, - const char* valueName, char* &valueData) + const char* valueName, char* &valueData, unsigned retries) { valueData=NULL; size_t stringSize = 100 + strlen(valueName) + strlen(tableName) + strlen(keyName) + 20; @@ -123,9 +131,9 @@ bool sqlite3_single_lookup(sqlite3* DB, const char* tableName, sprintf(query,"SELECT %s FROM %s WHERE %s == %u",valueName,tableName,keyName,keyData); // Prepare the statement. sqlite3_stmt *stmt; - if (sqlite3_prepare_statement(DB,&stmt,query)) return false; + if (sqlite3_prepare_statement(DB,&stmt,query,retries)) return false; // Read the result. - int src = sqlite3_run_query(DB,stmt); + int src = sqlite3_run_query(DB,stmt,retries); bool retVal = false; if (src == SQLITE_ROW) { const char* ptr = (const char*)sqlite3_column_text(stmt,0); @@ -139,15 +147,15 @@ bool sqlite3_single_lookup(sqlite3* DB, const char* tableName, -bool sqlite3_command(sqlite3* DB, const char* query) +bool sqlite3_command(sqlite3* DB, const char* query, unsigned retries) { // Prepare the statement. sqlite3_stmt *stmt; - if (sqlite3_prepare_statement(DB,&stmt,query)) return false; + if (sqlite3_prepare_statement(DB,&stmt,query,retries)) return false; // Run the query. - int src = sqlite3_run_query(DB,stmt); + int src = sqlite3_run_query(DB,stmt,retries); sqlite3_finalize(stmt); - return src==SQLITE_DONE; + return (src==SQLITE_DONE || src==SQLITE_OK || src==SQLITE_ROW); } diff --git a/sqlite3util.h b/sqlite3util.h index f2b3aa7..e429e63 100644 --- a/sqlite3util.h +++ b/sqlite3util.h @@ -3,27 +3,33 @@ #include -int sqlite3_prepare_statement(sqlite3* DB, sqlite3_stmt **stmt, const char* query); +// (pat) Dont put statics in .h files - they generate a zillion g++ error messages. +extern const char *enableWAL; +//static const char* enableWAL = { +// "PRAGMA journal_mode=WAL" +//}; -int sqlite3_run_query(sqlite3* DB, sqlite3_stmt *stmt); +int sqlite3_prepare_statement(sqlite3* DB, sqlite3_stmt **stmt, const char* query, unsigned retries = 5); + +int sqlite3_run_query(sqlite3* DB, sqlite3_stmt *stmt, unsigned retries = 5); bool sqlite3_single_lookup(sqlite3* DB, const char *tableName, const char* keyName, const char* keyData, - const char* valueName, unsigned &valueData); + const char* valueName, unsigned &valueData, unsigned retries = 5); bool sqlite3_single_lookup(sqlite3* DB, const char* tableName, const char* keyName, const char* keyData, - const char* valueName, char* &valueData); + const char* valueName, char* &valueData, unsigned retries = 5); // This function returns an allocated string that must be free'd by the caller. bool sqlite3_single_lookup(sqlite3* DB, const char* tableName, const char* keyName, unsigned keyData, - const char* valueName, char* &valueData); + const char* valueName, char* &valueData, unsigned retries = 5); bool sqlite3_exists(sqlite3* DB, const char* tableName, - const char* keyName, const char* keyData); + const char* keyName, const char* keyData, unsigned retries = 5); /** Run a query, ignoring the result; return true on success. */ -bool sqlite3_command(sqlite3* DB, const char* query); +bool sqlite3_command(sqlite3* DB, const char* query, unsigned retries = 5); #endif