summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Ierymenko <adam.ierymenko@gmail.com>2016-08-12 11:30:27 -0700
committerAdam Ierymenko <adam.ierymenko@gmail.com>2016-08-12 11:30:27 -0700
commitc30f74987ff9027e3f7be3a9f400134c17d555e6 (patch)
treeee027622d3d73eeaeb5956ebba53306b59621c1f
parentdd21c8a577f8bfba9caa58c7567893b2ec10aef9 (diff)
downloadinfinitytier-c30f74987ff9027e3f7be3a9f400134c17d555e6.tar.gz
infinitytier-c30f74987ff9027e3f7be3a9f400134c17d555e6.zip
Starting refactor of controller...
-rw-r--r--controller/SqliteNetworkController.cpp672
-rw-r--r--controller/SqliteNetworkController.hpp74
-rw-r--r--controller/schema.sql24
-rw-r--r--ext/offbase/README.md59
-rw-r--r--ext/offbase/json/LICENSE.MIT (renamed from ext/json/LICENSE.MIT)0
-rw-r--r--ext/offbase/json/README.md (renamed from ext/json/README.md)0
-rw-r--r--ext/offbase/json/json.hpp (renamed from ext/json/json.hpp)0
-rw-r--r--ext/offbase/offbase.hpp393
8 files changed, 584 insertions, 638 deletions
diff --git a/controller/SqliteNetworkController.cpp b/controller/SqliteNetworkController.cpp
index 81017897..56bddbc3 100644
--- a/controller/SqliteNetworkController.cpp
+++ b/controller/SqliteNetworkController.cpp
@@ -37,16 +37,11 @@
#include <utility>
#include <stdexcept>
#include <set>
+#include <map>
#include "../include/ZeroTierOne.h"
#include "../node/Constants.hpp"
-#ifdef ZT_USE_SYSTEM_JSON_PARSER
-#include <json-parser/json.h>
-#else
-#include "../ext/json-parser/json.h"
-#endif
-
#include "SqliteNetworkController.hpp"
#include "../node/Node.hpp"
@@ -60,17 +55,17 @@
#include "../osdep/OSUtils.hpp"
-// Include ZT_NETCONF_SCHEMA_SQL constant to init database
-#include "schema.sql.c"
+// offbase includes and builds upon nlohmann::json
+using json = nlohmann::json;
// Stored in database as schemaVersion key in Config.
// If not present, database is assumed to be empty and at the current schema version
// and this key/value is added automatically.
-#define ZT_NETCONF_SQLITE_SCHEMA_VERSION 5
-#define ZT_NETCONF_SQLITE_SCHEMA_VERSION_STR "5"
+//#define ZT_NETCONF_SQLITE_SCHEMA_VERSION 5
+//#define ZT_NETCONF_SQLITE_SCHEMA_VERSION_STR "5"
// API version reported via JSON control plane
-#define ZT_NETCONF_CONTROLLER_API_VERSION 2
+#define ZT_NETCONF_CONTROLLER_API_VERSION 3
// Number of requests to remember in member history
#define ZT_NETCONF_DB_MEMBER_HISTORY_LENGTH 8
@@ -79,155 +74,19 @@
#define ZT_NETCONF_MIN_REQUEST_PERIOD 1000
// Delay between backups in milliseconds
-#define ZT_NETCONF_BACKUP_PERIOD 300000
+//#define ZT_NETCONF_BACKUP_PERIOD 300000
// Nodes are considered active if they've queried in less than this long
#define ZT_NETCONF_NODE_ACTIVE_THRESHOLD ((ZT_NETWORK_AUTOCONF_DELAY * 2) + 5000)
-// Flags for Network 'flags' field in table
-#define ZT_DB_NETWORK_FLAG_ZT_MANAGED_V4_AUTO_ASSIGN 1
-#define ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_RFC4193 2
-#define ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_6PLANE 4
-#define ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_AUTO_ASSIGN 8
-
-// Flags with all V6 managed mode flags flipped off -- for masking in update operation and in string form for SQL building
-#define ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_MASK_S "268435441"
-
-// Uncomment to trace Sqlite for debugging
-//#define ZT_NETCONF_SQLITE_TRACE 1
-
namespace ZeroTier {
-namespace {
-
-static std::string _jsonEscape(const char *s)
-{
- if (!s)
- return std::string();
- std::string buf;
- for(const char *p=s;(*p);++p) {
- switch(*p) {
- case '\t': buf.append("\\t"); break;
- case '\b': buf.append("\\b"); break;
- case '\r': buf.append("\\r"); break;
- case '\n': buf.append("\\n"); break;
- case '\f': buf.append("\\f"); break;
- case '"': buf.append("\\\""); break;
- case '\\': buf.append("\\\\"); break;
- case '/': buf.append("\\/"); break;
- default: buf.push_back(*p); break;
- }
- }
- return buf;
-}
-static std::string _jsonEscape(const std::string &s) { return _jsonEscape(s.c_str()); }
-
-// Converts an InetAddress to a blob and an int for storage in database
-static void _ipToBlob(const InetAddress &a,char *ipBlob,int &ipVersion) /* blob[16] */
-{
- switch(a.ss_family) {
- case AF_INET:
- memset(ipBlob,0,12);
- memcpy(ipBlob + 12,a.rawIpData(),4);
- ipVersion = 4;
- break;
- case AF_INET6:
- memcpy(ipBlob,a.rawIpData(),16);
- ipVersion = 6;
- break;
- }
-}
-
-// Member.recentHistory is stored in a BLOB as an array of strings containing JSON objects.
-// This is kind of hacky but efficient and quick to parse and send to the client.
-class MemberRecentHistory : public std::list<std::string>
-{
-public:
- inline std::string toBlob() const
- {
- std::string b;
- for(const_iterator i(begin());i!=end();++i) {
- b.append(*i);
- b.push_back((char)0);
- }
- return b;
- }
-
- inline void fromBlob(const char *blob,unsigned int len)
- {
- for(unsigned int i=0,k=0;i<len;++i) {
- if (!blob[i]) {
- push_back(std::string(blob + k,i - k));
- k = i + 1;
- }
- }
- }
-};
-
-struct MemberRecord
-{
- sqlite3_int64 rowid;
- char nodeId[16];
- bool authorized;
- bool activeBridge;
- uint64_t lastRequestTime;
- MemberRecentHistory recentHistory;
-
- MemberRecord() :
- rowid(0),
- authorized(false),
- activeBridge(false),
- lastRequestTime(0)
- {
- nodeId[0] = (char)0;
- }
-};
-
-struct NetworkRecord
-{
- char id[24];
- const char *name;
- int flags;
- bool isPrivate;
- bool enableBroadcast;
- bool allowPassiveBridging;
- int multicastLimit;
- uint64_t creationTime;
- uint64_t revision;
- uint64_t memberRevisionCounter;
-
- NetworkRecord() :
- name((const char *)0),
- flags(0),
- isPrivate(true),
- enableBroadcast(false),
- allowPassiveBridging(false),
- multicastLimit(0),
- creationTime(0),
- revision(0),
- memberRevisionCounter(0)
- {
- id[0] = (char)0;
- }
-};
-
-#ifdef ZT_NETCONF_SQLITE_TRACE
-static void sqliteTraceFunc(void *ptr,const char *s)
-{
- fprintf(stderr,"SQLITE: %s\n",s);
-}
-#endif
-
-} // anonymous namespace
-
SqliteNetworkController::SqliteNetworkController(Node *node,const char *dbPath,const char *circuitTestPath) :
_node(node),
- _backupThreadRun(true),
- _backupNeeded(true),
- _dbPath(dbPath),
- _circuitTestPath(circuitTestPath),
- _db((sqlite3 *)0)
+ _db(dbPath),
+ _dbCommitThreadRun(true)
{
+ /*
if (sqlite3_open_v2(dbPath,&_db,SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE,(const char *)0) != SQLITE_OK)
throw std::runtime_error("SqliteNetworkController cannot open database file");
sqlite3_busy_timeout(_db,10000);
@@ -337,11 +196,12 @@ SqliteNetworkController::SqliteNetworkController(Node *node,const char *dbPath,c
if (schemaVersion < 5) {
// Upgrade old rough draft Rule table to new release format
if (sqlite3_exec(_db,
+ "DROP TABLE Relay;\n"
"DROP INDEX Rule_networkId_ruleNo;\n"
"ALTER TABLE \"Rule\" RENAME TO RuleOld;\n"
"CREATE TABLE Rule (\n"
" networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"
- " policyId varchar(32),\n"
+ " capId integer,\n"
" ruleNo integer NOT NULL,\n"
" ruleType integer NOT NULL DEFAULT(0),\n"
" \"addr\" blob(16),\n"
@@ -353,8 +213,16 @@ SqliteNetworkController::SqliteNetworkController(Node *node,const char *dbPath,c
"INSERT INTO \"Rule\" SELECT networkId,(ruleNo*2) AS ruleNo,37 AS \"ruleType\",etherType AS \"int1\" FROM RuleOld WHERE RuleOld.etherType IS NOT NULL AND RuleOld.etherType > 0;\n"
"INSERT INTO \"Rule\" SELECT networkId,((ruleNo*2)+1) AS ruleNo,1 AS \"ruleType\" FROM RuleOld;\n"
"DROP TABLE RuleOld;\n"
- "CREATE INDEX Rule_networkId_ruleNo ON Rule (networkId, ruleNo);\n"
- "CREATE INDEX Rule_networkId_policyId ON Rule (networkId, policyId);\n"
+ "CREATE INDEX Rule_networkId_capId ON Rule (networkId,capId);\n"
+ "CREATE TABLE MemberTC (\n"
+ " networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"
+ " nodeId char(10) NOT NULL REFERENCES Node(id) ON DELETE CASCADE,\n"
+ " tagId integer,\n"
+ " tagValue integer,\n"
+ " capId integer,\n"
+ " capMaxCustodyChainLength integer NOT NULL DEFAULT(1)\n"
+ ");\n"
+ "CREATE INDEX MemberTC_networkId_nodeId ON MemberTC (networkId,nodeId);\n"
"UPDATE \"Config\" SET \"v\" = 5 WHERE \"k\" = 'schemaVersion';\n"
,0,0,0) != SQLITE_OK) {
char err[1024];
@@ -383,7 +251,6 @@ SqliteNetworkController::SqliteNetworkController(Node *node,const char *dbPath,c
if (
- /* Network */
(sqlite3_prepare_v2(_db,"SELECT name,private,enableBroadcast,allowPassiveBridging,\"flags\",multicastLimit,creationTime,revision,memberRevisionCounter,(SELECT COUNT(1) FROM Member WHERE Member.networkId = Network.id AND Member.authorized > 0) FROM Network WHERE id = ?",-1,&_sGetNetworkById,(const char **)0) != SQLITE_OK)
||(sqlite3_prepare_v2(_db,"SELECT revision FROM Network WHERE id = ?",-1,&_sGetNetworkRevision,(const char **)0) != SQLITE_OK)
||(sqlite3_prepare_v2(_db,"UPDATE Network SET revision = ? WHERE id = ?",-1,&_sSetNetworkRevision,(const char **)0) != SQLITE_OK)
@@ -392,33 +259,23 @@ SqliteNetworkController::SqliteNetworkController(Node *node,const char *dbPath,c
||(sqlite3_prepare_v2(_db,"SELECT id FROM Network ORDER BY id ASC",-1,&_sListNetworks,(const char **)0) != SQLITE_OK)
||(sqlite3_prepare_v2(_db,"UPDATE Network SET memberRevisionCounter = (memberRevisionCounter + 1) WHERE id = ?",-1,&_sIncrementMemberRevisionCounter,(const char **)0) != SQLITE_OK)
- /* Node */
||(sqlite3_prepare_v2(_db,"SELECT identity FROM Node WHERE id = ?",-1,&_sGetNodeIdentity,(const char **)0) != SQLITE_OK)
||(sqlite3_prepare_v2(_db,"INSERT OR REPLACE INTO Node (id,identity) VALUES (?,?)",-1,&_sCreateOrReplaceNode,(const char **)0) != SQLITE_OK)
- /* Rule */
||(sqlite3_prepare_v2(_db,"INSERT INTO Rule (networkId,ruleNo,nodeId,ztSource,ztDest,vlanId,vlanPcp,vlanDei,) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",-1,&_sCreateRule,(const char **)0) != SQLITE_OK)
||(sqlite3_prepare_v2(_db,"SELECT ruleNo,nodeId,sourcePort,destPort,vlanId,vlanPcp,etherType,macSource,macDest,ipSource,ipDest,ipTos,ipProtocol,ipSourcePort,ipDestPort,\"flags\",invFlags,\"action\" FROM Rule WHERE networkId = ? ORDER BY ruleNo ASC",-1,&_sListRules,(const char **)0) != SQLITE_OK)
||(sqlite3_prepare_v2(_db,"DELETE FROM Rule WHERE networkId = ?",-1,&_sDeleteRulesForNetwork,(const char **)0) != SQLITE_OK)
- /* IpAssignmentPool */
||(sqlite3_prepare_v2(_db,"SELECT ipRangeStart,ipRangeEnd FROM IpAssignmentPool WHERE networkId = ? AND ipVersion = ?",-1,&_sGetIpAssignmentPools,(const char **)0) != SQLITE_OK)
||(sqlite3_prepare_v2(_db,"SELECT ipRangeStart,ipRangeEnd,ipVersion FROM IpAssignmentPool WHERE networkId = ? ORDER BY ipRangeStart ASC",-1,&_sGetIpAssignmentPools2,(const char **)0) != SQLITE_OK)
||(sqlite3_prepare_v2(_db,"INSERT INTO IpAssignmentPool (networkId,ipRangeStart,ipRangeEnd,ipVersion) VALUES (?,?,?,?)",-1,&_sCreateIpAssignmentPool,(const char **)0) != SQLITE_OK)
||(sqlite3_prepare_v2(_db,"DELETE FROM IpAssignmentPool WHERE networkId = ?",-1,&_sDeleteIpAssignmentPoolsForNetwork,(const char **)0) != SQLITE_OK)
- /* IpAssignment */
||(sqlite3_prepare_v2(_db,"SELECT ip,ipNetmaskBits,ipVersion FROM IpAssignment WHERE networkId = ? AND nodeId = ? AND \"type\" = 0 ORDER BY ip ASC",-1,&_sGetIpAssignmentsForNode,(const char **)0) != SQLITE_OK)
||(sqlite3_prepare_v2(_db,"SELECT 1 FROM IpAssignment WHERE networkId = ? AND ip = ? AND ipVersion = ? AND \"type\" = ?",-1,&_sCheckIfIpIsAllocated,(const char **)0) != SQLITE_OK)
||(sqlite3_prepare_v2(_db,"INSERT INTO IpAssignment (networkId,nodeId,\"type\",ip,ipNetmaskBits,ipVersion) VALUES (?,?,?,?,?,?)",-1,&_sAllocateIp,(const char **)0) != SQLITE_OK)
||(sqlite3_prepare_v2(_db,"DELETE FROM IpAssignment WHERE networkId = ? AND nodeId = ? AND \"type\" = ?",-1,&_sDeleteIpAllocations,(const char **)0) != SQLITE_OK)
- /* Relay */
- ||(sqlite3_prepare_v2(_db,"SELECT \"address\",\"phyAddress\" FROM Relay WHERE \"networkId\" = ? ORDER BY \"address\" ASC",-1,&_sGetRelays,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"DELETE FROM Relay WHERE networkId = ?",-1,&_sDeleteRelaysForNetwork,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"INSERT INTO Relay (\"networkId\",\"address\",\"phyAddress\") VALUES (?,?,?)",-1,&_sCreateRelay,(const char **)0) != SQLITE_OK)
-
- /* Member */
||(sqlite3_prepare_v2(_db,"SELECT rowid,authorized,activeBridge,memberRevision,\"flags\",lastRequestTime,recentHistory FROM Member WHERE networkId = ? AND nodeId = ?",-1,&_sGetMember,(const char **)0) != SQLITE_OK)
||(sqlite3_prepare_v2(_db,"SELECT m.authorized,m.activeBridge,m.memberRevision,n.identity,m.flags,m.lastRequestTime,m.recentHistory FROM Member AS m LEFT OUTER JOIN Node AS n ON n.id = m.nodeId WHERE m.networkId = ? AND m.nodeId = ?",-1,&_sGetMember2,(const char **)0) != SQLITE_OK)
||(sqlite3_prepare_v2(_db,"INSERT INTO Member (networkId,nodeId,authorized,activeBridge,memberRevision) VALUES (?,?,?,0,(SELECT memberRevisionCounter FROM Network WHERE id = ?))",-1,&_sCreateMember,(const char **)0) != SQLITE_OK)
@@ -431,12 +288,10 @@ SqliteNetworkController::SqliteNetworkController(Node *node,const char *dbPath,c
||(sqlite3_prepare_v2(_db,"DELETE FROM Member WHERE networkId = ?",-1,&_sDeleteAllNetworkMembers,(const char **)0) != SQLITE_OK)
||(sqlite3_prepare_v2(_db,"SELECT nodeId,recentHistory FROM Member WHERE networkId = ? AND lastRequestTime >= ?",-1,&_sGetActiveNodesOnNetwork,(const char **)0) != SQLITE_OK)
- /* Route */
||(sqlite3_prepare_v2(_db,"INSERT INTO Route (networkId,target,via,targetNetmaskBits,ipVersion,flags,metric) VALUES (?,?,?,?,?,?,?)",-1,&_sCreateRoute,(const char **)0) != SQLITE_OK)
||(sqlite3_prepare_v2(_db,"SELECT DISTINCT target,via,targetNetmaskBits,ipVersion,flags,metric FROM \"Route\" WHERE networkId = ? ORDER BY ipVersion,target,via",-1,&_sGetRoutes,(const char **)0) != SQLITE_OK)
||(sqlite3_prepare_v2(_db,"DELETE FROM \"Route\" WHERE networkId = ?",-1,&_sDeleteRoutes,(const char **)0) != SQLITE_OK)
- /* Config */
||(sqlite3_prepare_v2(_db,"SELECT \"v\" FROM \"Config\" WHERE \"k\" = ?",-1,&_sGetConfig,(const char **)0) != SQLITE_OK)
||(sqlite3_prepare_v2(_db,"INSERT OR REPLACE INTO \"Config\" (\"k\",\"v\") VALUES (?,?)",-1,&_sSetConfig,(const char **)0) != SQLITE_OK)
@@ -446,9 +301,6 @@ SqliteNetworkController::SqliteNetworkController(Node *node,const char *dbPath,c
throw std::runtime_error(err);
}
- /* Generate a 128-bit / 32-character "instance ID" if one isn't already
- * defined. Clients can use this to determine if this is the same controller
- * database they know and love. */
sqlite3_reset(_sGetConfig);
sqlite3_bind_text(_sGetConfig,1,"instanceId",10,SQLITE_STATIC);
if (sqlite3_step(_sGetConfig) != SQLITE_ROW) {
@@ -474,72 +326,32 @@ SqliteNetworkController::SqliteNetworkController(Node *node,const char *dbPath,c
#endif
_backupThread = Thread::start(this);
+ */
+
+ _dbCommitThread = Thread::start(this);
}
SqliteNetworkController::~SqliteNetworkController()
{
- _backupThreadRun = false;
- Thread::join(_backupThread);
-
- Mutex::Lock _l(_lock);
- if (_db) {
- sqlite3_finalize(_sGetNetworkById);
- sqlite3_finalize(_sGetMember);
- sqlite3_finalize(_sCreateMember);
- sqlite3_finalize(_sGetNodeIdentity);
- sqlite3_finalize(_sCreateOrReplaceNode);
- sqlite3_finalize(_sGetActiveBridges);
- sqlite3_finalize(_sGetIpAssignmentsForNode);
- sqlite3_finalize(_sGetIpAssignmentPools);
- sqlite3_finalize(_sCheckIfIpIsAllocated);
- sqlite3_finalize(_sAllocateIp);
- sqlite3_finalize(_sDeleteIpAllocations);
- sqlite3_finalize(_sGetRelays);
- sqlite3_finalize(_sListNetworks);
- sqlite3_finalize(_sListNetworkMembers);
- sqlite3_finalize(_sGetMember2);
- sqlite3_finalize(_sGetIpAssignmentPools2);
- sqlite3_finalize(_sListRules);
- sqlite3_finalize(_sCreateRule);
- sqlite3_finalize(_sCreateNetwork);
- sqlite3_finalize(_sGetNetworkRevision);
- sqlite3_finalize(_sSetNetworkRevision);
- sqlite3_finalize(_sDeleteRelaysForNetwork);
- sqlite3_finalize(_sCreateRelay);
- sqlite3_finalize(_sDeleteIpAssignmentPoolsForNetwork);
- sqlite3_finalize(_sDeleteRulesForNetwork);
- sqlite3_finalize(_sCreateIpAssignmentPool);
- sqlite3_finalize(_sUpdateMemberAuthorized);
- sqlite3_finalize(_sUpdateMemberActiveBridge);
- sqlite3_finalize(_sUpdateMemberHistory);
- sqlite3_finalize(_sDeleteMember);
- sqlite3_finalize(_sDeleteAllNetworkMembers);
- sqlite3_finalize(_sGetActiveNodesOnNetwork);
- sqlite3_finalize(_sDeleteNetwork);
- sqlite3_finalize(_sCreateRoute);
- sqlite3_finalize(_sGetRoutes);
- sqlite3_finalize(_sDeleteRoutes);
- sqlite3_finalize(_sIncrementMemberRevisionCounter);
- sqlite3_finalize(_sGetConfig);
- sqlite3_finalize(_sSetConfig);
- sqlite3_close(_db);
- }
+ _lock.lock();
+ _dbCommitThreadRun = false;
+ _lock.unlock();
+ Thread::join(_dbCommitThread);
}
-NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(const InetAddress &fromAddr,const Identity &signingId,const Identity &identity,uint64_t nwid,const Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> &metaData,NetworkConfig &nc)
+NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(const InetAddress &fromAddr,const Identity &signingId,const Identity &identity,uint64_t nwid,const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData,NetworkConfig &nc)
{
if (((!signingId)||(!signingId.hasPrivate()))||(signingId.address().toInt() != (nwid >> 24))) {
return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
}
- const uint64_t now = OSUtils::now();
-
- NetworkRecord network;
- Utils::snprintf(network.id,sizeof(network.id),"%.16llx",(unsigned long long)nwid);
+ char nwids[24],nodeIds[24];
+ Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
+ Utils::snprintf(nodeIds,sizeof(nodeIds),"%.10llx",(unsigned long long)identity.address().toInt());
- MemberRecord member;
- Utils::snprintf(member.nodeId,sizeof(member.nodeId),"%.10llx",(unsigned long long)identity.address().toInt());
+ const uint64_t now = OSUtils::now();
+ /*
{ // begin lock
Mutex::Lock _l(_lock);
@@ -874,13 +686,13 @@ NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(co
sqlite3_bind_text(_sCheckIfIpIsAllocated,1,network.id,16,SQLITE_STATIC);
sqlite3_bind_blob(_sCheckIfIpIsAllocated,2,(const void *)ip6.rawIpData(),16,SQLITE_STATIC);
sqlite3_bind_int(_sCheckIfIpIsAllocated,3,6); // 6 == IPv6
- sqlite3_bind_int(_sCheckIfIpIsAllocated,4,(int)0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/);
+ sqlite3_bind_int(_sCheckIfIpIsAllocated,4,(int)0);
if (sqlite3_step(_sCheckIfIpIsAllocated) != SQLITE_ROW) {
// No rows returned, so the IP is available
sqlite3_reset(_sAllocateIp);
sqlite3_bind_text(_sAllocateIp,1,network.id,16,SQLITE_STATIC);
sqlite3_bind_text(_sAllocateIp,2,member.nodeId,10,SQLITE_STATIC);
- sqlite3_bind_int(_sAllocateIp,3,(int)0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/);
+ sqlite3_bind_int(_sAllocateIp,3,(int)0);
sqlite3_bind_blob(_sAllocateIp,4,(const void *)ip6.rawIpData(),16,SQLITE_STATIC);
sqlite3_bind_int(_sAllocateIp,5,routedNetmaskBits); // IP netmask bits from matching route
sqlite3_bind_int(_sAllocateIp,6,6); // 6 == IPv6
@@ -943,13 +755,13 @@ NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(co
sqlite3_bind_text(_sCheckIfIpIsAllocated,1,network.id,16,SQLITE_STATIC);
sqlite3_bind_blob(_sCheckIfIpIsAllocated,2,(const void *)ipBlob,16,SQLITE_STATIC);
sqlite3_bind_int(_sCheckIfIpIsAllocated,3,4); // 4 == IPv4
- sqlite3_bind_int(_sCheckIfIpIsAllocated,4,(int)0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/);
+ sqlite3_bind_int(_sCheckIfIpIsAllocated,4,(int)0);
if (sqlite3_step(_sCheckIfIpIsAllocated) != SQLITE_ROW) {
// No rows returned, so the IP is available
sqlite3_reset(_sAllocateIp);
sqlite3_bind_text(_sAllocateIp,1,network.id,16,SQLITE_STATIC);
sqlite3_bind_text(_sAllocateIp,2,member.nodeId,10,SQLITE_STATIC);
- sqlite3_bind_int(_sAllocateIp,3,(int)0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/);
+ sqlite3_bind_int(_sAllocateIp,3,(int)0);
sqlite3_bind_blob(_sAllocateIp,4,(const void *)ipBlob,16,SQLITE_STATIC);
sqlite3_bind_int(_sAllocateIp,5,routedNetmaskBits); // IP netmask bits from matching route
sqlite3_bind_int(_sAllocateIp,6,4); // 4 == IPv4
@@ -980,6 +792,7 @@ NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(co
}
return NetworkController::NETCONF_QUERY_OK;
+ */
}
unsigned int SqliteNetworkController::handleControlPlaneHttpGET(
@@ -1671,76 +1484,19 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpDELETE(
void SqliteNetworkController::threadMain()
throw()
{
- uint64_t lastBackupTime = OSUtils::now();
- uint64_t lastCleanupTime = OSUtils::now();
-
- while (_backupThreadRun) {
- if ((OSUtils::now() - lastCleanupTime) >= 5000) {
- const uint64_t now = OSUtils::now();
- lastCleanupTime = now;
-
+ bool run = true;
+ while(run) {
+ Thread::sleep(250);
+ try {
+ std::vector<std::string> errors;
Mutex::Lock _l(_lock);
-
- // Clean out really old circuit tests to prevent memory build-up
- for(std::map< uint64_t,_CircuitTestEntry >::iterator ct(_circuitTests.begin());ct!=_circuitTests.end();) {
- if (!ct->second.test) {
- _circuitTests.erase(ct++);
- } else if ((now - ct->second.test->timestamp) >= ZT_SQLITENETWORKCONTROLLER_CIRCUIT_TEST_TIMEOUT) {
- _node->circuitTestEnd(ct->second.test);
- ::free((void *)ct->second.test);
- _circuitTests.erase(ct++);
- } else ++ct;
+ run = _dbCommitThreadRun;
+ if (!_db.commit(&errors)) {
+ // TODO: handle anything really bad
}
+ } catch ( ... ) {
+ // TODO: handle anything really bad
}
-
- if (((OSUtils::now() - lastBackupTime) >= ZT_NETCONF_BACKUP_PERIOD)&&(_backupNeeded)) {
- lastBackupTime = OSUtils::now();
-
- char backupPath[4096],backupPath2[4096];
- Utils::snprintf(backupPath,sizeof(backupPath),"%s.backupInProgress",_dbPath.c_str());
- Utils::snprintf(backupPath2,sizeof(backupPath),"%s.backup",_dbPath.c_str());
- OSUtils::rm(backupPath); // delete any unfinished backups
-
- sqlite3 *bakdb = (sqlite3 *)0;
- sqlite3_backup *bak = (sqlite3_backup *)0;
- if (sqlite3_open_v2(backupPath,&bakdb,SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE,(const char *)0) != SQLITE_OK) {
- fprintf(stderr,"SqliteNetworkController: CRITICAL: backup failed on sqlite3_open_v2()"ZT_EOL_S);
- continue;
- }
- bak = sqlite3_backup_init(bakdb,"main",_db,"main");
- if (!bak) {
- sqlite3_close(bakdb);
- OSUtils::rm(backupPath); // delete any unfinished backups
- fprintf(stderr,"SqliteNetworkController: CRITICAL: backup failed on sqlite3_backup_init()"ZT_EOL_S);
- continue;
- }
-
- int rc = SQLITE_OK;
- for(;;) {
- if (!_backupThreadRun) {
- sqlite3_backup_finish(bak);
- sqlite3_close(bakdb);
- OSUtils::rm(backupPath);
- return;
- }
- _lock.lock();
- rc = sqlite3_backup_step(bak,64);
- _lock.unlock();
- if ((rc == SQLITE_OK)||(rc == SQLITE_LOCKED)||(rc == SQLITE_BUSY))
- Thread::sleep(50);
- else break;
- }
-
- sqlite3_backup_finish(bak);
- sqlite3_close(bakdb);
-
- OSUtils::rm(backupPath2);
- ::rename(backupPath,backupPath2);
-
- _backupNeeded = false;
- }
-
- Thread::sleep(250);
}
}
@@ -1753,27 +1509,40 @@ unsigned int SqliteNetworkController::_doCPGet(
std::string &responseContentType)
{
// Assumes _lock is locked
- char json[65536];
-
if ((path.size() > 0)&&(path[0] == "network")) {
+ auto networks = _db.get<const json::object_t *>("network");
+ if (!networks) return 404;
if ((path.size() >= 2)&&(path[1].length() == 16)) {
- uint64_t nwid = Utils::hexStrToU64(path[1].c_str());
+ const uint64_t nwid = Utils::hexStrToU64(path[1].c_str());
char nwids[24];
Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
+ auto network = _db.get<const json::object_t *>(nwids);
+ if (!network) return 404;
if (path.size() >= 3) {
- // /network/<nwid>/...
if (path[2] == "member") {
+ auto members = network->get<const json::object_t *>("member");
+ if (!members) return 404;
if (path.size() >= 4) {
- // Get specific member info
-
- uint64_t address = Utils::hexStrToU64(path[3].c_str());
+ const uint64_t address = Utils::hexStrToU64(path[3].c_str());
char addrs[24];
Utils::snprintf(addrs,sizeof(addrs),"%.10llx",address);
+ auto member = members->get<const json::object_t *>(addrs);
+ if (!member) return 404;
+
+ nlohmann::json o(member);
+ o["nwid"] = nwids;
+ o["address"] = addrs;
+ o["controllerInstanceId"] = _instanceId;
+ o["clock"] = OSUtils::now();
+ responseBody = o.dump(2);
+ responseContentType = "application/json";
+ return 200;
+ /*
sqlite3_reset(_sGetMember2);
sqlite3_bind_text(_sGetMember2,1,nwids,16,SQLITE_STATIC);
sqlite3_bind_text(_sGetMember2,2,addrs,10,SQLITE_STATIC);
@@ -1838,20 +1607,18 @@ unsigned int SqliteNetworkController::_doCPGet(
responseContentType = "application/json";
return 200;
} // else 404
+ */
} else {
- // List members
- sqlite3_reset(_sListNetworkMembers);
- sqlite3_bind_text(_sListNetworkMembers,1,nwids,16,SQLITE_STATIC);
responseBody.push_back('{');
- bool firstMember = true;
- while (sqlite3_step(_sListNetworkMembers) == SQLITE_ROW) {
- responseBody.append(firstMember ? "\"" : ",\"");
- firstMember = false;
- responseBody.append((const char *)sqlite3_column_text(_sListNetworkMembers,0));
- responseBody.append("\":");
- responseBody.append((const char *)sqlite3_column_text(_sListNetworkMembers,1));
+ for(auto i(members->begin());i!=members->end();++i) {
+ responseBody.append((i == members->begin()) ? "\"" : ",\"");
+ responseBody.append(i->key());
+ responseBody.append("\":\"");
+ const std::string rc = i->value().value("memberRevision","0");
+ responseBody.append(rc);
+ responseBody.append('"');
}
responseBody.push_back('}');
responseContentType = "application/json";
@@ -1861,33 +1628,23 @@ unsigned int SqliteNetworkController::_doCPGet(
} else if ((path[2] == "active")&&(path.size() == 3)) {
- sqlite3_reset(_sGetActiveNodesOnNetwork);
- sqlite3_bind_text(_sGetActiveNodesOnNetwork,1,nwids,16,SQLITE_STATIC);
- sqlite3_bind_int64(_sGetActiveNodesOnNetwork,2,(int64_t)(OSUtils::now() - ZT_NETCONF_NODE_ACTIVE_THRESHOLD));
-
responseBody.push_back('{');
- bool firstActiveMember = true;
- while (sqlite3_step(_sGetActiveNodesOnNetwork) == SQLITE_ROW) {
- const char *nodeId = (const char *)sqlite3_column_text(_sGetActiveNodesOnNetwork,0);
- const char *rhblob = (const char *)sqlite3_column_blob(_sGetActiveNodesOnNetwork,1);
- if ((nodeId)&&(rhblob)) {
- MemberRecentHistory rh;
- rh.fromBlob(rhblob,sqlite3_column_bytes(_sGetActiveNodesOnNetwork,1));
- if (rh.size() > 0) {
- if (firstActiveMember) {
- firstActiveMember = false;
- } else {
- responseBody.push_back(',');
- }
- responseBody.push_back('"');
- responseBody.append(nodeId);
+ bool firstMember = true;
+ const uint64_t threshold = OSUtils::now() - ZT_NETCONF_NODE_ACTIVE_THRESHOLD;
+ for(auto i(members->begin());i!=members->end();++i) {
+ auto recentLog = i->value()->get<const json::array_t *>("recentLog");
+ if ((recentLog)&&(recentLog.size() > 0)) {
+ auto mostRecentLog = recentLog[0];
+ if ((mostRecentLog.is_object())&&((uint64_t)mostRecentLog.value("ts",0ULL) >= threshold)) {
+ responseBody.append((firstMember) ? "\"" : ",\"");
+ firstMember = false;
+ responseBody.append(i->key());
responseBody.append("\":");
- responseBody.append(rh.front());
+ responseBody.append(mostRecentLog.dump());
}
}
}
responseBody.push_back('}');
-
responseContentType = "application/json";
return 200;
@@ -1909,249 +1666,42 @@ unsigned int SqliteNetworkController::_doCPGet(
} else {
- sqlite3_reset(_sGetNetworkById);
- sqlite3_bind_text(_sGetNetworkById,1,nwids,16,SQLITE_STATIC);
- if (sqlite3_step(_sGetNetworkById) == SQLITE_ROW) {
- unsigned int fl = (unsigned int)sqlite3_column_int(_sGetNetworkById,4);
- std::string v6modes;
- if ((fl & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_RFC4193) != 0)
- v6modes.append("rfc4193");
- if ((fl & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_6PLANE) != 0) {
- if (v6modes.length() > 0)
- v6modes.push_back(',');
- v6modes.append("6plane");
- }
- if ((fl & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_AUTO_ASSIGN) != 0) {
- if (v6modes.length() > 0)
- v6modes.push_back(',');
- v6modes.append("zt");
- }
-
- Utils::snprintf(json,sizeof(json),
- "{\n"
- "\t\"nwid\": \"%s\",\n"
- "\t\"controllerInstanceId\": \"%s\",\n"
- "\t\"clock\": %llu,\n"
- "\t\"name\": \"%s\",\n"
- "\t\"private\": %s,\n"
- "\t\"enableBroadcast\": %s,\n"
- "\t\"allowPassiveBridging\": %s,\n"
- "\t\"v4AssignMode\": \"%s\",\n"
- "\t\"v6AssignMode\": \"%s\",\n"
- "\t\"multicastLimit\": %d,\n"
- "\t\"creationTime\": %llu,\n"
- "\t\"revision\": %llu,\n"
- "\t\"memberRevisionCounter\": %llu,\n"
- "\t\"authorizedMemberCount\": %llu,\n"
- "\t\"relays\": [",
- nwids,
- _instanceId.c_str(),
- (unsigned long long)OSUtils::now(),
- _jsonEscape((const char *)sqlite3_column_text(_sGetNetworkById,0)).c_str(),
- (sqlite3_column_int(_sGetNetworkById,1) > 0) ? "true" : "false",
- (sqlite3_column_int(_sGetNetworkById,2) > 0) ? "true" : "false",
- (sqlite3_column_int(_sGetNetworkById,3) > 0) ? "true" : "false",
- (((fl & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V4_AUTO_ASSIGN) != 0) ? "zt" : ""),
- v6modes.c_str(),
- sqlite3_column_int(_sGetNetworkById,5),
- (unsigned long long)sqlite3_column_int64(_sGetNetworkById,6),
- (unsigned long long)sqlite3_column_int64(_sGetNetworkById,7),
- (unsigned long long)sqlite3_column_int64(_sGetNetworkById,8),
- (unsigned long long)sqlite3_column_int64(_sGetNetworkById,9));
- responseBody = json;
-
- sqlite3_reset(_sGetRelays);
- sqlite3_bind_text(_sGetRelays,1,nwids,16,SQLITE_STATIC);
- bool firstRelay = true;
- while (sqlite3_step(_sGetRelays) == SQLITE_ROW) {
- responseBody.append(firstRelay ? "\n\t\t" : ",\n\t\t");
- firstRelay = false;
- responseBody.append("{\"address\":\"");
- responseBody.append((const char *)sqlite3_column_text(_sGetRelays,0));
- responseBody.append("\",\"phyAddress\":\"");
- responseBody.append(_jsonEscape((const char *)sqlite3_column_text(_sGetRelays,1)));
- responseBody.append("\"}");
- }
-
- responseBody.append("],\n\t\"routes\": [");
-
- sqlite3_reset(_sGetRoutes);
- sqlite3_bind_text(_sGetRoutes,1,nwids,16,SQLITE_STATIC);
- bool firstRoute = true;
- while (sqlite3_step(_sGetRoutes) == SQLITE_ROW) {
- responseBody.append(firstRoute ? "\n\t\t" : ",\n\t\t");
- firstRoute = false;
- responseBody.append("{\"target\":");
- char tmp[128];
- const unsigned char *ip = (const unsigned char *)sqlite3_column_blob(_sGetRoutes,0);
- switch(sqlite3_column_int(_sGetRoutes,3)) { // ipVersion
- case 4:
- Utils::snprintf(tmp,sizeof(tmp),"\"%d.%d.%d.%d/%d\"",(int)ip[12],(int)ip[13],(int)ip[14],(int)ip[15],sqlite3_column_int(_sGetRoutes,2));
- break;
- case 6:
- Utils::snprintf(tmp,sizeof(tmp),"\"%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x/%d\"",(int)ip[0],(int)ip[1],(int)ip[2],(int)ip[3],(int)ip[4],(int)ip[5],(int)ip[6],(int)ip[7],(int)ip[8],(int)ip[9],(int)ip[10],(int)ip[11],(int)ip[12],(int)ip[13],(int)ip[14],(int)ip[15],sqlite3_column_int(_sGetRoutes,2));
- break;
- }
- responseBody.append(tmp);
- if (sqlite3_column_type(_sGetRoutes,1) == SQLITE_NULL) {
- responseBody.append(",\"via\":null");
- } else {
- responseBody.append(",\"via\":");
- ip = (const unsigned char *)sqlite3_column_blob(_sGetRoutes,1);
- switch(sqlite3_column_int(_sGetRoutes,3)) { // ipVersion
- case 4:
- Utils::snprintf(tmp,sizeof(tmp),"\"%d.%d.%d.%d\"",(int)ip[12],(int)ip[13],(int)ip[14],(int)ip[15]);
- break;
- case 6:
- Utils::snprintf(tmp,sizeof(tmp),"\"%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x\"",(int)ip[0],(int)ip[1],(int)ip[2],(int)ip[3],(int)ip[4],(int)ip[5],(int)ip[6],(int)ip[7],(int)ip[8],(int)ip[9],(int)ip[10],(int)ip[11],(int)ip[12],(int)ip[13],(int)ip[14],(int)ip[15]);
- break;
- }
- responseBody.append(tmp);
- }
- responseBody.append(",\"flags\":");
- responseBody.append((const char *)sqlite3_column_text(_sGetRoutes,4));
- responseBody.append(",\"metric\":");
- responseBody.append((const char *)sqlite3_column_text(_sGetRoutes,5));
- responseBody.push_back('}');
- }
-
- responseBody.append("],\n\t\"ipAssignmentPools\": [");
-
- sqlite3_reset(_sGetIpAssignmentPools2);
- sqlite3_bind_text(_sGetIpAssignmentPools2,1,nwids,16,SQLITE_STATIC);
- bool firstIpAssignmentPool = true;
- while (sqlite3_step(_sGetIpAssignmentPools2) == SQLITE_ROW) {
- const char *ipRangeStartB = reinterpret_cast<const char *>(sqlite3_column_blob(_sGetIpAssignmentPools2,0));
- const char *ipRangeEndB = reinterpret_cast<const char *>(sqlite3_column_blob(_sGetIpAssignmentPools2,1));
- if ((ipRangeStartB)&&(ipRangeEndB)) {
- InetAddress ipps,ippe;
- int ipVersion = sqlite3_column_int(_sGetIpAssignmentPools2,2);
- if (ipVersion == 4) {
- ipps.set((const void *)(ipRangeStartB + 12),4,0);
- ippe.set((const void *)(ipRangeEndB + 12),4,0);
- } else if (ipVersion == 6) {
- ipps.set((const void *)ipRangeStartB,16,0);
- ippe.set((const void *)ipRangeEndB,16,0);
- }
- if (ipps) {
- responseBody.append(firstIpAssignmentPool ? "\n\t\t" : ",\n\t\t");
- firstIpAssignmentPool = false;
- Utils::snprintf(json,sizeof(json),"{\"ipRangeStart\":\"%s\",\"ipRangeEnd\":\"%s\"}",
- _jsonEscape(ipps.toIpString()).c_str(),
- _jsonEscape(ippe.toIpString()).c_str());
- responseBody.append(json);
- }
- }
- }
+ nlohmann::json o(network);
+ o["nwid"] = nwids;
+ o["controllerInstanceId"] = _instanceId;
+ o["clock"] = OSUtils::now();
+ responseBody = o.dump(2);
+ responseContentType = "application/json";
+ return 200;
- responseBody.append("],\n\t\"rules\": [");
-
- sqlite3_reset(_sListRules);
- sqlite3_bind_text(_sListRules,1,nwids,16,SQLITE_STATIC);
- bool firstRule = true;
- while (sqlite3_step(_sListRules) == SQLITE_ROW) {
- responseBody.append(firstRule ? "\n\t{\n" : ",{\n");
- firstRule = false;
- Utils::snprintf(json,sizeof(json),"\t\t\"ruleNo\": %lld,\n",sqlite3_column_int64(_sListRules,0));
- responseBody.append(json);
- if (sqlite3_column_type(_sListRules,1) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"nodeId\": \"%s\",\n",(const char *)sqlite3_column_text(_sListRules,1));
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,2) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"sourcePort\": \"%s\",\n",(const char *)sqlite3_column_text(_sListRules,2));
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,3) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"destPort\": \"%s\",\n",(const char *)sqlite3_column_text(_sListRules,3));
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,4) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"vlanId\": %d,\n",sqlite3_column_int(_sListRules,4));
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,5) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"vlanPcp\": %d,\n",sqlite3_column_int(_sListRules,5));
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,6) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"etherType\": %d,\n",sqlite3_column_int(_sListRules,6));
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,7) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"macSource\": \"%s\",\n",MAC((const char *)sqlite3_column_text(_sListRules,7)).toString().c_str());
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,8) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"macDest\": \"%s\",\n",MAC((const char *)sqlite3_column_text(_sListRules,8)).toString().c_str());
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,9) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"ipSource\": \"%s\",\n",_jsonEscape((const char *)sqlite3_column_text(_sListRules,9)).c_str());
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,10) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"ipDest\": \"%s\",\n",_jsonEscape((const char *)sqlite3_column_text(_sListRules,10)).c_str());
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,11) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"ipTos\": %d,\n",sqlite3_column_int(_sListRules,11));
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,12) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"ipProtocol\": %d,\n",sqlite3_column_int(_sListRules,12));
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,13) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"ipSourcePort\": %d,\n",sqlite3_column_int(_sListRules,13));
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,14) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"ipDestPort\": %d,\n",sqlite3_column_int(_sListRules,14));
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,15) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"flags\": %lu,\n",(unsigned long)sqlite3_column_int64(_sListRules,15));
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,16) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"invFlags\": %lu,\n",(unsigned long)sqlite3_column_int64(_sListRules,16));
- responseBody.append(json);
- }
- responseBody.append("\t\t\"action\": \"");
- responseBody.append(_jsonEscape( (sqlite3_column_type(_sListRules,17) == SQLITE_NULL) ? "drop" : (const char *)sqlite3_column_text(_sListRules,17) ));
- responseBody.append("\"\n\t}");
- }
-
- responseBody.append("]\n}\n");
- responseContentType = "application/json";
- return 200;
- } // else 404
}
} else if (path.size() == 1) {
- // list networks
- sqlite3_reset(_sListNetworks);
- responseContentType = "application/json";
+
responseBody = "[";
- bool first = true;
- while (sqlite3_step(_sListNetworks) == SQLITE_ROW) {
- if (first) {
- first = false;
- responseBody.push_back('"');
- } else responseBody.append(",\"");
- responseBody.append((const char *)sqlite3_column_text(_sListNetworks,0));
- responseBody.push_back('"');
+ for(auto i(networks->begin());i!=networks.end();++i) {
+ responseBody.append((i == networks->begin()) ? "\"" : ",\"");
+ responseBody.append(i->key());
+ responseBody.append("\"");
}
responseBody.push_back(']');
+ responseContentType = "application/json";
return 200;
+
} // else 404
+ } else if ((path.size() > 0)&&(path[0] == "_dump")) {
+
+ responseBody = _db.dump(2);
+ responseContentType = "application/json";
+ return 200;
+
} else {
- // GET /controller returns status and API version if controller is supported
+
Utils::snprintf(json,sizeof(json),"{\n\t\"controller\": true,\n\t\"apiVersion\": %d,\n\t\"clock\": %llu,\n\t\"instanceId\": \"%s\"\n}\n",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now(),_instanceId.c_str());
responseBody = json;
responseContentType = "application/json";
return 200;
+
}
return 404;
diff --git a/controller/SqliteNetworkController.hpp b/controller/SqliteNetworkController.hpp
index b917f6fb..0d549abc 100644
--- a/controller/SqliteNetworkController.hpp
+++ b/controller/SqliteNetworkController.hpp
@@ -30,22 +30,17 @@
#include <stdint.h>
-#include <sqlite3.h>
-
#include <string>
#include <map>
#include <vector>
#include "../node/Constants.hpp"
+
#include "../node/NetworkController.hpp"
#include "../node/Mutex.hpp"
#include "../osdep/Thread.hpp"
-// Number of in-memory last log entries to maintain per user
-#define ZT_SQLITENETWORKCONTROLLER_IN_MEMORY_LOG_SIZE 32
-
-// How long do circuit tests last before they're forgotten?
-#define ZT_SQLITENETWORKCONTROLLER_CIRCUIT_TEST_TIMEOUT 60000
+#include "../ext/offbase/offbase.hpp"
namespace ZeroTier {
@@ -54,7 +49,7 @@ class Node;
class SqliteNetworkController : public NetworkController
{
public:
- SqliteNetworkController(Node *node,const char *dbPath,const char *circuitTestPath);
+ SqliteNetworkController(Node *node,const char *dbPath);
virtual ~SqliteNetworkController();
virtual NetworkController::ResultCode doNetworkConfigRequest(
@@ -62,7 +57,7 @@ public:
const Identity &signingId,
const Identity &identity,
uint64_t nwid,
- const Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> &metaData,
+ const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData,
NetworkConfig &nc);
unsigned int handleControlPlaneHttpGET(
@@ -92,15 +87,6 @@ public:
throw();
private:
- /* deprecated
- enum IpAssignmentType {
- // IP assignment is a static IP address
- ZT_IP_ASSIGNMENT_TYPE_ADDRESS = 0,
- // IP assignment is a network -- a route via this interface, not an address
- ZT_IP_ASSIGNMENT_TYPE_NETWORK = 1
- };
- */
-
unsigned int _doCPGet(
const std::vector<std::string> &path,
const std::map<std::string,std::string> &urlArgs,
@@ -111,13 +97,11 @@ private:
static void _circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report);
- Node *_node;
- Thread _backupThread;
- volatile bool _backupThreadRun;
- volatile bool _backupNeeded;
- std::string _dbPath;
- std::string _circuitTestPath;
+ Node *const _node;
std::string _instanceId;
+ offbase _db;
+ Thread _dbCommitThread;
+ volatile bool _dbCommitThreadRun;
// Circuit tests outstanding
struct _CircuitTestEntry
@@ -130,48 +114,6 @@ private:
// Last request time by address, for rate limitation
std::map< std::pair<uint64_t,uint64_t>,uint64_t > _lastRequestTime;
- sqlite3 *_db;
-
- sqlite3_stmt *_sGetNetworkById;
- sqlite3_stmt *_sGetMember;
- sqlite3_stmt *_sCreateMember;
- sqlite3_stmt *_sGetNodeIdentity;
- sqlite3_stmt *_sCreateOrReplaceNode;
- sqlite3_stmt *_sGetActiveBridges;
- sqlite3_stmt *_sGetIpAssignmentsForNode;
- sqlite3_stmt *_sGetIpAssignmentPools;
- sqlite3_stmt *_sCheckIfIpIsAllocated;
- sqlite3_stmt *_sAllocateIp;
- sqlite3_stmt *_sDeleteIpAllocations;
- sqlite3_stmt *_sGetRelays;
- sqlite3_stmt *_sListNetworks;
- sqlite3_stmt *_sListNetworkMembers;
- sqlite3_stmt *_sGetMember2;
- sqlite3_stmt *_sGetIpAssignmentPools2;
- sqlite3_stmt *_sListRules;
- sqlite3_stmt *_sCreateRule;
- sqlite3_stmt *_sCreateNetwork;
- sqlite3_stmt *_sGetNetworkRevision;
- sqlite3_stmt *_sSetNetworkRevision;
- sqlite3_stmt *_sDeleteRelaysForNetwork;
- sqlite3_stmt *_sCreateRelay;
- sqlite3_stmt *_sDeleteIpAssignmentPoolsForNetwork;
- sqlite3_stmt *_sDeleteRulesForNetwork;
- sqlite3_stmt *_sCreateIpAssignmentPool;
- sqlite3_stmt *_sUpdateMemberAuthorized;
- sqlite3_stmt *_sUpdateMemberActiveBridge;
- sqlite3_stmt *_sUpdateMemberHistory;
- sqlite3_stmt *_sDeleteMember;
- sqlite3_stmt *_sDeleteAllNetworkMembers;
- sqlite3_stmt *_sGetActiveNodesOnNetwork;
- sqlite3_stmt *_sDeleteNetwork;
- sqlite3_stmt *_sCreateRoute;
- sqlite3_stmt *_sGetRoutes;
- sqlite3_stmt *_sDeleteRoutes;
- sqlite3_stmt *_sIncrementMemberRevisionCounter;
- sqlite3_stmt *_sGetConfig;
- sqlite3_stmt *_sSetConfig;
-
Mutex _lock;
};
diff --git a/controller/schema.sql b/controller/schema.sql
index 479daa68..d1daf8d0 100644
--- a/controller/schema.sql
+++ b/controller/schema.sql
@@ -86,17 +86,9 @@ CREATE TABLE Route (
CREATE INDEX Route_networkId ON Route (networkId);
-CREATE TABLE Relay (
- networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,
- address char(10) NOT NULL,
- phyAddress varchar(64) NOT NULL
-);
-
-CREATE UNIQUE INDEX Relay_networkId_address ON Relay (networkId,address);
-
CREATE TABLE Rule (
networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,
- policyId varchar(32),
+ capId integer,
ruleNo integer NOT NULL,
ruleType integer NOT NULL DEFAULT(0),
"addr" blob(16),
@@ -106,5 +98,15 @@ CREATE TABLE Rule (
"int4" integer
);
-CREATE INDEX Rule_networkId_ruleNo ON Rule (networkId, ruleNo);
-CREATE INDEX Rule_networkId_policyId ON Rule (networkId, policyId);
+CREATE INDEX Rule_networkId_capId ON Rule (networkId,capId);
+
+CREATE TABLE MemberTC (
+ networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,
+ nodeId char(10) NOT NULL REFERENCES Node(id) ON DELETE CASCADE,
+ tagId integer,
+ tagValue integer,
+ capId integer,
+ capMaxCustodyChainLength integer NOT NULL DEFAULT(1)
+);
+
+CREATE INDEX MemberTC_networkId_nodeId ON MemberTC (networkId,nodeId);
diff --git a/ext/offbase/README.md b/ext/offbase/README.md
new file mode 100644
index 00000000..6668b878
--- /dev/null
+++ b/ext/offbase/README.md
@@ -0,0 +1,59 @@
+A super-minimal in-filesystem persistent JSON object
+======
+
+[We](https://www.zerotier.com/) like minimalism.
+
+Offbase is an extension of the excellent [nlohmann/json](https://github.com/nlohmann/json) C++11 JSON class that adds simple object persistence to/from the filesystem. Objects are stored into a directory hierarchy in fully "expanded" form with each field/value being represented by a separate file.
+
+Features:
+
+ - Very easy to use
+ - Minimal!
+ - Easy to understand and maintain
+ - Trivial to implement in other languages
+ - No dependencies beyond standard libraries
+ - Small code footprint in both source and binary form
+ - Easy to port to other platforms
+ - Exactly reproduces JSON object hierarchies including all JSON type information
+ - Database can be explored from the shell, browsed in a web browser or file explorer, scanned with `find` and `grep`, etc.
+ - Database can be backed up, restored, versioned, etc. with tools like `git`, `rsync`, `duplicity`, etc.
+ - Alien files like `.git` or `.DS_Store` are harmlessly ignored if present
+ - Saving only changes what's changed to reduce I/O overhead and SSD wear
+
+Limitations and shortcomings:
+
+ - This creates a lot of tiny files, which is inefficient on some filesystems and might run into inode limits in extreme cases. For data sets with more than, say, a million items we recommend a filesystem like `btrfs` or `reiserfs`. Things like [redisfs](https://steve.fi/Software/redisfs/) are also worth exploring. On Linux another alternative is to put the database into `/dev/shm` (RAM disk) and then regularly back it up with `duplicity` or similar.
+ - The whole JSON object is held in memory *twice* for diffing purposes.
+ - Diffing traverses the whole tree and then updates the shadow copy, which makes `commit()` slow for huge data sets. This is not suitable for "big" data where "big" here is probably more than a few hundred megabytes.
+ - Recursion is used, so if you have object hierarchies that are incredibly deep (hundreds or more) it might be possible to overflow your stack and crash your app.
+ - This is not thread safe and must be guarded by a mutex if used in a threaded app.
+
+Caveats:
+
+ - Key names are escaped for safety in the filesystem, but we still don't recommend allowing external users to set just anything into your JSON store. See the point about recursion under limitations.
+
+Future:
+
+ - It would not be too hard to tie this into a filesystem change monitoring API and automatically read changes from disk if they are detected. This would allow the database to be edited "live" in the filesystem.
+ - In theory this could provide replication or clustering through distributed filesystems, file syncing, or things like [Amazon Elastic Filesystem](https://aws.amazon.com/efs/).
+ - Recursion could be factored out to get rid of any object hierarchy depth constraints.
+ - Mutexes could be integrated somehow to allow for finer grained locking in multithreaded apps.
+ - Diffing and selective updates could be made more memory and CPU efficient using hashes, etc.
+
+## How to Use
+
+The `offbase` class just extends [nlohmann::json](https://github.com/nlohmann/json) and gives you a JSON object. Take care to make sure you don't change the type of the 'root' object represented by the 'offbase' instance from JSON 'object'. Anything under it can of course be any JSON type, including any object.
+
+Just put data into the object and then periodically call `commit()` to persist changes to disk. The `commit()` method diffs the current contents of the object with what it knows to have been previously persisted to disk and modifies the representation on disk to match. This can be done after writes or periodically in a background thread.
+
+See comments in `offbase.hpp` for full documentation including details about error handling, etc.
+
+## Persistence format
+
+The base object represented by the `offbase` instance is persisted into a directory hierarchy under its base path. Files and directories are named according to a simple convention of `keyname.typecode` where `keyname` is an escaped key name (or hex array index in the case of arrays) and `typecode` is a single character indicating whether the item is a JSON value, array, or object.
+
+ - `*.V`: JSON values (actual value type is inferred during JSON parse)
+ - `*.O`: JSON objects (these are subdirectories)
+ - `*.A`: JSON arrays (also subdirectories containing items by hex array index)
+
+There are in theory simpler ways to represent JSON in a filesystem, such as the "flattened" JSON "pointer" format, but this has the disadvantage of not disambiguating objects vs. arrays. Offbase's persistence format is designed to perfectly reproduce the exact same JSON tree on load as was most recently committed.
diff --git a/ext/json/LICENSE.MIT b/ext/offbase/json/LICENSE.MIT
index e2ac4891..e2ac4891 100644
--- a/ext/json/LICENSE.MIT
+++ b/ext/offbase/json/LICENSE.MIT
diff --git a/ext/json/README.md b/ext/offbase/json/README.md
index c0bb61b1..c0bb61b1 100644
--- a/ext/json/README.md
+++ b/ext/offbase/json/README.md
diff --git a/ext/json/json.hpp b/ext/offbase/json/json.hpp
index 878fb899..878fb899 100644
--- a/ext/json/json.hpp
+++ b/ext/offbase/json/json.hpp
diff --git a/ext/offbase/offbase.hpp b/ext/offbase/offbase.hpp
new file mode 100644
index 00000000..acce8c4a
--- /dev/null
+++ b/ext/offbase/offbase.hpp
@@ -0,0 +1,393 @@
+/*
+ * Offbase: a super-minimal in-filesystem JSON object persistence store
+ */
+
+#ifndef OFFBASE_HPP__
+#define OFFBASE_HPP__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <dirent.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include <vector>
+#include <string>
+#include <map>
+
+#include "json/json.hpp"
+
+#define OFFBASE_PATH_SEP "/"
+
+/**
+ * A super-minimal in-filesystem JSON object persistence store
+ */
+class offbase : public nlohmann::json
+{
+public:
+ offbase(const char *p) :
+ nlohmann::json(nlohmann::json::object()),
+ _path(p),
+ _saved(nlohmann::json::object())
+ {
+ this->load();
+ }
+
+ ~offbase()
+ {
+ this->commit();
+ }
+
+ /**
+ * Load this instance from disk, clearing any existing contents first
+ *
+ * If the 'errors' vector is NULL, false is returned and reading aborts
+ * on any error. If this parameter is non-NULL the paths of errors will
+ * be added to the vector and reading will continue. False will only be
+ * returned on really big errors like no path being defined.
+ *
+ * @param errors If specified, fill this vector with the paths to any objects that fail read
+ * @return True on success, false on fatal error
+ */
+ inline bool load(std::vector<std::string> *errors = (std::vector<std::string> *)0)
+ {
+ if (!_path.length())
+ return false;
+ *this = nlohmann::json::object();
+ if (!_loadObj(_path,*this,errors))
+ return false;
+ _saved = *(reinterpret_cast<nlohmann::json *>(this));
+ return true;
+ }
+
+ /**
+ * Commit any pending changes to this object to disk
+ *
+ * @return True on success or false if an I/O error occurred
+ */
+ inline bool commit(std::vector<std::string> *errors = (std::vector<std::string> *)0)
+ {
+ if (!_path.length())
+ return false;
+ if (!_commitObj(_path,*this,&_saved,errors))
+ return false;
+ _saved = *(reinterpret_cast<nlohmann::json *>(this));
+ return true;
+ }
+
+ static inline std::string escapeKey(const std::string &k)
+ {
+ std::string e;
+ const char *ptr = k.data();
+ const char *eof = ptr + k.length();
+ char tmp[8];
+ while (ptr != eof) {
+ if ( ((*ptr >= 'a')&&(*ptr <= 'z')) || ((*ptr >= 'A')&&(*ptr <= 'Z')) || ((*ptr >= '0')&&(*ptr <= '9')) || (*ptr == '.') || (*ptr == '_') || (*ptr == '-') || (*ptr == ',') )
+ e.push_back(*ptr);
+ else {
+ snprintf(tmp,sizeof(tmp),"~%.2x",(unsigned int)*ptr);
+ e.append(tmp);
+ }
+ ++ptr;
+ }
+ return e;
+ }
+
+ static inline std::string unescapeKey(const std::string &k)
+ {
+ std::string u;
+ const char *ptr = k.data();
+ const char *eof = ptr + k.length();
+ char tmp[8];
+ while (ptr != eof) {
+ if (*ptr == '~') {
+ if (++ptr == eof) break;
+ tmp[0] = *ptr;
+ if (++ptr == eof) break;
+ tmp[1] = *(ptr++);
+ tmp[2] = (char)0;
+ u.push_back((char)strtol(tmp,(char **)0,16));
+ } else {
+ u.push_back(*(ptr++));
+ }
+ }
+ return u;
+ }
+
+private:
+ static inline bool _readFile(const char *path,std::string &buf)
+ {
+ char tmp[4096];
+ FILE *f = fopen(path,"rb");
+ if (f) {
+ for(;;) {
+ long n = (long)fread(tmp,1,sizeof(tmp),f);
+ if (n > 0)
+ buf.append(tmp,n);
+ else break;
+ }
+ fclose(f);
+ return true;
+ }
+ return false;
+ }
+
+ static inline bool _loadArr(const std::string &path,nlohmann::json &arr,std::vector<std::string> *errors)
+ {
+ std::map<unsigned long,nlohmann::json> atmp; // place into an ordered container first because filesystem does not guarantee this
+
+ struct dirent dbuf;
+ struct dirent *de;
+ DIR *d = opendir(path.c_str());
+ if (d) {
+ while (!readdir_d(d,&dbuf,&de)) {
+ if (!de) break;
+ const std::string name(de->d_name);
+ if (name.length() != 12) continue; // array entries are XXXXXXXXXX.T
+ if (name[name.length()-2] == '.') {
+ if (name[name.length()-1] == 'V') {
+ std::string buf;
+ if (_readFile((path + OFFBASE_PATH_SEP + name).c_str(),buf)) {
+ try {
+ atmp[strtoul(name.substr(0,10).c_str(),(char **)0,16)] = nlohmann::json::parse(buf);
+ } catch ( ... ) {
+ if (errors) {
+ errors->push_back(path + OFFBASE_PATH_SEP + name);
+ } else {
+ return false;
+ }
+ }
+ } else if (errors) {
+ errors->push_back(path + OFFBASE_PATH_SEP + name);
+ } else return false;
+ } else if (name[name.length()-1] == 'O') {
+ if (!_loadObj(path + OFFBASE_PATH_SEP + name,atmp[strtoul(name.substr(0,10).c_str(),(char **)0,16)] = nlohmann::json::object(),errors))
+ return false;
+ } else if (name[name.length()-1] == 'A') {
+ if (!_loadArr(path + OFFBASE_PATH_SEP + name,atmp[strtoul(name.substr(0,10).c_str(),(char **)0,16)] = nlohmann::json::array(),errors))
+ return false;
+ }
+ }
+ }
+ closedir(d);
+ } else if (errors) {
+ errors->push_back(path);
+ } else return false;
+
+ if (atmp.size() > 0) {
+ unsigned long lasti = 0;
+ for(std::map<unsigned long,nlohmann::json>::iterator i(atmp.begin());i!=atmp.end();++i) {
+ for(unsigned long k=lasti;k<i->first;++k) // fill any gaps with nulls
+ arr.push_back(nlohmann::json(std::nullptr_t));
+ lasti = i->first;
+ arr.push_back(i->second);
+ }
+ }
+
+ return true;
+ }
+
+ static inline bool _loadObj(const std::string &path,nlohmann::json &obj,std::vector<std::string> *errors)
+ {
+ struct dirent dbuf;
+ struct dirent *de;
+ DIR *d = opendir(path.c_str());
+ if (d) {
+ while (!readdir_d(d,&dbuf,&de)) {
+ if (!de) break;
+ if ((strcmp(de->d_name,".") == 0)||(strcmp(de->d_name,"..") == 0)) continue; // sanity check
+ const std::string name(de->d_name);
+ if (name.length() <= 2) continue;
+ if (name[name.length()-2] == '.') {
+ if (name[name.length()-1] == 'V') {
+ std::string buf;
+ if (_readFile((path + OFFBASE_PATH_SEP + name).c_str(),buf)) {
+ try {
+ obj[unescapeKey(name)] = nlohmann::json::parse(buf);
+ } catch ( ... ) {
+ if (errors) {
+ errors->push_back(path + OFFBASE_PATH_SEP + name);
+ } else {
+ return false;
+ }
+ }
+ } else if (errors) {
+ errors->push_back(path + OFFBASE_PATH_SEP + name);
+ } else return false;
+ } else if (name[name.length()-1] == 'O') {
+ if (!_loadObj(path + OFFBASE_PATH_SEP + name,obj[unescapeKey(name)] = nlohmann::json::object(),errors))
+ return false;
+ } else if (name[name.length()-1] == 'A') {
+ if (!_loadArr(path + OFFBASE_PATH_SEP + name,obj[unescapeKey(name)] = nlohmann::json::array(),errors))
+ return false;
+ }
+ }
+ }
+ closedir(d);
+ } else if (errors) {
+ errors->push_back(path);
+ } else return false;
+ return true;
+ }
+
+ static inline void _rmDashRf(const std::string &path)
+ {
+ struct dirent dbuf;
+ struct dirent *de;
+ DIR *d = opendir(path.c_str());
+ if (d) {
+ while (!readdir_r(d,&dbuf,&de)) {
+ if (!de) break;
+ if ((strcmp(de->d_name,".") == 0)||(strcmp(de->d_name,"..") == 0)) continue; // sanity check
+ const std::string full(path + OFFBASE_PATH_SEP + de->d_name);
+ if (unlink(full.c_str())) {
+ _rmDashRf(full);
+ rmdir(full.c_str());
+ }
+ }
+ closedir(d);
+ }
+ rmdir(path.c_str());
+ }
+
+ static inline bool _commitArr(const std::string &path,const nlohmann::json &arr,const nlohmann::json *previous,std::vector<std::string> *errors)
+ {
+ char tmp[32];
+
+ if (!arr.is_array())
+ return false;
+
+ mkdir(path.c_str(),0755);
+
+ for(unsigned long i=0;i<(unsigned long)arr.size();++i) {
+ const nlohmann::json &value = arr[i];
+
+ const nlohmann::json *next = (const nlohmann::json *)0;
+ if ((previous)&&(previous->is_array())&&(i < previous->size())) {
+ next = &((*previous)[i]);
+ if (*next == value)
+ continue;
+ }
+
+ if (value.is_object()) {
+ snprintf(tmp,sizeof(tmp),"%s%.10lx.O",OFFBASE_PATH_SEP,i);
+ if (!_commitObj(path + tmp,value,next,errors))
+ return false;
+ snprintf(tmp,sizeof(tmp),"%s%.10lx.V",OFFBASE_PATH_SEP,i);
+ unlink((path + tmp).c_str());
+ snprintf(tmp,sizeof(tmp),"%s%.10lx.A",OFFBASE_PATH_SEP,i);
+ _rmDashRf(path + tmp);
+ } else if (value.is_array()) {
+ snprintf(tmp,sizeof(tmp),"%s%.10lx.A",OFFBASE_PATH_SEP,i);
+ if (!_commitArr(path + tmp,value,next,errors))
+ return false;
+ snprintf(tmp,sizeof(tmp),"%s%.10lx.O",OFFBASE_PATH_SEP,i);
+ _rmDashRf(path + tmp);
+ snprintf(tmp,sizeof(tmp),"%s%.10lx.V",OFFBASE_PATH_SEP,i);
+ unlink((path + tmp).c_str());
+ } else {
+ snprintf(tmp,sizeof(tmp),"%s%.10lx.V",OFFBASE_PATH_SEP,i);
+ FILE *f = fopen((path + tmp).c_str(),"w");
+ if (f) {
+ const std::string v(value.dump());
+ if (fwrite(v.c_str(),v.length(),1,f) != 1) {
+ fclose(f);
+ return false;
+ } else {
+ fclose(f);
+ }
+ } else {
+ return false;
+ }
+ snprintf(tmp,sizeof(tmp),"%s%.10lx.A",OFFBASE_PATH_SEP,i);
+ _rmDashRf(path + tmp);
+ snprintf(tmp,sizeof(tmp),"%s%.10lx.O",OFFBASE_PATH_SEP,i);
+ _rmDashRf(path + tmp);
+ }
+ }
+
+ if ((previous)&&(previous->is_array())) {
+ for(unsigned long i=(unsigned long)arr.size();i<(unsigned long)previous->size();++i) {
+ snprintf(tmp,sizeof(tmp),"%s%.10lx.V",OFFBASE_PATH_SEP,i);
+ unlink((path + tmp).c_str());
+ snprintf(tmp,sizeof(tmp),"%s%.10lx.A",OFFBASE_PATH_SEP,i);
+ _rmDashRf(path + tmp);
+ snprintf(tmp,sizeof(tmp),"%s%.10lx.O",OFFBASE_PATH_SEP,i);
+ _rmDashRf(path + tmp);
+ }
+ }
+
+ return true;
+ }
+
+ static inline bool _commitObj(const std::string &path,const nlohmann::json &obj,const nlohmann::json *previous,std::vector<std::string> *errors)
+ {
+ if (!obj.is_object())
+ return false;
+
+ mkdir(path.c_str(),0755);
+
+ for(nlohmann::json::const_iterator i(obj.begin());i!=obj.end();++i) {
+ if (i.key().length() == 0)
+ continue;
+
+ const nlohmann::json *next = (const nlohmann::json *)0;
+ if ((previous)&&(previous->is_object())) {
+ nlohmann::json::const_iterator saved(previous->find(i.key()));
+ if (saved != previous->end()) {
+ next = &(saved.value());
+ if (i.value() == *next)
+ continue;
+ }
+ }
+
+ const std::string keyp(path + OFFBASE_PATH_SEP + escapeKey(i.key()));
+ if (i.value().is_object()) {
+ if (!_commitObj(keyp + ".O",i.value(),next,errors))
+ return false;
+ unlink((keyp + ".V").c_str());
+ _rmDashRf(keyp + ".A");
+ } else if (i.value().is_array()) {
+ if (!_commitArr(keyp + ".A",i.value(),next,errors))
+ return false;
+ unlink((keyp + ".V").c_str());
+ _rmDashRf(keyp + ".O");
+ } else {
+ FILE *f = fopen((keyp + ".V").c_str(),"w");
+ if (f) {
+ const std::string v(i.value().dump());
+ if (fwrite(v.c_str(),v.length(),1,f) != 1) {
+ fclose(f);
+ return false;
+ } else {
+ fclose(f);
+ }
+ } else {
+ return false;
+ }
+ _rmDashRf(keyp + ".A");
+ _rmDashRf(keyp + ".O");
+ }
+ }
+
+ if ((previous)&&(previous->is_object())) {
+ for(nlohmann::json::const_iterator i(previous->begin());i!=previous->end();++i) {
+ if ((i.key().length() > 0)&&(obj.find(i.key()) == obj.end())) {
+ const std::string keyp(path + OFFBASE_PATH_SEP + escapeKey(i.key()));
+ unlink((keyp + ".V").c_str());
+ _rmDashRf(keyp + ".A");
+ _rmDashRf(keyp + ".O");
+ }
+ }
+ }
+
+ return true;
+ }
+
+ std::string _path;
+ nlohmann::json _saved;
+};
+
+#endif