mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-26 14:05:51 +00:00
Moved cpp code to src/cpp and js code to src/js.
This commit is contained in:
201
src/cpp/database/SqliteDatabase.cpp
Normal file
201
src/cpp/database/SqliteDatabase.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
#include "SqliteDatabase.h"
|
||||
#include "sqlite3.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
SqliteDatabase::SqliteDatabase(const char* host) : Database(host,"","")
|
||||
{
|
||||
mConnection=NULL;
|
||||
mCurrentStmt=NULL;
|
||||
}
|
||||
|
||||
void SqliteDatabase::connect()
|
||||
{
|
||||
int rc = sqlite3_open(mHost.c_str(), &mConnection);
|
||||
if( rc )
|
||||
{
|
||||
cout << "Can't open database: " << mHost << " " << rc << endl;
|
||||
sqlite3_close(mConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void SqliteDatabase::disconnect()
|
||||
{
|
||||
sqlite3_finalize(mCurrentStmt);
|
||||
sqlite3_close(mConnection);
|
||||
}
|
||||
|
||||
// returns true if the query went ok
|
||||
bool SqliteDatabase::executeSQL(const char* sql, bool fail_ok)
|
||||
{
|
||||
sqlite3_finalize(mCurrentStmt);
|
||||
int rc = sqlite3_prepare_v2(mConnection, sql, -1, &mCurrentStmt, NULL);
|
||||
if (rc != SQLITE_OK )
|
||||
{
|
||||
if (!fail_ok)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
cout << "SQL Perror:" << rc << endl;
|
||||
cout << "Statement: " << sql << endl;
|
||||
cout << "Error: " << sqlite3_errmsg(mConnection) << endl;
|
||||
#endif
|
||||
}
|
||||
return false;
|
||||
}
|
||||
rc = sqlite3_step(mCurrentStmt);
|
||||
if (rc == SQLITE_ROW)
|
||||
{
|
||||
mMoreRows = true;
|
||||
}
|
||||
else if (rc == SQLITE_DONE)
|
||||
{
|
||||
mMoreRows = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
mMoreRows = false;
|
||||
if (!fail_ok)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
cout << "SQL Serror:" << rc << endl;
|
||||
cout << "Statement: " << sql << endl;
|
||||
cout << "Error: " << sqlite3_errmsg(mConnection) << endl;
|
||||
#endif
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// tells you how many rows were changed by an update or insert
|
||||
int SqliteDatabase::getNumRowsAffected()
|
||||
{
|
||||
// TODO: SqliteDatabase::getNumRowsAffected()
|
||||
return(0);
|
||||
}
|
||||
|
||||
int SqliteDatabase::getLastInsertID()
|
||||
{
|
||||
return(sqlite3_last_insert_rowid(mConnection));
|
||||
}
|
||||
|
||||
// returns false if there are no results
|
||||
bool SqliteDatabase::startIterRows()
|
||||
{
|
||||
mColNameTable.clear();
|
||||
mColNameTable.resize(sqlite3_column_count(mCurrentStmt));
|
||||
for(unsigned n=0; n<mColNameTable.size(); n++)
|
||||
{
|
||||
mColNameTable[n]=sqlite3_column_name(mCurrentStmt,n);
|
||||
}
|
||||
|
||||
return(mMoreRows);
|
||||
}
|
||||
|
||||
void SqliteDatabase::endIterRows()
|
||||
{
|
||||
sqlite3_finalize(mCurrentStmt);
|
||||
mCurrentStmt=NULL;
|
||||
}
|
||||
|
||||
// call this after you executeSQL
|
||||
// will return false if there are no more rows
|
||||
bool SqliteDatabase::getNextRow()
|
||||
{
|
||||
if(!mMoreRows) return(false);
|
||||
|
||||
int rc=sqlite3_step(mCurrentStmt);
|
||||
if(rc==SQLITE_ROW)
|
||||
{
|
||||
return(true);
|
||||
}else if(rc==SQLITE_DONE)
|
||||
{
|
||||
return(false);
|
||||
}else
|
||||
{
|
||||
cout << "SQL Rerror:" << rc << endl;
|
||||
return(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool SqliteDatabase::getNull(int colIndex)
|
||||
{
|
||||
return(SQLITE_NULL == sqlite3_column_type(mCurrentStmt, colIndex));
|
||||
}
|
||||
|
||||
char* SqliteDatabase::getStr(int colIndex,std::string& retStr)
|
||||
{
|
||||
retStr=(char*)sqlite3_column_text(mCurrentStmt, colIndex);
|
||||
return((char*)retStr.c_str());
|
||||
}
|
||||
|
||||
int32 SqliteDatabase::getInt(int colIndex)
|
||||
{
|
||||
return(sqlite3_column_int(mCurrentStmt, colIndex));
|
||||
}
|
||||
|
||||
float SqliteDatabase::getFloat(int colIndex)
|
||||
{
|
||||
return(sqlite3_column_double(mCurrentStmt, colIndex));
|
||||
}
|
||||
|
||||
bool SqliteDatabase::getBool(int colIndex)
|
||||
{
|
||||
return(sqlite3_column_int(mCurrentStmt, colIndex));
|
||||
}
|
||||
|
||||
int SqliteDatabase::getBinary(int colIndex,unsigned char* buf,int maxSize)
|
||||
{
|
||||
const void* blob=sqlite3_column_blob(mCurrentStmt, colIndex);
|
||||
int size=sqlite3_column_bytes(mCurrentStmt, colIndex);
|
||||
if(size<maxSize) maxSize=size;
|
||||
memcpy(buf,blob,maxSize);
|
||||
return(size);
|
||||
}
|
||||
|
||||
std::vector<unsigned char> SqliteDatabase::getBinary(int colIndex)
|
||||
{
|
||||
const unsigned char* blob = reinterpret_cast<const unsigned char*>(sqlite3_column_blob(mCurrentStmt, colIndex));
|
||||
size_t iSize = sqlite3_column_bytes(mCurrentStmt, colIndex);
|
||||
std::vector<unsigned char> vucResult;
|
||||
|
||||
vucResult.resize(iSize);
|
||||
std::copy(blob, blob+iSize, vucResult.begin());
|
||||
|
||||
return vucResult;
|
||||
}
|
||||
|
||||
uint64 SqliteDatabase::getBigInt(int colIndex)
|
||||
{
|
||||
return(sqlite3_column_int64(mCurrentStmt, colIndex));
|
||||
}
|
||||
|
||||
|
||||
/* http://www.sqlite.org/lang_expr.html
|
||||
BLOB literals are string literals containing hexadecimal data and preceded by a single "x" or "X" character. For example:
|
||||
X'53514C697465'
|
||||
*/
|
||||
void SqliteDatabase::escape(const unsigned char* start, int size, std::string& retStr)
|
||||
{
|
||||
static const char toHex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'A', 'B', 'C', 'D', 'E', 'F' };
|
||||
|
||||
retStr.resize(3 + (size * 2));
|
||||
|
||||
int pos = 0;
|
||||
retStr[pos++] = 'X';
|
||||
retStr[pos++] = '\'';
|
||||
|
||||
for (int n = 0; n < size; ++n)
|
||||
{
|
||||
retStr[pos++] = toHex[start[n] >> 4];
|
||||
retStr[pos++] = toHex[start[n] & 0x0f];
|
||||
}
|
||||
retStr[pos] = '\'';
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
45
src/cpp/database/SqliteDatabase.h
Normal file
45
src/cpp/database/SqliteDatabase.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "database.h"
|
||||
|
||||
struct sqlite3;
|
||||
struct sqlite3_stmt;
|
||||
|
||||
class SqliteDatabase : public Database
|
||||
{
|
||||
sqlite3* mConnection;
|
||||
sqlite3_stmt* mCurrentStmt;
|
||||
bool mMoreRows;
|
||||
public:
|
||||
SqliteDatabase(const char* host);
|
||||
|
||||
void connect();
|
||||
void disconnect();
|
||||
|
||||
// returns true if the query went ok
|
||||
bool executeSQL(const char* sql, bool fail_okay);
|
||||
|
||||
// tells you how many rows were changed by an update or insert
|
||||
int getNumRowsAffected();
|
||||
int getLastInsertID();
|
||||
|
||||
// returns false if there are no results
|
||||
bool startIterRows();
|
||||
void endIterRows();
|
||||
|
||||
// call this after you executeSQL
|
||||
// will return false if there are no more rows
|
||||
bool getNextRow();
|
||||
|
||||
bool getNull(int colIndex);
|
||||
char* getStr(int colIndex,std::string& retStr);
|
||||
int32 getInt(int colIndex);
|
||||
float getFloat(int colIndex);
|
||||
bool getBool(int colIndex);
|
||||
// returns amount stored in buf
|
||||
int getBinary(int colIndex,unsigned char* buf,int maxSize);
|
||||
std::vector<unsigned char> getBinary(int colIndex);
|
||||
uint64 getBigInt(int colIndex);
|
||||
|
||||
void escape(const unsigned char* start,int size,std::string& retStr);
|
||||
};
|
||||
|
||||
// vim:ts=4
|
||||
196
src/cpp/database/database.cpp
Normal file
196
src/cpp/database/database.cpp
Normal file
@@ -0,0 +1,196 @@
|
||||
#include "database.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
Database::Database(const char* host,const char* user,const char* pass) : mNumCol(0)
|
||||
{
|
||||
mDBPass = pass;
|
||||
mHost = host;
|
||||
mUser = user;
|
||||
}
|
||||
|
||||
Database::~Database()
|
||||
{
|
||||
}
|
||||
|
||||
bool Database::getNull(const char* colName)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (getColNumber(colName,&index))
|
||||
{
|
||||
return getNull(index);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
char* Database::getStr(const char* colName,std::string& retStr)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (getColNumber(colName,&index))
|
||||
{
|
||||
return getStr(index,retStr);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int32 Database::getInt(const char* colName)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (getColNumber(colName,&index))
|
||||
{
|
||||
return getInt(index);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
float Database::getFloat(const char* colName)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (getColNumber(colName,&index))
|
||||
{
|
||||
return getFloat(index);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Database::getBool(const char* colName)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (getColNumber(colName,&index))
|
||||
{
|
||||
return getBool(index);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Database::getBinary(const char* colName,unsigned char* buf,int maxSize)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (getColNumber(colName,&index))
|
||||
{
|
||||
return(getBinary(index,buf,maxSize));
|
||||
}
|
||||
|
||||
return(0);
|
||||
}
|
||||
|
||||
std::vector<unsigned char> Database::getBinary(const std::string& strColName)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (getColNumber(strColName.c_str(), &index))
|
||||
{
|
||||
return getBinary(index);
|
||||
}
|
||||
|
||||
return std::vector<unsigned char>();
|
||||
}
|
||||
|
||||
std::string Database::getStrBinary(const std::string& strColName)
|
||||
{
|
||||
// YYY Could eliminate a copy if getStrBinary was a template.
|
||||
return strCopy(getBinary(strColName.c_str()));
|
||||
}
|
||||
|
||||
uint64 Database::getBigInt(const char* colName)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (getColNumber(colName,&index))
|
||||
{
|
||||
return getBigInt(index);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// returns false if can't find col
|
||||
bool Database::getColNumber(const char* colName,int* retIndex)
|
||||
{
|
||||
for (unsigned int n=0; n<mColNameTable.size(); n++)
|
||||
{
|
||||
if (strcmp(colName,mColNameTable[n].c_str())==0)
|
||||
{
|
||||
*retIndex=n;
|
||||
return(true);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
int Database::getSingleDBValueInt(const char* sql)
|
||||
{
|
||||
int ret;
|
||||
if ( executeSQL(sql) && startIterRows()
|
||||
{
|
||||
ret=getInt(0);
|
||||
endIterRows();
|
||||
}
|
||||
else
|
||||
{
|
||||
//theUI->statusMsg("ERROR with database: %s",sql);
|
||||
ret=0;
|
||||
}
|
||||
return(ret);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
float Database::getSingleDBValueFloat(const char* sql)
|
||||
{
|
||||
float ret;
|
||||
if (executeSQL(sql) && startIterRows() && getNextRow())
|
||||
{
|
||||
ret=getFloat(0);
|
||||
endIterRows();
|
||||
}
|
||||
else
|
||||
{
|
||||
//theUI->statusMsg("ERROR with database: %s",sql);
|
||||
ret=0;
|
||||
}
|
||||
return(ret);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
char* Database::getSingleDBValueStr(const char* sql,std::string& retStr)
|
||||
{
|
||||
char* ret;
|
||||
if (executeSQL(sql) && startIterRows())
|
||||
{
|
||||
ret=getStr(0,retStr);
|
||||
endIterRows();
|
||||
}
|
||||
else
|
||||
{
|
||||
//theUI->statusMsg("ERROR with database: %s",sql);
|
||||
ret=0;
|
||||
}
|
||||
return(ret);
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string Database::escape(const std::string strValue)
|
||||
{
|
||||
std::string strReturn;
|
||||
|
||||
escape(reinterpret_cast<const unsigned char*>(strValue.c_str()), strValue.size(), strReturn);
|
||||
|
||||
return strReturn;
|
||||
}
|
||||
// vim:ts=4
|
||||
92
src/cpp/database/database.h
Normal file
92
src/cpp/database/database.h
Normal file
@@ -0,0 +1,92 @@
|
||||
#ifndef __DATABASE__
|
||||
#define __DATABASE__
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "../ripple/types.h"
|
||||
#include "../ripple/utils.h"
|
||||
|
||||
#define SQL_FOREACH(_db, _strQuery) \
|
||||
if ((_db)->executeSQL(_strQuery)) \
|
||||
for (bool _bMore = (_db)->startIterRows(); _bMore; _bMore = (_db)->getNextRow())
|
||||
|
||||
#define SQL_EXISTS(_db, _strQuery) \
|
||||
((_db)->executeSQL(_strQuery) && (_db)->startIterRows())
|
||||
|
||||
/*
|
||||
this maintains the connection to the database
|
||||
*/
|
||||
class Database
|
||||
{
|
||||
protected:
|
||||
int mNumCol;
|
||||
std::string mUser;
|
||||
std::string mHost;
|
||||
std::string mDBPass;
|
||||
std::vector<std::string> mColNameTable;
|
||||
|
||||
bool getColNumber(const char* colName, int* retIndex);
|
||||
|
||||
public:
|
||||
Database(const char* host,const char* user,const char* pass);
|
||||
static Database* newMysqlDatabase(const char* host,const char* user,const char* pass);
|
||||
virtual ~Database();
|
||||
|
||||
virtual void connect()=0;
|
||||
virtual void disconnect()=0;
|
||||
|
||||
std::string& getPass(){ return(mDBPass); }
|
||||
|
||||
virtual void escape(const unsigned char* start,int size,std::string& retStr)=0;
|
||||
std::string escape(const std::string strValue);
|
||||
|
||||
// returns true if the query went ok
|
||||
virtual bool executeSQL(const char* sql, bool fail_okay=false)=0;
|
||||
|
||||
bool executeSQL(std::string strSql, bool fail_okay=false) {
|
||||
return executeSQL(strSql.c_str(), fail_okay);
|
||||
}
|
||||
|
||||
// tells you how many rows were changed by an update or insert
|
||||
virtual int getNumRowsAffected()=0;
|
||||
virtual int getLastInsertID()=0;
|
||||
|
||||
// returns false if there are no results
|
||||
virtual bool startIterRows()=0;
|
||||
virtual void endIterRows()=0;
|
||||
|
||||
// call this after you executeSQL
|
||||
// will return false if there are no more rows
|
||||
virtual bool getNextRow()=0;
|
||||
|
||||
// get Data from the current row
|
||||
bool getNull(const char* colName);
|
||||
char* getStr(const char* colName,std::string& retStr);
|
||||
std::string getStrBinary(const std::string& strColName);
|
||||
int32 getInt(const char* colName);
|
||||
float getFloat(const char* colName);
|
||||
bool getBool(const char* colName);
|
||||
|
||||
// returns amount stored in buf
|
||||
int getBinary(const char* colName, unsigned char* buf, int maxSize);
|
||||
std::vector<unsigned char> getBinary(const std::string& strColName);
|
||||
|
||||
uint64 getBigInt(const char* colName);
|
||||
|
||||
virtual bool getNull(int colIndex)=0;
|
||||
virtual char* getStr(int colIndex,std::string& retStr)=0;
|
||||
virtual int32 getInt(int colIndex)=0;
|
||||
virtual float getFloat(int colIndex)=0;
|
||||
virtual bool getBool(int colIndex)=0;
|
||||
virtual int getBinary(int colIndex,unsigned char* buf,int maxSize)=0;
|
||||
virtual uint64 getBigInt(int colIndex)=0;
|
||||
virtual std::vector<unsigned char> getBinary(int colIndex)=0;
|
||||
|
||||
// int getSingleDBValueInt(const char* sql);
|
||||
// float getSingleDBValueFloat(const char* sql);
|
||||
// char* getSingleDBValueStr(const char* sql, std::string& retStr);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
// vim:ts=4
|
||||
136
src/cpp/database/linux/mysqldatabase.cpp
Normal file
136
src/cpp/database/linux/mysqldatabase.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
#include "mysqldatabase.h"
|
||||
#include "reportingmechanism.h"
|
||||
#include "string/i4string.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
Database* Database::newDatabase(const char* host,const char* user,const char* pass)
|
||||
{
|
||||
return(new MySqlDatabase(host,user,pass));
|
||||
}
|
||||
|
||||
MySqlDatabase::MySqlDatabase(const char* host,const char* user,const char* pass) : Database(host,user,pass)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
MySqlDatabase::~MySqlDatabase()
|
||||
{
|
||||
}
|
||||
|
||||
void MySqlDatabase::connect()
|
||||
{
|
||||
mysql_init(&mMysql);
|
||||
mysql_options(&mMysql,MYSQL_READ_DEFAULT_GROUP,"i4min");
|
||||
if(!mysql_real_connect(&mMysql,mHost,mUser,mDBPass,NULL,0,NULL,0))
|
||||
{
|
||||
theUI->statusMsg("Failed to connect to database: Error: %s\n", mysql_error(&mMysql));
|
||||
}else theUI->statusMsg("Connection Established to DB");
|
||||
}
|
||||
|
||||
void MySqlDatabase::disconnect()
|
||||
{
|
||||
mysql_close(&mMysql);
|
||||
}
|
||||
|
||||
int MySqlDatabase::getNumRowsAffected()
|
||||
{
|
||||
return( mysql_affected_rows(&mMysql));
|
||||
}
|
||||
|
||||
// returns true if the query went ok
|
||||
bool MySqlDatabase::executeSQL(const char* sql)
|
||||
{
|
||||
int ret=mysql_query(&mMysql, sql);
|
||||
if(ret)
|
||||
{
|
||||
connect();
|
||||
int ret=mysql_query(&mMysql, sql);
|
||||
if(ret)
|
||||
{
|
||||
theUI->statusMsg("ERROR with executeSQL: %d %s",ret,sql);
|
||||
return(false);
|
||||
}
|
||||
}
|
||||
return(true);
|
||||
}
|
||||
|
||||
bool MySqlDatabase::startIterRows()
|
||||
{
|
||||
mResult=mysql_store_result(&mMysql);
|
||||
// get total number of columns from the resultset
|
||||
mNumCol = mysql_num_fields(mResult);
|
||||
if(mNumCol)
|
||||
{
|
||||
delete[](mColNameTable);
|
||||
mColNameTable=new i4_str[mNumCol];
|
||||
|
||||
// fill out the column name table
|
||||
for(int n = 0; n < mNumCol; n++)
|
||||
{
|
||||
MYSQL_FIELD* field=mysql_fetch_field(mResult);
|
||||
mColNameTable[n]= field->name;
|
||||
}
|
||||
return(true);
|
||||
}
|
||||
return(false);
|
||||
}
|
||||
|
||||
// call this after you executeSQL
|
||||
// will return false if there are no more rows
|
||||
bool MySqlDatabase::getNextRow()
|
||||
{
|
||||
mCurRow=mysql_fetch_row(mResult);
|
||||
|
||||
return(mCurRow!=NULL);
|
||||
}
|
||||
|
||||
char* MySqlDatabase::getStr(int colIndex,i4_str* retStr)
|
||||
{
|
||||
if(mCurRow[colIndex])
|
||||
{
|
||||
(*retStr)=mCurRow[colIndex];
|
||||
}else (*retStr)="";
|
||||
|
||||
return(*retStr);
|
||||
}
|
||||
|
||||
w32 MySqlDatabase::getInt(int colIndex)
|
||||
{
|
||||
|
||||
if(mCurRow[colIndex])
|
||||
{
|
||||
w32 ret=atoll(mCurRow[colIndex]);
|
||||
//theUI->statusMsg("getInt: %s,%c,%u",mCurRow[colIndex],mCurRow[colIndex][0],ret);
|
||||
return(ret);
|
||||
}
|
||||
return(0);
|
||||
}
|
||||
|
||||
float MySqlDatabase::getFloat(int colIndex)
|
||||
{
|
||||
if(mCurRow[colIndex])
|
||||
{
|
||||
float ret=atof(mCurRow[colIndex]);
|
||||
return(ret);
|
||||
}
|
||||
return(0.0);
|
||||
}
|
||||
|
||||
bool MySqlDatabase::getBool(int colIndex)
|
||||
{
|
||||
if(mCurRow[colIndex])
|
||||
{
|
||||
int ret=atoi(mCurRow[colIndex]);
|
||||
return(ret);
|
||||
}
|
||||
return(false);
|
||||
}
|
||||
|
||||
|
||||
void MySqlDatabase::endIterRows()
|
||||
{
|
||||
mysql_free_result(mResult);
|
||||
}
|
||||
|
||||
|
||||
47
src/cpp/database/linux/mysqldatabase.h
Normal file
47
src/cpp/database/linux/mysqldatabase.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#ifndef __MYSQLDATABASE__
|
||||
#define __MYSQLDATABASE__
|
||||
|
||||
#include "../database.h"
|
||||
#include "string/i4string.h"
|
||||
#include "mysql/mysql.h"
|
||||
|
||||
/*
|
||||
this maintains the connection to the database
|
||||
*/
|
||||
class MySqlDatabase : public Database
|
||||
{
|
||||
MYSQL mMysql;
|
||||
MYSQL_RES* mResult;
|
||||
MYSQL_ROW mCurRow;
|
||||
|
||||
public:
|
||||
MySqlDatabase(const char* host,const char* user,const char* pass);
|
||||
~MySqlDatabase();
|
||||
|
||||
void connect();
|
||||
void disconnect();
|
||||
|
||||
// returns true if the query went ok
|
||||
bool executeSQL(const char* sql);
|
||||
|
||||
int getNumRowsAffected();
|
||||
|
||||
// returns false if there are no results
|
||||
bool startIterRows();
|
||||
void endIterRows();
|
||||
|
||||
// call this after you executeSQL
|
||||
// will return false if there are no more rows
|
||||
bool getNextRow();
|
||||
|
||||
// get Data from the current row
|
||||
|
||||
char* getStr(int colIndex,i4_str* retStr);
|
||||
w32 getInt(int colIndex);
|
||||
float getFloat(int colIndex);
|
||||
bool getBool(int colIndex);
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
134161
src/cpp/database/sqlite3.c
Normal file
134161
src/cpp/database/sqlite3.c
Normal file
File diff suppressed because it is too large
Load Diff
6999
src/cpp/database/sqlite3.h
Normal file
6999
src/cpp/database/sqlite3.h
Normal file
File diff suppressed because it is too large
Load Diff
447
src/cpp/database/sqlite3ext.h
Normal file
447
src/cpp/database/sqlite3ext.h
Normal file
@@ -0,0 +1,447 @@
|
||||
/*
|
||||
** 2006 June 7
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This header file defines the SQLite interface for use by
|
||||
** shared libraries that want to be imported as extensions into
|
||||
** an SQLite instance. Shared libraries that intend to be loaded
|
||||
** as extensions by SQLite should #include this file instead of
|
||||
** sqlite3.h.
|
||||
*/
|
||||
#ifndef _SQLITE3EXT_H_
|
||||
#define _SQLITE3EXT_H_
|
||||
#include "sqlite3.h"
|
||||
|
||||
typedef struct sqlite3_api_routines sqlite3_api_routines;
|
||||
|
||||
/*
|
||||
** The following structure holds pointers to all of the SQLite API
|
||||
** routines.
|
||||
**
|
||||
** WARNING: In order to maintain backwards compatibility, add new
|
||||
** interfaces to the end of this structure only. If you insert new
|
||||
** interfaces in the middle of this structure, then older different
|
||||
** versions of SQLite will not be able to load each others' shared
|
||||
** libraries!
|
||||
*/
|
||||
struct sqlite3_api_routines {
|
||||
void * (*aggregate_context)(sqlite3_context*,int nBytes);
|
||||
int (*aggregate_count)(sqlite3_context*);
|
||||
int (*bind_blob)(sqlite3_stmt*,int,const void*,int n,void(*)(void*));
|
||||
int (*bind_double)(sqlite3_stmt*,int,double);
|
||||
int (*bind_int)(sqlite3_stmt*,int,int);
|
||||
int (*bind_int64)(sqlite3_stmt*,int,sqlite_int64);
|
||||
int (*bind_null)(sqlite3_stmt*,int);
|
||||
int (*bind_parameter_count)(sqlite3_stmt*);
|
||||
int (*bind_parameter_index)(sqlite3_stmt*,const char*zName);
|
||||
const char * (*bind_parameter_name)(sqlite3_stmt*,int);
|
||||
int (*bind_text)(sqlite3_stmt*,int,const char*,int n,void(*)(void*));
|
||||
int (*bind_text16)(sqlite3_stmt*,int,const void*,int,void(*)(void*));
|
||||
int (*bind_value)(sqlite3_stmt*,int,const sqlite3_value*);
|
||||
int (*busy_handler)(sqlite3*,int(*)(void*,int),void*);
|
||||
int (*busy_timeout)(sqlite3*,int ms);
|
||||
int (*changes)(sqlite3*);
|
||||
int (*close)(sqlite3*);
|
||||
int (*collation_needed)(sqlite3*,void*,void(*)(void*,sqlite3*,
|
||||
int eTextRep,const char*));
|
||||
int (*collation_needed16)(sqlite3*,void*,void(*)(void*,sqlite3*,
|
||||
int eTextRep,const void*));
|
||||
const void * (*column_blob)(sqlite3_stmt*,int iCol);
|
||||
int (*column_bytes)(sqlite3_stmt*,int iCol);
|
||||
int (*column_bytes16)(sqlite3_stmt*,int iCol);
|
||||
int (*column_count)(sqlite3_stmt*pStmt);
|
||||
const char * (*column_database_name)(sqlite3_stmt*,int);
|
||||
const void * (*column_database_name16)(sqlite3_stmt*,int);
|
||||
const char * (*column_decltype)(sqlite3_stmt*,int i);
|
||||
const void * (*column_decltype16)(sqlite3_stmt*,int);
|
||||
double (*column_double)(sqlite3_stmt*,int iCol);
|
||||
int (*column_int)(sqlite3_stmt*,int iCol);
|
||||
sqlite_int64 (*column_int64)(sqlite3_stmt*,int iCol);
|
||||
const char * (*column_name)(sqlite3_stmt*,int);
|
||||
const void * (*column_name16)(sqlite3_stmt*,int);
|
||||
const char * (*column_origin_name)(sqlite3_stmt*,int);
|
||||
const void * (*column_origin_name16)(sqlite3_stmt*,int);
|
||||
const char * (*column_table_name)(sqlite3_stmt*,int);
|
||||
const void * (*column_table_name16)(sqlite3_stmt*,int);
|
||||
const unsigned char * (*column_text)(sqlite3_stmt*,int iCol);
|
||||
const void * (*column_text16)(sqlite3_stmt*,int iCol);
|
||||
int (*column_type)(sqlite3_stmt*,int iCol);
|
||||
sqlite3_value* (*column_value)(sqlite3_stmt*,int iCol);
|
||||
void * (*commit_hook)(sqlite3*,int(*)(void*),void*);
|
||||
int (*complete)(const char*sql);
|
||||
int (*complete16)(const void*sql);
|
||||
int (*create_collation)(sqlite3*,const char*,int,void*,
|
||||
int(*)(void*,int,const void*,int,const void*));
|
||||
int (*create_collation16)(sqlite3*,const void*,int,void*,
|
||||
int(*)(void*,int,const void*,int,const void*));
|
||||
int (*create_function)(sqlite3*,const char*,int,int,void*,
|
||||
void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
|
||||
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
|
||||
void (*xFinal)(sqlite3_context*));
|
||||
int (*create_function16)(sqlite3*,const void*,int,int,void*,
|
||||
void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
|
||||
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
|
||||
void (*xFinal)(sqlite3_context*));
|
||||
int (*create_module)(sqlite3*,const char*,const sqlite3_module*,void*);
|
||||
int (*data_count)(sqlite3_stmt*pStmt);
|
||||
sqlite3 * (*db_handle)(sqlite3_stmt*);
|
||||
int (*declare_vtab)(sqlite3*,const char*);
|
||||
int (*enable_shared_cache)(int);
|
||||
int (*errcode)(sqlite3*db);
|
||||
const char * (*errmsg)(sqlite3*);
|
||||
const void * (*errmsg16)(sqlite3*);
|
||||
int (*exec)(sqlite3*,const char*,sqlite3_callback,void*,char**);
|
||||
int (*expired)(sqlite3_stmt*);
|
||||
int (*finalize)(sqlite3_stmt*pStmt);
|
||||
void (*free)(void*);
|
||||
void (*free_table)(char**result);
|
||||
int (*get_autocommit)(sqlite3*);
|
||||
void * (*get_auxdata)(sqlite3_context*,int);
|
||||
int (*get_table)(sqlite3*,const char*,char***,int*,int*,char**);
|
||||
int (*global_recover)(void);
|
||||
void (*interruptx)(sqlite3*);
|
||||
sqlite_int64 (*last_insert_rowid)(sqlite3*);
|
||||
const char * (*libversion)(void);
|
||||
int (*libversion_number)(void);
|
||||
void *(*malloc)(int);
|
||||
char * (*mprintf)(const char*,...);
|
||||
int (*open)(const char*,sqlite3**);
|
||||
int (*open16)(const void*,sqlite3**);
|
||||
int (*prepare)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
|
||||
int (*prepare16)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
|
||||
void * (*profile)(sqlite3*,void(*)(void*,const char*,sqlite_uint64),void*);
|
||||
void (*progress_handler)(sqlite3*,int,int(*)(void*),void*);
|
||||
void *(*realloc)(void*,int);
|
||||
int (*reset)(sqlite3_stmt*pStmt);
|
||||
void (*result_blob)(sqlite3_context*,const void*,int,void(*)(void*));
|
||||
void (*result_double)(sqlite3_context*,double);
|
||||
void (*result_error)(sqlite3_context*,const char*,int);
|
||||
void (*result_error16)(sqlite3_context*,const void*,int);
|
||||
void (*result_int)(sqlite3_context*,int);
|
||||
void (*result_int64)(sqlite3_context*,sqlite_int64);
|
||||
void (*result_null)(sqlite3_context*);
|
||||
void (*result_text)(sqlite3_context*,const char*,int,void(*)(void*));
|
||||
void (*result_text16)(sqlite3_context*,const void*,int,void(*)(void*));
|
||||
void (*result_text16be)(sqlite3_context*,const void*,int,void(*)(void*));
|
||||
void (*result_text16le)(sqlite3_context*,const void*,int,void(*)(void*));
|
||||
void (*result_value)(sqlite3_context*,sqlite3_value*);
|
||||
void * (*rollback_hook)(sqlite3*,void(*)(void*),void*);
|
||||
int (*set_authorizer)(sqlite3*,int(*)(void*,int,const char*,const char*,
|
||||
const char*,const char*),void*);
|
||||
void (*set_auxdata)(sqlite3_context*,int,void*,void (*)(void*));
|
||||
char * (*snprintf)(int,char*,const char*,...);
|
||||
int (*step)(sqlite3_stmt*);
|
||||
int (*table_column_metadata)(sqlite3*,const char*,const char*,const char*,
|
||||
char const**,char const**,int*,int*,int*);
|
||||
void (*thread_cleanup)(void);
|
||||
int (*total_changes)(sqlite3*);
|
||||
void * (*trace)(sqlite3*,void(*xTrace)(void*,const char*),void*);
|
||||
int (*transfer_bindings)(sqlite3_stmt*,sqlite3_stmt*);
|
||||
void * (*update_hook)(sqlite3*,void(*)(void*,int ,char const*,char const*,
|
||||
sqlite_int64),void*);
|
||||
void * (*user_data)(sqlite3_context*);
|
||||
const void * (*value_blob)(sqlite3_value*);
|
||||
int (*value_bytes)(sqlite3_value*);
|
||||
int (*value_bytes16)(sqlite3_value*);
|
||||
double (*value_double)(sqlite3_value*);
|
||||
int (*value_int)(sqlite3_value*);
|
||||
sqlite_int64 (*value_int64)(sqlite3_value*);
|
||||
int (*value_numeric_type)(sqlite3_value*);
|
||||
const unsigned char * (*value_text)(sqlite3_value*);
|
||||
const void * (*value_text16)(sqlite3_value*);
|
||||
const void * (*value_text16be)(sqlite3_value*);
|
||||
const void * (*value_text16le)(sqlite3_value*);
|
||||
int (*value_type)(sqlite3_value*);
|
||||
char *(*vmprintf)(const char*,va_list);
|
||||
/* Added ??? */
|
||||
int (*overload_function)(sqlite3*, const char *zFuncName, int nArg);
|
||||
/* Added by 3.3.13 */
|
||||
int (*prepare_v2)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
|
||||
int (*prepare16_v2)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
|
||||
int (*clear_bindings)(sqlite3_stmt*);
|
||||
/* Added by 3.4.1 */
|
||||
int (*create_module_v2)(sqlite3*,const char*,const sqlite3_module*,void*,
|
||||
void (*xDestroy)(void *));
|
||||
/* Added by 3.5.0 */
|
||||
int (*bind_zeroblob)(sqlite3_stmt*,int,int);
|
||||
int (*blob_bytes)(sqlite3_blob*);
|
||||
int (*blob_close)(sqlite3_blob*);
|
||||
int (*blob_open)(sqlite3*,const char*,const char*,const char*,sqlite3_int64,
|
||||
int,sqlite3_blob**);
|
||||
int (*blob_read)(sqlite3_blob*,void*,int,int);
|
||||
int (*blob_write)(sqlite3_blob*,const void*,int,int);
|
||||
int (*create_collation_v2)(sqlite3*,const char*,int,void*,
|
||||
int(*)(void*,int,const void*,int,const void*),
|
||||
void(*)(void*));
|
||||
int (*file_control)(sqlite3*,const char*,int,void*);
|
||||
sqlite3_int64 (*memory_highwater)(int);
|
||||
sqlite3_int64 (*memory_used)(void);
|
||||
sqlite3_mutex *(*mutex_alloc)(int);
|
||||
void (*mutex_enter)(sqlite3_mutex*);
|
||||
void (*mutex_free)(sqlite3_mutex*);
|
||||
void (*mutex_leave)(sqlite3_mutex*);
|
||||
int (*mutex_try)(sqlite3_mutex*);
|
||||
int (*open_v2)(const char*,sqlite3**,int,const char*);
|
||||
int (*release_memory)(int);
|
||||
void (*result_error_nomem)(sqlite3_context*);
|
||||
void (*result_error_toobig)(sqlite3_context*);
|
||||
int (*sleep)(int);
|
||||
void (*soft_heap_limit)(int);
|
||||
sqlite3_vfs *(*vfs_find)(const char*);
|
||||
int (*vfs_register)(sqlite3_vfs*,int);
|
||||
int (*vfs_unregister)(sqlite3_vfs*);
|
||||
int (*xthreadsafe)(void);
|
||||
void (*result_zeroblob)(sqlite3_context*,int);
|
||||
void (*result_error_code)(sqlite3_context*,int);
|
||||
int (*test_control)(int, ...);
|
||||
void (*randomness)(int,void*);
|
||||
sqlite3 *(*context_db_handle)(sqlite3_context*);
|
||||
int (*extended_result_codes)(sqlite3*,int);
|
||||
int (*limit)(sqlite3*,int,int);
|
||||
sqlite3_stmt *(*next_stmt)(sqlite3*,sqlite3_stmt*);
|
||||
const char *(*sql)(sqlite3_stmt*);
|
||||
int (*status)(int,int*,int*,int);
|
||||
int (*backup_finish)(sqlite3_backup*);
|
||||
sqlite3_backup *(*backup_init)(sqlite3*,const char*,sqlite3*,const char*);
|
||||
int (*backup_pagecount)(sqlite3_backup*);
|
||||
int (*backup_remaining)(sqlite3_backup*);
|
||||
int (*backup_step)(sqlite3_backup*,int);
|
||||
const char *(*compileoption_get)(int);
|
||||
int (*compileoption_used)(const char*);
|
||||
int (*create_function_v2)(sqlite3*,const char*,int,int,void*,
|
||||
void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
|
||||
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
|
||||
void (*xFinal)(sqlite3_context*),
|
||||
void(*xDestroy)(void*));
|
||||
int (*db_config)(sqlite3*,int,...);
|
||||
sqlite3_mutex *(*db_mutex)(sqlite3*);
|
||||
int (*db_status)(sqlite3*,int,int*,int*,int);
|
||||
int (*extended_errcode)(sqlite3*);
|
||||
void (*log)(int,const char*,...);
|
||||
sqlite3_int64 (*soft_heap_limit64)(sqlite3_int64);
|
||||
const char *(*sourceid)(void);
|
||||
int (*stmt_status)(sqlite3_stmt*,int,int);
|
||||
int (*strnicmp)(const char*,const char*,int);
|
||||
int (*unlock_notify)(sqlite3*,void(*)(void**,int),void*);
|
||||
int (*wal_autocheckpoint)(sqlite3*,int);
|
||||
int (*wal_checkpoint)(sqlite3*,const char*);
|
||||
void *(*wal_hook)(sqlite3*,int(*)(void*,sqlite3*,const char*,int),void*);
|
||||
int (*blob_reopen)(sqlite3_blob*,sqlite3_int64);
|
||||
int (*vtab_config)(sqlite3*,int op,...);
|
||||
int (*vtab_on_conflict)(sqlite3*);
|
||||
};
|
||||
|
||||
/*
|
||||
** The following macros redefine the API routines so that they are
|
||||
** redirected throught the global sqlite3_api structure.
|
||||
**
|
||||
** This header file is also used by the loadext.c source file
|
||||
** (part of the main SQLite library - not an extension) so that
|
||||
** it can get access to the sqlite3_api_routines structure
|
||||
** definition. But the main library does not want to redefine
|
||||
** the API. So the redefinition macros are only valid if the
|
||||
** SQLITE_CORE macros is undefined.
|
||||
*/
|
||||
#ifndef SQLITE_CORE
|
||||
#define sqlite3_aggregate_context sqlite3_api->aggregate_context
|
||||
#ifndef SQLITE_OMIT_DEPRECATED
|
||||
#define sqlite3_aggregate_count sqlite3_api->aggregate_count
|
||||
#endif
|
||||
#define sqlite3_bind_blob sqlite3_api->bind_blob
|
||||
#define sqlite3_bind_double sqlite3_api->bind_double
|
||||
#define sqlite3_bind_int sqlite3_api->bind_int
|
||||
#define sqlite3_bind_int64 sqlite3_api->bind_int64
|
||||
#define sqlite3_bind_null sqlite3_api->bind_null
|
||||
#define sqlite3_bind_parameter_count sqlite3_api->bind_parameter_count
|
||||
#define sqlite3_bind_parameter_index sqlite3_api->bind_parameter_index
|
||||
#define sqlite3_bind_parameter_name sqlite3_api->bind_parameter_name
|
||||
#define sqlite3_bind_text sqlite3_api->bind_text
|
||||
#define sqlite3_bind_text16 sqlite3_api->bind_text16
|
||||
#define sqlite3_bind_value sqlite3_api->bind_value
|
||||
#define sqlite3_busy_handler sqlite3_api->busy_handler
|
||||
#define sqlite3_busy_timeout sqlite3_api->busy_timeout
|
||||
#define sqlite3_changes sqlite3_api->changes
|
||||
#define sqlite3_close sqlite3_api->close
|
||||
#define sqlite3_collation_needed sqlite3_api->collation_needed
|
||||
#define sqlite3_collation_needed16 sqlite3_api->collation_needed16
|
||||
#define sqlite3_column_blob sqlite3_api->column_blob
|
||||
#define sqlite3_column_bytes sqlite3_api->column_bytes
|
||||
#define sqlite3_column_bytes16 sqlite3_api->column_bytes16
|
||||
#define sqlite3_column_count sqlite3_api->column_count
|
||||
#define sqlite3_column_database_name sqlite3_api->column_database_name
|
||||
#define sqlite3_column_database_name16 sqlite3_api->column_database_name16
|
||||
#define sqlite3_column_decltype sqlite3_api->column_decltype
|
||||
#define sqlite3_column_decltype16 sqlite3_api->column_decltype16
|
||||
#define sqlite3_column_double sqlite3_api->column_double
|
||||
#define sqlite3_column_int sqlite3_api->column_int
|
||||
#define sqlite3_column_int64 sqlite3_api->column_int64
|
||||
#define sqlite3_column_name sqlite3_api->column_name
|
||||
#define sqlite3_column_name16 sqlite3_api->column_name16
|
||||
#define sqlite3_column_origin_name sqlite3_api->column_origin_name
|
||||
#define sqlite3_column_origin_name16 sqlite3_api->column_origin_name16
|
||||
#define sqlite3_column_table_name sqlite3_api->column_table_name
|
||||
#define sqlite3_column_table_name16 sqlite3_api->column_table_name16
|
||||
#define sqlite3_column_text sqlite3_api->column_text
|
||||
#define sqlite3_column_text16 sqlite3_api->column_text16
|
||||
#define sqlite3_column_type sqlite3_api->column_type
|
||||
#define sqlite3_column_value sqlite3_api->column_value
|
||||
#define sqlite3_commit_hook sqlite3_api->commit_hook
|
||||
#define sqlite3_complete sqlite3_api->complete
|
||||
#define sqlite3_complete16 sqlite3_api->complete16
|
||||
#define sqlite3_create_collation sqlite3_api->create_collation
|
||||
#define sqlite3_create_collation16 sqlite3_api->create_collation16
|
||||
#define sqlite3_create_function sqlite3_api->create_function
|
||||
#define sqlite3_create_function16 sqlite3_api->create_function16
|
||||
#define sqlite3_create_module sqlite3_api->create_module
|
||||
#define sqlite3_create_module_v2 sqlite3_api->create_module_v2
|
||||
#define sqlite3_data_count sqlite3_api->data_count
|
||||
#define sqlite3_db_handle sqlite3_api->db_handle
|
||||
#define sqlite3_declare_vtab sqlite3_api->declare_vtab
|
||||
#define sqlite3_enable_shared_cache sqlite3_api->enable_shared_cache
|
||||
#define sqlite3_errcode sqlite3_api->errcode
|
||||
#define sqlite3_errmsg sqlite3_api->errmsg
|
||||
#define sqlite3_errmsg16 sqlite3_api->errmsg16
|
||||
#define sqlite3_exec sqlite3_api->exec
|
||||
#ifndef SQLITE_OMIT_DEPRECATED
|
||||
#define sqlite3_expired sqlite3_api->expired
|
||||
#endif
|
||||
#define sqlite3_finalize sqlite3_api->finalize
|
||||
#define sqlite3_free sqlite3_api->free
|
||||
#define sqlite3_free_table sqlite3_api->free_table
|
||||
#define sqlite3_get_autocommit sqlite3_api->get_autocommit
|
||||
#define sqlite3_get_auxdata sqlite3_api->get_auxdata
|
||||
#define sqlite3_get_table sqlite3_api->get_table
|
||||
#ifndef SQLITE_OMIT_DEPRECATED
|
||||
#define sqlite3_global_recover sqlite3_api->global_recover
|
||||
#endif
|
||||
#define sqlite3_interrupt sqlite3_api->interruptx
|
||||
#define sqlite3_last_insert_rowid sqlite3_api->last_insert_rowid
|
||||
#define sqlite3_libversion sqlite3_api->libversion
|
||||
#define sqlite3_libversion_number sqlite3_api->libversion_number
|
||||
#define sqlite3_malloc sqlite3_api->malloc
|
||||
#define sqlite3_mprintf sqlite3_api->mprintf
|
||||
#define sqlite3_open sqlite3_api->open
|
||||
#define sqlite3_open16 sqlite3_api->open16
|
||||
#define sqlite3_prepare sqlite3_api->prepare
|
||||
#define sqlite3_prepare16 sqlite3_api->prepare16
|
||||
#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
|
||||
#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
|
||||
#define sqlite3_profile sqlite3_api->profile
|
||||
#define sqlite3_progress_handler sqlite3_api->progress_handler
|
||||
#define sqlite3_realloc sqlite3_api->realloc
|
||||
#define sqlite3_reset sqlite3_api->reset
|
||||
#define sqlite3_result_blob sqlite3_api->result_blob
|
||||
#define sqlite3_result_double sqlite3_api->result_double
|
||||
#define sqlite3_result_error sqlite3_api->result_error
|
||||
#define sqlite3_result_error16 sqlite3_api->result_error16
|
||||
#define sqlite3_result_int sqlite3_api->result_int
|
||||
#define sqlite3_result_int64 sqlite3_api->result_int64
|
||||
#define sqlite3_result_null sqlite3_api->result_null
|
||||
#define sqlite3_result_text sqlite3_api->result_text
|
||||
#define sqlite3_result_text16 sqlite3_api->result_text16
|
||||
#define sqlite3_result_text16be sqlite3_api->result_text16be
|
||||
#define sqlite3_result_text16le sqlite3_api->result_text16le
|
||||
#define sqlite3_result_value sqlite3_api->result_value
|
||||
#define sqlite3_rollback_hook sqlite3_api->rollback_hook
|
||||
#define sqlite3_set_authorizer sqlite3_api->set_authorizer
|
||||
#define sqlite3_set_auxdata sqlite3_api->set_auxdata
|
||||
#define sqlite3_snprintf sqlite3_api->snprintf
|
||||
#define sqlite3_step sqlite3_api->step
|
||||
#define sqlite3_table_column_metadata sqlite3_api->table_column_metadata
|
||||
#define sqlite3_thread_cleanup sqlite3_api->thread_cleanup
|
||||
#define sqlite3_total_changes sqlite3_api->total_changes
|
||||
#define sqlite3_trace sqlite3_api->trace
|
||||
#ifndef SQLITE_OMIT_DEPRECATED
|
||||
#define sqlite3_transfer_bindings sqlite3_api->transfer_bindings
|
||||
#endif
|
||||
#define sqlite3_update_hook sqlite3_api->update_hook
|
||||
#define sqlite3_user_data sqlite3_api->user_data
|
||||
#define sqlite3_value_blob sqlite3_api->value_blob
|
||||
#define sqlite3_value_bytes sqlite3_api->value_bytes
|
||||
#define sqlite3_value_bytes16 sqlite3_api->value_bytes16
|
||||
#define sqlite3_value_double sqlite3_api->value_double
|
||||
#define sqlite3_value_int sqlite3_api->value_int
|
||||
#define sqlite3_value_int64 sqlite3_api->value_int64
|
||||
#define sqlite3_value_numeric_type sqlite3_api->value_numeric_type
|
||||
#define sqlite3_value_text sqlite3_api->value_text
|
||||
#define sqlite3_value_text16 sqlite3_api->value_text16
|
||||
#define sqlite3_value_text16be sqlite3_api->value_text16be
|
||||
#define sqlite3_value_text16le sqlite3_api->value_text16le
|
||||
#define sqlite3_value_type sqlite3_api->value_type
|
||||
#define sqlite3_vmprintf sqlite3_api->vmprintf
|
||||
#define sqlite3_overload_function sqlite3_api->overload_function
|
||||
#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
|
||||
#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
|
||||
#define sqlite3_clear_bindings sqlite3_api->clear_bindings
|
||||
#define sqlite3_bind_zeroblob sqlite3_api->bind_zeroblob
|
||||
#define sqlite3_blob_bytes sqlite3_api->blob_bytes
|
||||
#define sqlite3_blob_close sqlite3_api->blob_close
|
||||
#define sqlite3_blob_open sqlite3_api->blob_open
|
||||
#define sqlite3_blob_read sqlite3_api->blob_read
|
||||
#define sqlite3_blob_write sqlite3_api->blob_write
|
||||
#define sqlite3_create_collation_v2 sqlite3_api->create_collation_v2
|
||||
#define sqlite3_file_control sqlite3_api->file_control
|
||||
#define sqlite3_memory_highwater sqlite3_api->memory_highwater
|
||||
#define sqlite3_memory_used sqlite3_api->memory_used
|
||||
#define sqlite3_mutex_alloc sqlite3_api->mutex_alloc
|
||||
#define sqlite3_mutex_enter sqlite3_api->mutex_enter
|
||||
#define sqlite3_mutex_free sqlite3_api->mutex_free
|
||||
#define sqlite3_mutex_leave sqlite3_api->mutex_leave
|
||||
#define sqlite3_mutex_try sqlite3_api->mutex_try
|
||||
#define sqlite3_open_v2 sqlite3_api->open_v2
|
||||
#define sqlite3_release_memory sqlite3_api->release_memory
|
||||
#define sqlite3_result_error_nomem sqlite3_api->result_error_nomem
|
||||
#define sqlite3_result_error_toobig sqlite3_api->result_error_toobig
|
||||
#define sqlite3_sleep sqlite3_api->sleep
|
||||
#define sqlite3_soft_heap_limit sqlite3_api->soft_heap_limit
|
||||
#define sqlite3_vfs_find sqlite3_api->vfs_find
|
||||
#define sqlite3_vfs_register sqlite3_api->vfs_register
|
||||
#define sqlite3_vfs_unregister sqlite3_api->vfs_unregister
|
||||
#define sqlite3_threadsafe sqlite3_api->xthreadsafe
|
||||
#define sqlite3_result_zeroblob sqlite3_api->result_zeroblob
|
||||
#define sqlite3_result_error_code sqlite3_api->result_error_code
|
||||
#define sqlite3_test_control sqlite3_api->test_control
|
||||
#define sqlite3_randomness sqlite3_api->randomness
|
||||
#define sqlite3_context_db_handle sqlite3_api->context_db_handle
|
||||
#define sqlite3_extended_result_codes sqlite3_api->extended_result_codes
|
||||
#define sqlite3_limit sqlite3_api->limit
|
||||
#define sqlite3_next_stmt sqlite3_api->next_stmt
|
||||
#define sqlite3_sql sqlite3_api->sql
|
||||
#define sqlite3_status sqlite3_api->status
|
||||
#define sqlite3_backup_finish sqlite3_api->backup_finish
|
||||
#define sqlite3_backup_init sqlite3_api->backup_init
|
||||
#define sqlite3_backup_pagecount sqlite3_api->backup_pagecount
|
||||
#define sqlite3_backup_remaining sqlite3_api->backup_remaining
|
||||
#define sqlite3_backup_step sqlite3_api->backup_step
|
||||
#define sqlite3_compileoption_get sqlite3_api->compileoption_get
|
||||
#define sqlite3_compileoption_used sqlite3_api->compileoption_used
|
||||
#define sqlite3_create_function_v2 sqlite3_api->create_function_v2
|
||||
#define sqlite3_db_config sqlite3_api->db_config
|
||||
#define sqlite3_db_mutex sqlite3_api->db_mutex
|
||||
#define sqlite3_db_status sqlite3_api->db_status
|
||||
#define sqlite3_extended_errcode sqlite3_api->extended_errcode
|
||||
#define sqlite3_log sqlite3_api->log
|
||||
#define sqlite3_soft_heap_limit64 sqlite3_api->soft_heap_limit64
|
||||
#define sqlite3_sourceid sqlite3_api->sourceid
|
||||
#define sqlite3_stmt_status sqlite3_api->stmt_status
|
||||
#define sqlite3_strnicmp sqlite3_api->strnicmp
|
||||
#define sqlite3_unlock_notify sqlite3_api->unlock_notify
|
||||
#define sqlite3_wal_autocheckpoint sqlite3_api->wal_autocheckpoint
|
||||
#define sqlite3_wal_checkpoint sqlite3_api->wal_checkpoint
|
||||
#define sqlite3_wal_hook sqlite3_api->wal_hook
|
||||
#define sqlite3_blob_reopen sqlite3_api->blob_reopen
|
||||
#define sqlite3_vtab_config sqlite3_api->vtab_config
|
||||
#define sqlite3_vtab_on_conflict sqlite3_api->vtab_on_conflict
|
||||
#endif /* SQLITE_CORE */
|
||||
|
||||
#define SQLITE_EXTENSION_INIT1 const sqlite3_api_routines *sqlite3_api = 0;
|
||||
#define SQLITE_EXTENSION_INIT2(v) sqlite3_api = v;
|
||||
|
||||
#endif /* _SQLITE3EXT_H_ */
|
||||
82
src/cpp/database/win/dbutility.h
Normal file
82
src/cpp/database/win/dbutility.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#ifndef __TMYODBC_UTILITY_H__
|
||||
#define __TMYODBC_UTILITY_H__
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <myconf.h>
|
||||
#endif
|
||||
|
||||
|
||||
/* STANDARD C HEADERS */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
/* ODBC HEADERS */
|
||||
#include <sqlext.h>
|
||||
|
||||
#define MAX_NAME_LEN 95
|
||||
#define MAX_COLUMNS 255
|
||||
#define MAX_ROW_DATA_LEN 255
|
||||
|
||||
|
||||
/* PROTOTYPE */
|
||||
void myerror(SQLRETURN rc,SQLSMALLINT htype, SQLHANDLE handle);
|
||||
|
||||
/* UTILITY MACROS */
|
||||
#define myenv(henv,r) \
|
||||
if ( ((r) != SQL_SUCCESS) ) \
|
||||
myerror(r, 1,henv); \
|
||||
assert( ((r) == SQL_SUCCESS) || ((r) == SQL_SUCCESS_WITH_INFO) )
|
||||
|
||||
#define myenv_err(henv,r,rc) \
|
||||
if ( rc == SQL_ERROR || rc == SQL_SUCCESS_WITH_INFO ) \
|
||||
myerror(rc, 1, henv); \
|
||||
assert( r )
|
||||
|
||||
#define mycon(hdbc,r) \
|
||||
if ( ((r) != SQL_SUCCESS) ) \
|
||||
myerror(r, 2, hdbc); \
|
||||
assert( ((r) == SQL_SUCCESS) || ((r) == SQL_SUCCESS_WITH_INFO) )
|
||||
|
||||
#define mycon_err(hdbc,r,rc) \
|
||||
if ( rc == SQL_ERROR || rc == SQL_SUCCESS_WITH_INFO ) \
|
||||
myerror(rc, 2, hdbc); \
|
||||
assert( r )
|
||||
|
||||
#define mystmt(hstmt,r) \
|
||||
if ( ((r) != SQL_SUCCESS) ) \
|
||||
myerror(r, 3, hstmt); \
|
||||
assert( ((r) == SQL_SUCCESS) || ((r) == SQL_SUCCESS_WITH_INFO) )
|
||||
|
||||
#define mystmt_err(hstmt,r,rc) \
|
||||
if ( rc == SQL_ERROR || rc == SQL_SUCCESS_WITH_INFO ) \
|
||||
myerror(rc, 3, hstmt); \
|
||||
assert( r )
|
||||
|
||||
/********************************************************
|
||||
* MyODBC 3.51 error handler *
|
||||
*********************************************************/
|
||||
void myerror(SQLRETURN rc, SQLSMALLINT htype, SQLHANDLE handle)
|
||||
{
|
||||
SQLRETURN lrc;
|
||||
|
||||
if( rc == SQL_ERROR || rc == SQL_SUCCESS_WITH_INFO )
|
||||
{
|
||||
SQLCHAR szSqlState[6],szErrorMsg[SQL_MAX_MESSAGE_LENGTH];
|
||||
SQLINTEGER pfNativeError;
|
||||
SQLSMALLINT pcbErrorMsg;
|
||||
|
||||
lrc = SQLGetDiagRec(htype, handle,1,
|
||||
(SQLCHAR *)&szSqlState,
|
||||
(SQLINTEGER *)&pfNativeError,
|
||||
(SQLCHAR *)&szErrorMsg,
|
||||
SQL_MAX_MESSAGE_LENGTH-1,
|
||||
(SQLSMALLINT *)&pcbErrorMsg);
|
||||
if(lrc == SQL_SUCCESS || lrc == SQL_SUCCESS_WITH_INFO)
|
||||
printf("\n [%s][%d:%s]\n",szSqlState,pfNativeError,szErrorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* __TMYODBC_UTILITY_H__ */
|
||||
|
||||
246
src/cpp/database/win/windatabase.cpp
Normal file
246
src/cpp/database/win/windatabase.cpp
Normal file
@@ -0,0 +1,246 @@
|
||||
#include "windatabase.h"
|
||||
#include "dbutility.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
Database* Database::newMysqlDatabase(const char* host,const char* user,const char* pass)
|
||||
{
|
||||
return(new WinDatabase(host,user,pass));
|
||||
}
|
||||
|
||||
WinDatabase::WinDatabase(const char* host,const char* user,const char* pass) : Database(host,user,pass)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
WinDatabase::~WinDatabase()
|
||||
{
|
||||
disconnect();
|
||||
}
|
||||
|
||||
|
||||
void WinDatabase::connect()
|
||||
{
|
||||
SQLRETURN rc;
|
||||
|
||||
rc = SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&henv);
|
||||
myenv(henv, rc);
|
||||
|
||||
rc = SQLSetEnvAttr(henv,SQL_ATTR_ODBC_VERSION,(SQLPOINTER)SQL_OV_ODBC3,0);
|
||||
myenv(henv, rc);
|
||||
|
||||
rc = SQLAllocHandle(SQL_HANDLE_DBC,henv, &hdbc);
|
||||
myenv(henv, rc);
|
||||
|
||||
rc = SQLConnect(hdbc, (unsigned char*)(char*) mHost.c_str(), SQL_NTS, (unsigned char*)(char*) mUser.c_str(), SQL_NTS, (unsigned char*)(char*) mDBPass.c_str(), SQL_NTS);
|
||||
mycon(hdbc, rc);
|
||||
|
||||
rc = SQLSetConnectAttr(hdbc,SQL_ATTR_AUTOCOMMIT,(SQLPOINTER)SQL_AUTOCOMMIT_ON,0);
|
||||
mycon(hdbc,rc);
|
||||
|
||||
rc = SQLAllocHandle(SQL_HANDLE_STMT,hdbc,&hstmt);
|
||||
mycon(hdbc,rc);
|
||||
|
||||
//rc = SQLGetInfo(hdbc,SQL_DBMS_NAME,&server_name,40,NULL);
|
||||
//mycon(hdbc, rc);
|
||||
|
||||
//theUI->statusMsg("Connection Established to DB");
|
||||
}
|
||||
|
||||
void WinDatabase::disconnect()
|
||||
{
|
||||
SQLRETURN rc;
|
||||
|
||||
rc = SQLFreeStmt(hstmt, SQL_DROP);
|
||||
mystmt(hstmt,rc);
|
||||
|
||||
rc = SQLDisconnect(hdbc);
|
||||
mycon(hdbc, rc);
|
||||
|
||||
rc = SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
|
||||
mycon(hdbc, rc);
|
||||
|
||||
rc = SQLFreeHandle(SQL_HANDLE_ENV, henv);
|
||||
myenv(henv, rc);
|
||||
}
|
||||
|
||||
int WinDatabase::getNumRowsAffected()
|
||||
{
|
||||
// theUI->statusMsg("getNumRowsAffected()");
|
||||
SQLINTEGER ret;
|
||||
SQLRowCount(hstmt,&ret);
|
||||
return(ret);
|
||||
}
|
||||
|
||||
// returns true if the query went ok
|
||||
bool WinDatabase::executeSQL(const char* sql, bool fail_okay)
|
||||
{
|
||||
SQLRETURN rc = SQLExecDirect(hstmt,(unsigned char*) sql,SQL_NTS);
|
||||
if(rc==SQL_ERROR)
|
||||
{
|
||||
//theUI->errorMsg("Trying to recover from DB error");
|
||||
rc = SQLExecDirect(hstmt,(unsigned char*) sql,SQL_NTS);
|
||||
if(rc==SQL_ERROR)
|
||||
{
|
||||
SQLCHAR SqlState[6], /*SQLStmt[100],*/ Msg[SQL_MAX_MESSAGE_LENGTH];
|
||||
SQLINTEGER NativeError;
|
||||
SQLSMALLINT i, MsgLen;
|
||||
SQLRETURN /*rc1,*/ rc2;
|
||||
|
||||
i = 1;
|
||||
while ((rc2 = SQLGetDiagRec(SQL_HANDLE_STMT, hstmt, i, SqlState, &NativeError, Msg, sizeof(Msg), &MsgLen)) != SQL_NO_DATA)
|
||||
{
|
||||
//theUI->errorMsg("DB ERROR: %s,%d,%s",SqlState,NativeError,Msg);
|
||||
i++;
|
||||
}
|
||||
|
||||
return(false);
|
||||
}
|
||||
}
|
||||
|
||||
mystmt(hstmt,rc);
|
||||
|
||||
// commit the transaction
|
||||
rc = SQLEndTran(SQL_HANDLE_DBC, hdbc, SQL_COMMIT);
|
||||
mycon(hdbc,rc);
|
||||
|
||||
return(true);
|
||||
|
||||
}
|
||||
|
||||
bool WinDatabase::startIterRows()
|
||||
{
|
||||
SQLUINTEGER pcColDef;
|
||||
SQLCHAR szColName[MAX_NAME_LEN];
|
||||
SQLSMALLINT pfSqlType, pcbScale, pfNullable;
|
||||
SQLSMALLINT numCol;
|
||||
|
||||
/* get total number of columns from the resultset */
|
||||
SQLRETURN rc = SQLNumResultCols(hstmt,&numCol);
|
||||
mystmt(hstmt,rc);
|
||||
mNumCol=(int)numCol;
|
||||
|
||||
if(mNumCol)
|
||||
{
|
||||
mColNameTable.resize(mNumCol);
|
||||
|
||||
// fill out the column name table
|
||||
for(int n = 1; n <= mNumCol; n++)
|
||||
{
|
||||
rc = SQLDescribeCol(hstmt,n,szColName, MAX_NAME_LEN, NULL, &pfSqlType,&pcColDef,&pcbScale,&pfNullable);
|
||||
mystmt(hstmt,rc);
|
||||
|
||||
mColNameTable[n-1]= (char*)szColName;
|
||||
}
|
||||
return(true);
|
||||
}
|
||||
return(false);
|
||||
}
|
||||
|
||||
// call this after you executeSQL
|
||||
// will return false if there are no more rows
|
||||
bool WinDatabase::getNextRow()
|
||||
{
|
||||
SQLRETURN rc = SQLFetch(hstmt);
|
||||
return((rc==SQL_SUCCESS) || (rc==SQL_SUCCESS_WITH_INFO));
|
||||
}
|
||||
|
||||
|
||||
|
||||
char* WinDatabase::getStr(int colIndex,string& retStr)
|
||||
{
|
||||
colIndex++;
|
||||
retStr="";
|
||||
char buf[1000];
|
||||
// SQLINTEGER len;
|
||||
buf[0]=0;
|
||||
|
||||
while(SQLGetData(hstmt, colIndex, SQL_C_CHAR, &buf, 1000,NULL)!= SQL_NO_DATA)
|
||||
{
|
||||
retStr += buf;
|
||||
// theUI->statusMsg("Win: %s",buf);
|
||||
}
|
||||
|
||||
//SQLRETURN rc = SQLGetData(hstmt,colIndex,SQL_C_CHAR,&buf,30000,&len);
|
||||
//mystmt(hstmt,rc);
|
||||
//*retStr=buf;
|
||||
|
||||
//theUI->statusMsg("Win: %s",buf);
|
||||
|
||||
return((char*)retStr.c_str());
|
||||
}
|
||||
|
||||
int32 WinDatabase::getInt(int colIndex)
|
||||
{
|
||||
colIndex++;
|
||||
int ret=0;
|
||||
SQLRETURN rc = SQLGetData(hstmt,colIndex,SQL_INTEGER,&ret,sizeof(int),NULL);
|
||||
mystmt(hstmt,rc);
|
||||
return(ret);
|
||||
}
|
||||
|
||||
float WinDatabase::getFloat(int colIndex)
|
||||
{
|
||||
colIndex++;
|
||||
float ret=0;
|
||||
SQLRETURN rc = SQLGetData(hstmt,colIndex,SQL_C_FLOAT,&ret,sizeof(float),NULL);
|
||||
mystmt(hstmt,rc);
|
||||
|
||||
return(ret);
|
||||
}
|
||||
|
||||
bool WinDatabase::getBool(int colIndex)
|
||||
{
|
||||
colIndex++;
|
||||
char buf[1];
|
||||
buf[0]=0;
|
||||
SQLRETURN rc = SQLGetData(hstmt,colIndex,SQL_C_CHAR,&buf,1,NULL);
|
||||
mystmt(hstmt,rc);
|
||||
|
||||
return(buf[0] != 0);
|
||||
}
|
||||
|
||||
|
||||
void WinDatabase::endIterRows()
|
||||
{
|
||||
// free the statement row bind resources
|
||||
SQLRETURN rc = SQLFreeStmt(hstmt, SQL_UNBIND);
|
||||
mystmt(hstmt,rc);
|
||||
|
||||
// free the statement cursor
|
||||
rc = SQLFreeStmt(hstmt, SQL_CLOSE);
|
||||
mystmt(hstmt,rc);
|
||||
}
|
||||
|
||||
// TODO
|
||||
void WinDatabase::escape(const unsigned char* start,int size,std::string& retStr)
|
||||
{
|
||||
retStr=(char*)start;
|
||||
}
|
||||
|
||||
// TODO
|
||||
int WinDatabase::getLastInsertID()
|
||||
{
|
||||
return(0);
|
||||
}
|
||||
|
||||
uint64 WinDatabase::getBigInt(int colIndex)
|
||||
{
|
||||
colIndex++;
|
||||
uint64 ret=0;
|
||||
SQLRETURN rc = SQLGetData(hstmt,colIndex,SQL_INTEGER,&ret,sizeof(uint64),NULL);
|
||||
mystmt(hstmt,rc);
|
||||
return(ret);
|
||||
}
|
||||
// TODO:
|
||||
int WinDatabase::getBinary(int colIndex,unsigned char* buf,int maxSize)
|
||||
{
|
||||
return(0);
|
||||
}
|
||||
|
||||
std::vector<unsigned char> WinDatabase::getBinary(int colIndex)
|
||||
{
|
||||
// TODO:
|
||||
std::vector<unsigned char> vucResult;
|
||||
return vucResult;
|
||||
}
|
||||
61
src/cpp/database/win/windatabase.h
Normal file
61
src/cpp/database/win/windatabase.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#ifndef __WINDATABASE__
|
||||
#define __WINDATABASE__
|
||||
|
||||
#include "../database.h"
|
||||
|
||||
|
||||
#ifdef WIN32
|
||||
#define _WINSOCKAPI_ // prevent inclusion of winsock.h in windows.h
|
||||
#include <windows.h>
|
||||
#include "sql.h"
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
this maintains the connection to the database
|
||||
*/
|
||||
class WinDatabase : public Database
|
||||
{
|
||||
SQLHENV henv;
|
||||
SQLHDBC hdbc;
|
||||
SQLHSTMT hstmt;
|
||||
|
||||
public:
|
||||
WinDatabase(const char* host,const char* user,const char* pass);
|
||||
virtual ~WinDatabase();
|
||||
|
||||
void connect();
|
||||
void disconnect();
|
||||
|
||||
//char* getPass(){ return((char*)mDBPass.c_str()); }
|
||||
|
||||
// returns true if the query went ok
|
||||
bool executeSQL(const char* sql, bool fail_okay=false);
|
||||
|
||||
int getNumRowsAffected();
|
||||
int getLastInsertID();
|
||||
|
||||
// returns false if there are no results
|
||||
bool startIterRows();
|
||||
void endIterRows();
|
||||
|
||||
// call this after you executeSQL
|
||||
// will return false if there are no more rows
|
||||
bool getNextRow();
|
||||
|
||||
// get Data from the current row
|
||||
char* getStr(int colIndex,std::string& retStr);
|
||||
int32 getInt(int colIndex);
|
||||
float getFloat(int colIndex);
|
||||
bool getBool(int colIndex);
|
||||
uint64 getBigInt(int colIndex);
|
||||
int getBinary(int colIndex,unsigned char* buf,int maxSize);
|
||||
std::vector<unsigned char> getBinary(int colIndex);
|
||||
bool getNull(int colIndex){ return(true); }
|
||||
|
||||
void escape(const unsigned char* start,int size,std::string& retStr);
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
BIN
src/cpp/json/LICENSE
Normal file
BIN
src/cpp/json/LICENSE
Normal file
Binary file not shown.
19
src/cpp/json/autolink.h
Normal file
19
src/cpp/json/autolink.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef JSON_AUTOLINK_H_INCLUDED
|
||||
# define JSON_AUTOLINK_H_INCLUDED
|
||||
|
||||
# include "config.h"
|
||||
|
||||
# ifdef JSON_IN_CPPTL
|
||||
# include <cpptl/cpptl_autolink.h>
|
||||
# endif
|
||||
|
||||
# if !defined(JSON_NO_AUTOLINK) && !defined(JSON_DLL_BUILD) && !defined(JSON_IN_CPPTL)
|
||||
# define CPPTL_AUTOLINK_NAME "json"
|
||||
# undef CPPTL_AUTOLINK_DLL
|
||||
# ifdef JSON_DLL
|
||||
# define CPPTL_AUTOLINK_DLL
|
||||
# endif
|
||||
# include "autolink.h"
|
||||
# endif
|
||||
|
||||
#endif // JSON_AUTOLINK_H_INCLUDED
|
||||
43
src/cpp/json/config.h
Normal file
43
src/cpp/json/config.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef JSON_CONFIG_H_INCLUDED
|
||||
# define JSON_CONFIG_H_INCLUDED
|
||||
|
||||
/// If defined, indicates that json library is embedded in CppTL library.
|
||||
//# define JSON_IN_CPPTL 1
|
||||
|
||||
/// If defined, indicates that json may leverage CppTL library
|
||||
//# define JSON_USE_CPPTL 1
|
||||
/// If defined, indicates that cpptl vector based map should be used instead of std::map
|
||||
/// as Value container.
|
||||
//# define JSON_USE_CPPTL_SMALLMAP 1
|
||||
/// If defined, indicates that Json specific container should be used
|
||||
/// (hash table & simple deque container with customizable allocator).
|
||||
/// THIS FEATURE IS STILL EXPERIMENTAL!
|
||||
//# define JSON_VALUE_USE_INTERNAL_MAP 1
|
||||
/// Force usage of standard new/malloc based allocator instead of memory pool based allocator.
|
||||
/// The memory pools allocator used optimization (initializing Value and ValueInternalLink
|
||||
/// as if it was a POD) that may cause some validation tool to report errors.
|
||||
/// Only has effects if JSON_VALUE_USE_INTERNAL_MAP is defined.
|
||||
//# define JSON_USE_SIMPLE_INTERNAL_ALLOCATOR 1
|
||||
|
||||
/// If defined, indicates that Json use exception to report invalid type manipulation
|
||||
/// instead of C assert macro.
|
||||
# define JSON_USE_EXCEPTION 1
|
||||
|
||||
# ifdef JSON_IN_CPPTL
|
||||
# include <cpptl/config.h>
|
||||
# ifndef JSON_USE_CPPTL
|
||||
# define JSON_USE_CPPTL 1
|
||||
# endif
|
||||
# endif
|
||||
|
||||
# ifdef JSON_IN_CPPTL
|
||||
# define JSON_API CPPTL_API
|
||||
# elif defined(JSON_DLL_BUILD)
|
||||
# define JSON_API __declspec(dllexport)
|
||||
# elif defined(JSON_DLL)
|
||||
# define JSON_API __declspec(dllimport)
|
||||
# else
|
||||
# define JSON_API
|
||||
# endif
|
||||
|
||||
#endif // JSON_CONFIG_H_INCLUDED
|
||||
42
src/cpp/json/features.h
Normal file
42
src/cpp/json/features.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef CPPTL_JSON_FEATURES_H_INCLUDED
|
||||
# define CPPTL_JSON_FEATURES_H_INCLUDED
|
||||
|
||||
# include "forwards.h"
|
||||
|
||||
namespace Json {
|
||||
|
||||
/** \brief Configuration passed to reader and writer.
|
||||
* This configuration object can be used to force the Reader or Writer
|
||||
* to behave in a standard conforming way.
|
||||
*/
|
||||
class JSON_API Features
|
||||
{
|
||||
public:
|
||||
/** \brief A configuration that allows all features and assumes all strings are UTF-8.
|
||||
* - C & C++ comments are allowed
|
||||
* - Root object can be any JSON value
|
||||
* - Assumes Value strings are encoded in UTF-8
|
||||
*/
|
||||
static Features all();
|
||||
|
||||
/** \brief A configuration that is strictly compatible with the JSON specification.
|
||||
* - Comments are forbidden.
|
||||
* - Root object must be either an array or an object value.
|
||||
* - Assumes Value strings are encoded in UTF-8
|
||||
*/
|
||||
static Features strictMode();
|
||||
|
||||
/** \brief Initialize the configuration like JsonConfig::allFeatures;
|
||||
*/
|
||||
Features();
|
||||
|
||||
/// \c true if comments are allowed. Default: \c true.
|
||||
bool allowComments_;
|
||||
|
||||
/// \c true if root must be either an array or an object value. Default: \c false.
|
||||
bool strictRoot_;
|
||||
};
|
||||
|
||||
} // namespace Json
|
||||
|
||||
#endif // CPPTL_JSON_FEATURES_H_INCLUDED
|
||||
39
src/cpp/json/forwards.h
Normal file
39
src/cpp/json/forwards.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#ifndef JSON_FORWARDS_H_INCLUDED
|
||||
# define JSON_FORWARDS_H_INCLUDED
|
||||
|
||||
# include "config.h"
|
||||
|
||||
namespace Json {
|
||||
|
||||
// writer.h
|
||||
class FastWriter;
|
||||
class StyledWriter;
|
||||
|
||||
// reader.h
|
||||
class Reader;
|
||||
|
||||
// features.h
|
||||
class Features;
|
||||
|
||||
// value.h
|
||||
typedef int Int;
|
||||
typedef unsigned int UInt;
|
||||
class StaticString;
|
||||
class Path;
|
||||
class PathArgument;
|
||||
class Value;
|
||||
class ValueIteratorBase;
|
||||
class ValueIterator;
|
||||
class ValueConstIterator;
|
||||
#ifdef JSON_VALUE_USE_INTERNAL_MAP
|
||||
class ValueAllocator;
|
||||
class ValueMapAllocator;
|
||||
class ValueInternalLink;
|
||||
class ValueInternalArray;
|
||||
class ValueInternalMap;
|
||||
#endif // #ifdef JSON_VALUE_USE_INTERNAL_MAP
|
||||
|
||||
} // namespace Json
|
||||
|
||||
|
||||
#endif // JSON_FORWARDS_H_INCLUDED
|
||||
10
src/cpp/json/json.h
Normal file
10
src/cpp/json/json.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef JSON_JSON_H_INCLUDED
|
||||
# define JSON_JSON_H_INCLUDED
|
||||
|
||||
# include "autolink.h"
|
||||
# include "value.h"
|
||||
# include "reader.h"
|
||||
# include "writer.h"
|
||||
# include "features.h"
|
||||
|
||||
#endif // JSON_JSON_H_INCLUDED
|
||||
125
src/cpp/json/json_batchallocator.h
Normal file
125
src/cpp/json/json_batchallocator.h
Normal file
@@ -0,0 +1,125 @@
|
||||
#ifndef JSONCPP_BATCHALLOCATOR_H_INCLUDED
|
||||
# define JSONCPP_BATCHALLOCATOR_H_INCLUDED
|
||||
|
||||
# include <stdlib.h>
|
||||
# include <assert.h>
|
||||
|
||||
# ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION
|
||||
|
||||
namespace Json {
|
||||
|
||||
/* Fast memory allocator.
|
||||
*
|
||||
* This memory allocator allocates memory for a batch of object (specified by
|
||||
* the page size, the number of object in each page).
|
||||
*
|
||||
* It does not allow the destruction of a single object. All the allocated objects
|
||||
* can be destroyed at once. The memory can be either released or reused for future
|
||||
* allocation.
|
||||
*
|
||||
* The in-place new operator must be used to construct the object using the pointer
|
||||
* returned by allocate.
|
||||
*/
|
||||
template<typename AllocatedType
|
||||
,const unsigned int objectPerAllocation>
|
||||
class BatchAllocator
|
||||
{
|
||||
public:
|
||||
typedef AllocatedType Type;
|
||||
|
||||
BatchAllocator( unsigned int objectsPerPage = 255 )
|
||||
: freeHead_( 0 )
|
||||
, objectsPerPage_( objectsPerPage )
|
||||
{
|
||||
// printf( "Size: %d => %s\n", sizeof(AllocatedType), typeid(AllocatedType).name() );
|
||||
assert( sizeof(AllocatedType) * objectPerAllocation >= sizeof(AllocatedType *) ); // We must be able to store a slist in the object free space.
|
||||
assert( objectsPerPage >= 16 );
|
||||
batches_ = allocateBatch( 0 ); // allocated a dummy page
|
||||
currentBatch_ = batches_;
|
||||
}
|
||||
|
||||
~BatchAllocator()
|
||||
{
|
||||
for ( BatchInfo *batch = batches_; batch; )
|
||||
{
|
||||
BatchInfo *nextBatch = batch->next_;
|
||||
free( batch );
|
||||
batch = nextBatch;
|
||||
}
|
||||
}
|
||||
|
||||
/// allocate space for an array of objectPerAllocation object.
|
||||
/// @warning it is the responsability of the caller to call objects constructors.
|
||||
AllocatedType *allocate()
|
||||
{
|
||||
if ( freeHead_ ) // returns node from free list.
|
||||
{
|
||||
AllocatedType *object = freeHead_;
|
||||
freeHead_ = *(AllocatedType **)object;
|
||||
return object;
|
||||
}
|
||||
if ( currentBatch_->used_ == currentBatch_->end_ )
|
||||
{
|
||||
currentBatch_ = currentBatch_->next_;
|
||||
while ( currentBatch_ && currentBatch_->used_ == currentBatch_->end_ )
|
||||
currentBatch_ = currentBatch_->next_;
|
||||
|
||||
if ( !currentBatch_ ) // no free batch found, allocate a new one
|
||||
{
|
||||
currentBatch_ = allocateBatch( objectsPerPage_ );
|
||||
currentBatch_->next_ = batches_; // insert at the head of the list
|
||||
batches_ = currentBatch_;
|
||||
}
|
||||
}
|
||||
AllocatedType *allocated = currentBatch_->used_;
|
||||
currentBatch_->used_ += objectPerAllocation;
|
||||
return allocated;
|
||||
}
|
||||
|
||||
/// Release the object.
|
||||
/// @warning it is the responsability of the caller to actually destruct the object.
|
||||
void release( AllocatedType *object )
|
||||
{
|
||||
assert( object != 0 );
|
||||
*(AllocatedType **)object = freeHead_;
|
||||
freeHead_ = object;
|
||||
}
|
||||
|
||||
private:
|
||||
struct BatchInfo
|
||||
{
|
||||
BatchInfo *next_;
|
||||
AllocatedType *used_;
|
||||
AllocatedType *end_;
|
||||
AllocatedType buffer_[objectPerAllocation];
|
||||
};
|
||||
|
||||
// disabled copy constructor and assignement operator.
|
||||
BatchAllocator( const BatchAllocator & );
|
||||
void operator =( const BatchAllocator &);
|
||||
|
||||
static BatchInfo *allocateBatch( unsigned int objectsPerPage )
|
||||
{
|
||||
const unsigned int mallocSize = sizeof(BatchInfo) - sizeof(AllocatedType)* objectPerAllocation
|
||||
+ sizeof(AllocatedType) * objectPerAllocation * objectsPerPage;
|
||||
BatchInfo *batch = static_cast<BatchInfo*>( malloc( mallocSize ) );
|
||||
batch->next_ = 0;
|
||||
batch->used_ = batch->buffer_;
|
||||
batch->end_ = batch->buffer_ + objectsPerPage;
|
||||
return batch;
|
||||
}
|
||||
|
||||
BatchInfo *batches_;
|
||||
BatchInfo *currentBatch_;
|
||||
/// Head of a single linked list within the allocated space of freeed object
|
||||
AllocatedType *freeHead_;
|
||||
unsigned int objectsPerPage_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Json
|
||||
|
||||
# endif // ifndef JSONCPP_DOC_INCLUDE_IMPLEMENTATION
|
||||
|
||||
#endif // JSONCPP_BATCHALLOCATOR_H_INCLUDED
|
||||
|
||||
448
src/cpp/json/json_internalarray.inl
Normal file
448
src/cpp/json/json_internalarray.inl
Normal file
@@ -0,0 +1,448 @@
|
||||
// included by json_value.cpp
|
||||
// everything is within Json namespace
|
||||
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// class ValueInternalArray
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
|
||||
ValueArrayAllocator::~ValueArrayAllocator()
|
||||
{
|
||||
}
|
||||
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// class DefaultValueArrayAllocator
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
#ifdef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR
|
||||
class DefaultValueArrayAllocator : public ValueArrayAllocator
|
||||
{
|
||||
public: // overridden from ValueArrayAllocator
|
||||
virtual ~DefaultValueArrayAllocator()
|
||||
{
|
||||
}
|
||||
|
||||
virtual ValueInternalArray *newArray()
|
||||
{
|
||||
return new ValueInternalArray();
|
||||
}
|
||||
|
||||
virtual ValueInternalArray *newArrayCopy( const ValueInternalArray &other )
|
||||
{
|
||||
return new ValueInternalArray( other );
|
||||
}
|
||||
|
||||
virtual void destructArray( ValueInternalArray *array )
|
||||
{
|
||||
delete array;
|
||||
}
|
||||
|
||||
virtual void reallocateArrayPageIndex( Value **&indexes,
|
||||
ValueInternalArray::PageIndex &indexCount,
|
||||
ValueInternalArray::PageIndex minNewIndexCount )
|
||||
{
|
||||
ValueInternalArray::PageIndex newIndexCount = (indexCount*3)/2 + 1;
|
||||
if ( minNewIndexCount > newIndexCount )
|
||||
newIndexCount = minNewIndexCount;
|
||||
void *newIndexes = realloc( indexes, sizeof(Value*) * newIndexCount );
|
||||
if ( !newIndexes )
|
||||
throw std::bad_alloc();
|
||||
indexCount = newIndexCount;
|
||||
indexes = static_cast<Value **>( newIndexes );
|
||||
}
|
||||
virtual void releaseArrayPageIndex( Value **indexes,
|
||||
ValueInternalArray::PageIndex indexCount )
|
||||
{
|
||||
if ( indexes )
|
||||
free( indexes );
|
||||
}
|
||||
|
||||
virtual Value *allocateArrayPage()
|
||||
{
|
||||
return static_cast<Value *>( malloc( sizeof(Value) * ValueInternalArray::itemsPerPage ) );
|
||||
}
|
||||
|
||||
virtual void releaseArrayPage( Value *value )
|
||||
{
|
||||
if ( value )
|
||||
free( value );
|
||||
}
|
||||
};
|
||||
|
||||
#else // #ifdef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR
|
||||
/// @todo make this thread-safe (lock when accessign batch allocator)
|
||||
class DefaultValueArrayAllocator : public ValueArrayAllocator
|
||||
{
|
||||
public: // overridden from ValueArrayAllocator
|
||||
virtual ~DefaultValueArrayAllocator()
|
||||
{
|
||||
}
|
||||
|
||||
virtual ValueInternalArray *newArray()
|
||||
{
|
||||
ValueInternalArray *array = arraysAllocator_.allocate();
|
||||
new (array) ValueInternalArray(); // placement new
|
||||
return array;
|
||||
}
|
||||
|
||||
virtual ValueInternalArray *newArrayCopy( const ValueInternalArray &other )
|
||||
{
|
||||
ValueInternalArray *array = arraysAllocator_.allocate();
|
||||
new (array) ValueInternalArray( other ); // placement new
|
||||
return array;
|
||||
}
|
||||
|
||||
virtual void destructArray( ValueInternalArray *array )
|
||||
{
|
||||
if ( array )
|
||||
{
|
||||
array->~ValueInternalArray();
|
||||
arraysAllocator_.release( array );
|
||||
}
|
||||
}
|
||||
|
||||
virtual void reallocateArrayPageIndex( Value **&indexes,
|
||||
ValueInternalArray::PageIndex &indexCount,
|
||||
ValueInternalArray::PageIndex minNewIndexCount )
|
||||
{
|
||||
ValueInternalArray::PageIndex newIndexCount = (indexCount*3)/2 + 1;
|
||||
if ( minNewIndexCount > newIndexCount )
|
||||
newIndexCount = minNewIndexCount;
|
||||
void *newIndexes = realloc( indexes, sizeof(Value*) * newIndexCount );
|
||||
if ( !newIndexes )
|
||||
throw std::bad_alloc();
|
||||
indexCount = newIndexCount;
|
||||
indexes = static_cast<Value **>( newIndexes );
|
||||
}
|
||||
virtual void releaseArrayPageIndex( Value **indexes,
|
||||
ValueInternalArray::PageIndex indexCount )
|
||||
{
|
||||
if ( indexes )
|
||||
free( indexes );
|
||||
}
|
||||
|
||||
virtual Value *allocateArrayPage()
|
||||
{
|
||||
return static_cast<Value *>( pagesAllocator_.allocate() );
|
||||
}
|
||||
|
||||
virtual void releaseArrayPage( Value *value )
|
||||
{
|
||||
if ( value )
|
||||
pagesAllocator_.release( value );
|
||||
}
|
||||
private:
|
||||
BatchAllocator<ValueInternalArray,1> arraysAllocator_;
|
||||
BatchAllocator<Value,ValueInternalArray::itemsPerPage> pagesAllocator_;
|
||||
};
|
||||
#endif // #ifdef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR
|
||||
|
||||
static ValueArrayAllocator *&arrayAllocator()
|
||||
{
|
||||
static DefaultValueArrayAllocator defaultAllocator;
|
||||
static ValueArrayAllocator *arrayAllocator = &defaultAllocator;
|
||||
return arrayAllocator;
|
||||
}
|
||||
|
||||
static struct DummyArrayAllocatorInitializer {
|
||||
DummyArrayAllocatorInitializer()
|
||||
{
|
||||
arrayAllocator(); // ensure arrayAllocator() statics are initialized before main().
|
||||
}
|
||||
} dummyArrayAllocatorInitializer;
|
||||
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// class ValueInternalArray
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
bool
|
||||
ValueInternalArray::equals( const IteratorState &x,
|
||||
const IteratorState &other )
|
||||
{
|
||||
return x.array_ == other.array_
|
||||
&& x.currentItemIndex_ == other.currentItemIndex_
|
||||
&& x.currentPageIndex_ == other.currentPageIndex_;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ValueInternalArray::increment( IteratorState &it )
|
||||
{
|
||||
JSON_ASSERT_MESSAGE( it.array_ &&
|
||||
(it.currentPageIndex_ - it.array_->pages_)*itemsPerPage + it.currentItemIndex_
|
||||
!= it.array_->size_,
|
||||
"ValueInternalArray::increment(): moving iterator beyond end" );
|
||||
++(it.currentItemIndex_);
|
||||
if ( it.currentItemIndex_ == itemsPerPage )
|
||||
{
|
||||
it.currentItemIndex_ = 0;
|
||||
++(it.currentPageIndex_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ValueInternalArray::decrement( IteratorState &it )
|
||||
{
|
||||
JSON_ASSERT_MESSAGE( it.array_ && it.currentPageIndex_ == it.array_->pages_
|
||||
&& it.currentItemIndex_ == 0,
|
||||
"ValueInternalArray::decrement(): moving iterator beyond end" );
|
||||
if ( it.currentItemIndex_ == 0 )
|
||||
{
|
||||
it.currentItemIndex_ = itemsPerPage-1;
|
||||
--(it.currentPageIndex_);
|
||||
}
|
||||
else
|
||||
{
|
||||
--(it.currentItemIndex_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Value &
|
||||
ValueInternalArray::unsafeDereference( const IteratorState &it )
|
||||
{
|
||||
return (*(it.currentPageIndex_))[it.currentItemIndex_];
|
||||
}
|
||||
|
||||
|
||||
Value &
|
||||
ValueInternalArray::dereference( const IteratorState &it )
|
||||
{
|
||||
JSON_ASSERT_MESSAGE( it.array_ &&
|
||||
(it.currentPageIndex_ - it.array_->pages_)*itemsPerPage + it.currentItemIndex_
|
||||
< it.array_->size_,
|
||||
"ValueInternalArray::dereference(): dereferencing invalid iterator" );
|
||||
return unsafeDereference( it );
|
||||
}
|
||||
|
||||
void
|
||||
ValueInternalArray::makeBeginIterator( IteratorState &it ) const
|
||||
{
|
||||
it.array_ = const_cast<ValueInternalArray *>( this );
|
||||
it.currentItemIndex_ = 0;
|
||||
it.currentPageIndex_ = pages_;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ValueInternalArray::makeIterator( IteratorState &it, ArrayIndex index ) const
|
||||
{
|
||||
it.array_ = const_cast<ValueInternalArray *>( this );
|
||||
it.currentItemIndex_ = index % itemsPerPage;
|
||||
it.currentPageIndex_ = pages_ + index / itemsPerPage;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ValueInternalArray::makeEndIterator( IteratorState &it ) const
|
||||
{
|
||||
makeIterator( it, size_ );
|
||||
}
|
||||
|
||||
|
||||
ValueInternalArray::ValueInternalArray()
|
||||
: pages_( 0 )
|
||||
, size_( 0 )
|
||||
, pageCount_( 0 )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
ValueInternalArray::ValueInternalArray( const ValueInternalArray &other )
|
||||
: pages_( 0 )
|
||||
, pageCount_( 0 )
|
||||
, size_( other.size_ )
|
||||
{
|
||||
PageIndex minNewPages = other.size_ / itemsPerPage;
|
||||
arrayAllocator()->reallocateArrayPageIndex( pages_, pageCount_, minNewPages );
|
||||
JSON_ASSERT_MESSAGE( pageCount_ >= minNewPages,
|
||||
"ValueInternalArray::reserve(): bad reallocation" );
|
||||
IteratorState itOther;
|
||||
other.makeBeginIterator( itOther );
|
||||
Value *value;
|
||||
for ( ArrayIndex index = 0; index < size_; ++index, increment(itOther) )
|
||||
{
|
||||
if ( index % itemsPerPage == 0 )
|
||||
{
|
||||
PageIndex pageIndex = index / itemsPerPage;
|
||||
value = arrayAllocator()->allocateArrayPage();
|
||||
pages_[pageIndex] = value;
|
||||
}
|
||||
new (value) Value( dereference( itOther ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ValueInternalArray &
|
||||
ValueInternalArray::operator =( const ValueInternalArray &other )
|
||||
{
|
||||
ValueInternalArray temp( other );
|
||||
swap( temp );
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
ValueInternalArray::~ValueInternalArray()
|
||||
{
|
||||
// destroy all constructed items
|
||||
IteratorState it;
|
||||
IteratorState itEnd;
|
||||
makeBeginIterator( it);
|
||||
makeEndIterator( itEnd );
|
||||
for ( ; !equals(it,itEnd); increment(it) )
|
||||
{
|
||||
Value *value = &dereference(it);
|
||||
value->~Value();
|
||||
}
|
||||
// release all pages
|
||||
PageIndex lastPageIndex = size_ / itemsPerPage;
|
||||
for ( PageIndex pageIndex = 0; pageIndex < lastPageIndex; ++pageIndex )
|
||||
arrayAllocator()->releaseArrayPage( pages_[pageIndex] );
|
||||
// release pages index
|
||||
arrayAllocator()->releaseArrayPageIndex( pages_, pageCount_ );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ValueInternalArray::swap( ValueInternalArray &other )
|
||||
{
|
||||
Value **tempPages = pages_;
|
||||
pages_ = other.pages_;
|
||||
other.pages_ = tempPages;
|
||||
ArrayIndex tempSize = size_;
|
||||
size_ = other.size_;
|
||||
other.size_ = tempSize;
|
||||
PageIndex tempPageCount = pageCount_;
|
||||
pageCount_ = other.pageCount_;
|
||||
other.pageCount_ = tempPageCount;
|
||||
}
|
||||
|
||||
void
|
||||
ValueInternalArray::clear()
|
||||
{
|
||||
ValueInternalArray dummy;
|
||||
swap( dummy );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ValueInternalArray::resize( ArrayIndex newSize )
|
||||
{
|
||||
if ( newSize == 0 )
|
||||
clear();
|
||||
else if ( newSize < size_ )
|
||||
{
|
||||
IteratorState it;
|
||||
IteratorState itEnd;
|
||||
makeIterator( it, newSize );
|
||||
makeIterator( itEnd, size_ );
|
||||
for ( ; !equals(it,itEnd); increment(it) )
|
||||
{
|
||||
Value *value = &dereference(it);
|
||||
value->~Value();
|
||||
}
|
||||
PageIndex pageIndex = (newSize + itemsPerPage - 1) / itemsPerPage;
|
||||
PageIndex lastPageIndex = size_ / itemsPerPage;
|
||||
for ( ; pageIndex < lastPageIndex; ++pageIndex )
|
||||
arrayAllocator()->releaseArrayPage( pages_[pageIndex] );
|
||||
size_ = newSize;
|
||||
}
|
||||
else if ( newSize > size_ )
|
||||
resolveReference( newSize );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ValueInternalArray::makeIndexValid( ArrayIndex index )
|
||||
{
|
||||
// Need to enlarge page index ?
|
||||
if ( index >= pageCount_ * itemsPerPage )
|
||||
{
|
||||
PageIndex minNewPages = (index + 1) / itemsPerPage;
|
||||
arrayAllocator()->reallocateArrayPageIndex( pages_, pageCount_, minNewPages );
|
||||
JSON_ASSERT_MESSAGE( pageCount_ >= minNewPages, "ValueInternalArray::reserve(): bad reallocation" );
|
||||
}
|
||||
|
||||
// Need to allocate new pages ?
|
||||
ArrayIndex nextPageIndex =
|
||||
(size_ % itemsPerPage) != 0 ? size_ - (size_%itemsPerPage) + itemsPerPage
|
||||
: size_;
|
||||
if ( nextPageIndex <= index )
|
||||
{
|
||||
PageIndex pageIndex = nextPageIndex / itemsPerPage;
|
||||
PageIndex pageToAllocate = (index - nextPageIndex) / itemsPerPage + 1;
|
||||
for ( ; pageToAllocate-- > 0; ++pageIndex )
|
||||
pages_[pageIndex] = arrayAllocator()->allocateArrayPage();
|
||||
}
|
||||
|
||||
// Initialize all new entries
|
||||
IteratorState it;
|
||||
IteratorState itEnd;
|
||||
makeIterator( it, size_ );
|
||||
size_ = index + 1;
|
||||
makeIterator( itEnd, size_ );
|
||||
for ( ; !equals(it,itEnd); increment(it) )
|
||||
{
|
||||
Value *value = &dereference(it);
|
||||
new (value) Value(); // Construct a default value using placement new
|
||||
}
|
||||
}
|
||||
|
||||
Value &
|
||||
ValueInternalArray::resolveReference( ArrayIndex index )
|
||||
{
|
||||
if ( index >= size_ )
|
||||
makeIndexValid( index );
|
||||
return pages_[index/itemsPerPage][index%itemsPerPage];
|
||||
}
|
||||
|
||||
Value *
|
||||
ValueInternalArray::find( ArrayIndex index ) const
|
||||
{
|
||||
if ( index >= size_ )
|
||||
return 0;
|
||||
return &(pages_[index/itemsPerPage][index%itemsPerPage]);
|
||||
}
|
||||
|
||||
ValueInternalArray::ArrayIndex
|
||||
ValueInternalArray::size() const
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
int
|
||||
ValueInternalArray::distance( const IteratorState &x, const IteratorState &y )
|
||||
{
|
||||
return indexOf(y) - indexOf(x);
|
||||
}
|
||||
|
||||
|
||||
ValueInternalArray::ArrayIndex
|
||||
ValueInternalArray::indexOf( const IteratorState &iterator )
|
||||
{
|
||||
if ( !iterator.array_ )
|
||||
return ArrayIndex(-1);
|
||||
return ArrayIndex(
|
||||
(iterator.currentPageIndex_ - iterator.array_->pages_) * itemsPerPage
|
||||
+ iterator.currentItemIndex_ );
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
ValueInternalArray::compare( const ValueInternalArray &other ) const
|
||||
{
|
||||
int sizeDiff( size_ - other.size_ );
|
||||
if ( sizeDiff != 0 )
|
||||
return sizeDiff;
|
||||
|
||||
for ( ArrayIndex index =0; index < size_; ++index )
|
||||
{
|
||||
int diff = pages_[index/itemsPerPage][index%itemsPerPage].compare(
|
||||
other.pages_[index/itemsPerPage][index%itemsPerPage] );
|
||||
if ( diff != 0 )
|
||||
return diff;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
607
src/cpp/json/json_internalmap.inl
Normal file
607
src/cpp/json/json_internalmap.inl
Normal file
@@ -0,0 +1,607 @@
|
||||
// included by json_value.cpp
|
||||
// everything is within Json namespace
|
||||
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// class ValueInternalMap
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
|
||||
/** \internal MUST be safely initialized using memset( this, 0, sizeof(ValueInternalLink) );
|
||||
* This optimization is used by the fast allocator.
|
||||
*/
|
||||
ValueInternalLink::ValueInternalLink()
|
||||
: previous_( 0 )
|
||||
, next_( 0 )
|
||||
{
|
||||
}
|
||||
|
||||
ValueInternalLink::~ValueInternalLink()
|
||||
{
|
||||
for ( int index =0; index < itemPerLink; ++index )
|
||||
{
|
||||
if ( !items_[index].isItemAvailable() )
|
||||
{
|
||||
if ( !items_[index].isMemberNameStatic() )
|
||||
free( keys_[index] );
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
ValueMapAllocator::~ValueMapAllocator()
|
||||
{
|
||||
}
|
||||
|
||||
#ifdef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR
|
||||
class DefaultValueMapAllocator : public ValueMapAllocator
|
||||
{
|
||||
public: // overridden from ValueMapAllocator
|
||||
virtual ValueInternalMap *newMap()
|
||||
{
|
||||
return new ValueInternalMap();
|
||||
}
|
||||
|
||||
virtual ValueInternalMap *newMapCopy( const ValueInternalMap &other )
|
||||
{
|
||||
return new ValueInternalMap( other );
|
||||
}
|
||||
|
||||
virtual void destructMap( ValueInternalMap *map )
|
||||
{
|
||||
delete map;
|
||||
}
|
||||
|
||||
virtual ValueInternalLink *allocateMapBuckets( unsigned int size )
|
||||
{
|
||||
return new ValueInternalLink[size];
|
||||
}
|
||||
|
||||
virtual void releaseMapBuckets( ValueInternalLink *links )
|
||||
{
|
||||
delete [] links;
|
||||
}
|
||||
|
||||
virtual ValueInternalLink *allocateMapLink()
|
||||
{
|
||||
return new ValueInternalLink();
|
||||
}
|
||||
|
||||
virtual void releaseMapLink( ValueInternalLink *link )
|
||||
{
|
||||
delete link;
|
||||
}
|
||||
};
|
||||
#else
|
||||
/// @todo make this thread-safe (lock when accessign batch allocator)
|
||||
class DefaultValueMapAllocator : public ValueMapAllocator
|
||||
{
|
||||
public: // overridden from ValueMapAllocator
|
||||
virtual ValueInternalMap *newMap()
|
||||
{
|
||||
ValueInternalMap *map = mapsAllocator_.allocate();
|
||||
new (map) ValueInternalMap(); // placement new
|
||||
return map;
|
||||
}
|
||||
|
||||
virtual ValueInternalMap *newMapCopy( const ValueInternalMap &other )
|
||||
{
|
||||
ValueInternalMap *map = mapsAllocator_.allocate();
|
||||
new (map) ValueInternalMap( other ); // placement new
|
||||
return map;
|
||||
}
|
||||
|
||||
virtual void destructMap( ValueInternalMap *map )
|
||||
{
|
||||
if ( map )
|
||||
{
|
||||
map->~ValueInternalMap();
|
||||
mapsAllocator_.release( map );
|
||||
}
|
||||
}
|
||||
|
||||
virtual ValueInternalLink *allocateMapBuckets( unsigned int size )
|
||||
{
|
||||
return new ValueInternalLink[size];
|
||||
}
|
||||
|
||||
virtual void releaseMapBuckets( ValueInternalLink *links )
|
||||
{
|
||||
delete [] links;
|
||||
}
|
||||
|
||||
virtual ValueInternalLink *allocateMapLink()
|
||||
{
|
||||
ValueInternalLink *link = linksAllocator_.allocate();
|
||||
memset( link, 0, sizeof(ValueInternalLink) );
|
||||
return link;
|
||||
}
|
||||
|
||||
virtual void releaseMapLink( ValueInternalLink *link )
|
||||
{
|
||||
link->~ValueInternalLink();
|
||||
linksAllocator_.release( link );
|
||||
}
|
||||
private:
|
||||
BatchAllocator<ValueInternalMap,1> mapsAllocator_;
|
||||
BatchAllocator<ValueInternalLink,1> linksAllocator_;
|
||||
};
|
||||
#endif
|
||||
|
||||
static ValueMapAllocator *&mapAllocator()
|
||||
{
|
||||
static DefaultValueMapAllocator defaultAllocator;
|
||||
static ValueMapAllocator *mapAllocator = &defaultAllocator;
|
||||
return mapAllocator;
|
||||
}
|
||||
|
||||
static struct DummyMapAllocatorInitializer {
|
||||
DummyMapAllocatorInitializer()
|
||||
{
|
||||
mapAllocator(); // ensure mapAllocator() statics are initialized before main().
|
||||
}
|
||||
} dummyMapAllocatorInitializer;
|
||||
|
||||
|
||||
|
||||
// h(K) = value * K >> w ; with w = 32 & K prime w.r.t. 2^32.
|
||||
|
||||
/*
|
||||
use linked list hash map.
|
||||
buckets array is a container.
|
||||
linked list element contains 6 key/values. (memory = (16+4) * 6 + 4 = 124)
|
||||
value have extra state: valid, available, deleted
|
||||
*/
|
||||
|
||||
|
||||
ValueInternalMap::ValueInternalMap()
|
||||
: buckets_( 0 )
|
||||
, tailLink_( 0 )
|
||||
, bucketsSize_( 0 )
|
||||
, itemCount_( 0 )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
ValueInternalMap::ValueInternalMap( const ValueInternalMap &other )
|
||||
: buckets_( 0 )
|
||||
, tailLink_( 0 )
|
||||
, bucketsSize_( 0 )
|
||||
, itemCount_( 0 )
|
||||
{
|
||||
reserve( other.itemCount_ );
|
||||
IteratorState it;
|
||||
IteratorState itEnd;
|
||||
other.makeBeginIterator( it );
|
||||
other.makeEndIterator( itEnd );
|
||||
for ( ; !equals(it,itEnd); increment(it) )
|
||||
{
|
||||
bool isStatic;
|
||||
const char *memberName = key( it, isStatic );
|
||||
const Value &aValue = value( it );
|
||||
resolveReference(memberName, isStatic) = aValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ValueInternalMap &
|
||||
ValueInternalMap::operator =( const ValueInternalMap &other )
|
||||
{
|
||||
ValueInternalMap dummy( other );
|
||||
swap( dummy );
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
ValueInternalMap::~ValueInternalMap()
|
||||
{
|
||||
if ( buckets_ )
|
||||
{
|
||||
for ( BucketIndex bucketIndex =0; bucketIndex < bucketsSize_; ++bucketIndex )
|
||||
{
|
||||
ValueInternalLink *link = buckets_[bucketIndex].next_;
|
||||
while ( link )
|
||||
{
|
||||
ValueInternalLink *linkToRelease = link;
|
||||
link = link->next_;
|
||||
mapAllocator()->releaseMapLink( linkToRelease );
|
||||
}
|
||||
}
|
||||
mapAllocator()->releaseMapBuckets( buckets_ );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ValueInternalMap::swap( ValueInternalMap &other )
|
||||
{
|
||||
ValueInternalLink *tempBuckets = buckets_;
|
||||
buckets_ = other.buckets_;
|
||||
other.buckets_ = tempBuckets;
|
||||
ValueInternalLink *tempTailLink = tailLink_;
|
||||
tailLink_ = other.tailLink_;
|
||||
other.tailLink_ = tempTailLink;
|
||||
BucketIndex tempBucketsSize = bucketsSize_;
|
||||
bucketsSize_ = other.bucketsSize_;
|
||||
other.bucketsSize_ = tempBucketsSize;
|
||||
BucketIndex tempItemCount = itemCount_;
|
||||
itemCount_ = other.itemCount_;
|
||||
other.itemCount_ = tempItemCount;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ValueInternalMap::clear()
|
||||
{
|
||||
ValueInternalMap dummy;
|
||||
swap( dummy );
|
||||
}
|
||||
|
||||
|
||||
ValueInternalMap::BucketIndex
|
||||
ValueInternalMap::size() const
|
||||
{
|
||||
return itemCount_;
|
||||
}
|
||||
|
||||
bool
|
||||
ValueInternalMap::reserveDelta( BucketIndex growth )
|
||||
{
|
||||
return reserve( itemCount_ + growth );
|
||||
}
|
||||
|
||||
bool
|
||||
ValueInternalMap::reserve( BucketIndex newItemCount )
|
||||
{
|
||||
if ( !buckets_ && newItemCount > 0 )
|
||||
{
|
||||
buckets_ = mapAllocator()->allocateMapBuckets( 1 );
|
||||
bucketsSize_ = 1;
|
||||
tailLink_ = &buckets_[0];
|
||||
}
|
||||
// BucketIndex idealBucketCount = (newItemCount + ValueInternalLink::itemPerLink) / ValueInternalLink::itemPerLink;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
const Value *
|
||||
ValueInternalMap::find( const char *key ) const
|
||||
{
|
||||
if ( !bucketsSize_ )
|
||||
return 0;
|
||||
HashKey hashedKey = hash( key );
|
||||
BucketIndex bucketIndex = hashedKey % bucketsSize_;
|
||||
for ( const ValueInternalLink *current = &buckets_[bucketIndex];
|
||||
current != 0;
|
||||
current = current->next_ )
|
||||
{
|
||||
for ( BucketIndex index=0; index < ValueInternalLink::itemPerLink; ++index )
|
||||
{
|
||||
if ( current->items_[index].isItemAvailable() )
|
||||
return 0;
|
||||
if ( strcmp( key, current->keys_[index] ) == 0 )
|
||||
return ¤t->items_[index];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Value *
|
||||
ValueInternalMap::find( const char *key )
|
||||
{
|
||||
const ValueInternalMap *constThis = this;
|
||||
return const_cast<Value *>( constThis->find( key ) );
|
||||
}
|
||||
|
||||
|
||||
Value &
|
||||
ValueInternalMap::resolveReference( const char *key,
|
||||
bool isStatic )
|
||||
{
|
||||
HashKey hashedKey = hash( key );
|
||||
if ( bucketsSize_ )
|
||||
{
|
||||
BucketIndex bucketIndex = hashedKey % bucketsSize_;
|
||||
ValueInternalLink **previous = 0;
|
||||
BucketIndex index;
|
||||
for ( ValueInternalLink *current = &buckets_[bucketIndex];
|
||||
current != 0;
|
||||
previous = ¤t->next_, current = current->next_ )
|
||||
{
|
||||
for ( index=0; index < ValueInternalLink::itemPerLink; ++index )
|
||||
{
|
||||
if ( current->items_[index].isItemAvailable() )
|
||||
return setNewItem( key, isStatic, current, index );
|
||||
if ( strcmp( key, current->keys_[index] ) == 0 )
|
||||
return current->items_[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reserveDelta( 1 );
|
||||
return unsafeAdd( key, isStatic, hashedKey );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ValueInternalMap::remove( const char *key )
|
||||
{
|
||||
HashKey hashedKey = hash( key );
|
||||
if ( !bucketsSize_ )
|
||||
return;
|
||||
BucketIndex bucketIndex = hashedKey % bucketsSize_;
|
||||
for ( ValueInternalLink *link = &buckets_[bucketIndex];
|
||||
link != 0;
|
||||
link = link->next_ )
|
||||
{
|
||||
BucketIndex index;
|
||||
for ( index =0; index < ValueInternalLink::itemPerLink; ++index )
|
||||
{
|
||||
if ( link->items_[index].isItemAvailable() )
|
||||
return;
|
||||
if ( strcmp( key, link->keys_[index] ) == 0 )
|
||||
{
|
||||
doActualRemove( link, index, bucketIndex );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ValueInternalMap::doActualRemove( ValueInternalLink *link,
|
||||
BucketIndex index,
|
||||
BucketIndex bucketIndex )
|
||||
{
|
||||
// find last item of the bucket and swap it with the 'removed' one.
|
||||
// set removed items flags to 'available'.
|
||||
// if last page only contains 'available' items, then desallocate it (it's empty)
|
||||
ValueInternalLink *&lastLink = getLastLinkInBucket( index );
|
||||
BucketIndex lastItemIndex = 1; // a link can never be empty, so start at 1
|
||||
for ( ;
|
||||
lastItemIndex < ValueInternalLink::itemPerLink;
|
||||
++lastItemIndex ) // may be optimized with dicotomic search
|
||||
{
|
||||
if ( lastLink->items_[lastItemIndex].isItemAvailable() )
|
||||
break;
|
||||
}
|
||||
|
||||
BucketIndex lastUsedIndex = lastItemIndex - 1;
|
||||
Value *valueToDelete = &link->items_[index];
|
||||
Value *valueToPreserve = &lastLink->items_[lastUsedIndex];
|
||||
if ( valueToDelete != valueToPreserve )
|
||||
valueToDelete->swap( *valueToPreserve );
|
||||
if ( lastUsedIndex == 0 ) // page is now empty
|
||||
{ // remove it from bucket linked list and delete it.
|
||||
ValueInternalLink *linkPreviousToLast = lastLink->previous_;
|
||||
if ( linkPreviousToLast != 0 ) // can not deleted bucket link.
|
||||
{
|
||||
mapAllocator()->releaseMapLink( lastLink );
|
||||
linkPreviousToLast->next_ = 0;
|
||||
lastLink = linkPreviousToLast;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Value dummy;
|
||||
valueToPreserve->swap( dummy ); // restore deleted to default Value.
|
||||
valueToPreserve->setItemUsed( false );
|
||||
}
|
||||
--itemCount_;
|
||||
}
|
||||
|
||||
|
||||
ValueInternalLink *&
|
||||
ValueInternalMap::getLastLinkInBucket( BucketIndex bucketIndex )
|
||||
{
|
||||
if ( bucketIndex == bucketsSize_ - 1 )
|
||||
return tailLink_;
|
||||
ValueInternalLink *&previous = buckets_[bucketIndex+1].previous_;
|
||||
if ( !previous )
|
||||
previous = &buckets_[bucketIndex];
|
||||
return previous;
|
||||
}
|
||||
|
||||
|
||||
Value &
|
||||
ValueInternalMap::setNewItem( const char *key,
|
||||
bool isStatic,
|
||||
ValueInternalLink *link,
|
||||
BucketIndex index )
|
||||
{
|
||||
char *duplicatedKey = valueAllocator()->makeMemberName( key );
|
||||
++itemCount_;
|
||||
link->keys_[index] = duplicatedKey;
|
||||
link->items_[index].setItemUsed();
|
||||
link->items_[index].setMemberNameIsStatic( isStatic );
|
||||
return link->items_[index]; // items already default constructed.
|
||||
}
|
||||
|
||||
|
||||
Value &
|
||||
ValueInternalMap::unsafeAdd( const char *key,
|
||||
bool isStatic,
|
||||
HashKey hashedKey )
|
||||
{
|
||||
JSON_ASSERT_MESSAGE( bucketsSize_ > 0, "ValueInternalMap::unsafeAdd(): internal logic error." );
|
||||
BucketIndex bucketIndex = hashedKey % bucketsSize_;
|
||||
ValueInternalLink *&previousLink = getLastLinkInBucket( bucketIndex );
|
||||
ValueInternalLink *link = previousLink;
|
||||
BucketIndex index;
|
||||
for ( index =0; index < ValueInternalLink::itemPerLink; ++index )
|
||||
{
|
||||
if ( link->items_[index].isItemAvailable() )
|
||||
break;
|
||||
}
|
||||
if ( index == ValueInternalLink::itemPerLink ) // need to add a new page
|
||||
{
|
||||
ValueInternalLink *newLink = mapAllocator()->allocateMapLink();
|
||||
index = 0;
|
||||
link->next_ = newLink;
|
||||
previousLink = newLink;
|
||||
link = newLink;
|
||||
}
|
||||
return setNewItem( key, isStatic, link, index );
|
||||
}
|
||||
|
||||
|
||||
ValueInternalMap::HashKey
|
||||
ValueInternalMap::hash( const char *key ) const
|
||||
{
|
||||
HashKey hash = 0;
|
||||
while ( *key )
|
||||
hash += *key++ * 37;
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
ValueInternalMap::compare( const ValueInternalMap &other ) const
|
||||
{
|
||||
int sizeDiff( itemCount_ - other.itemCount_ );
|
||||
if ( sizeDiff != 0 )
|
||||
return sizeDiff;
|
||||
// Strict order guaranty is required. Compare all keys FIRST, then compare values.
|
||||
IteratorState it;
|
||||
IteratorState itEnd;
|
||||
makeBeginIterator( it );
|
||||
makeEndIterator( itEnd );
|
||||
for ( ; !equals(it,itEnd); increment(it) )
|
||||
{
|
||||
if ( !other.find( key( it ) ) )
|
||||
return 1;
|
||||
}
|
||||
|
||||
// All keys are equals, let's compare values
|
||||
makeBeginIterator( it );
|
||||
for ( ; !equals(it,itEnd); increment(it) )
|
||||
{
|
||||
const Value *otherValue = other.find( key( it ) );
|
||||
int valueDiff = value(it).compare( *otherValue );
|
||||
if ( valueDiff != 0 )
|
||||
return valueDiff;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ValueInternalMap::makeBeginIterator( IteratorState &it ) const
|
||||
{
|
||||
it.map_ = const_cast<ValueInternalMap *>( this );
|
||||
it.bucketIndex_ = 0;
|
||||
it.itemIndex_ = 0;
|
||||
it.link_ = buckets_;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ValueInternalMap::makeEndIterator( IteratorState &it ) const
|
||||
{
|
||||
it.map_ = const_cast<ValueInternalMap *>( this );
|
||||
it.bucketIndex_ = bucketsSize_;
|
||||
it.itemIndex_ = 0;
|
||||
it.link_ = 0;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
ValueInternalMap::equals( const IteratorState &x, const IteratorState &other )
|
||||
{
|
||||
return x.map_ == other.map_
|
||||
&& x.bucketIndex_ == other.bucketIndex_
|
||||
&& x.link_ == other.link_
|
||||
&& x.itemIndex_ == other.itemIndex_;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ValueInternalMap::incrementBucket( IteratorState &iterator )
|
||||
{
|
||||
++iterator.bucketIndex_;
|
||||
JSON_ASSERT_MESSAGE( iterator.bucketIndex_ <= iterator.map_->bucketsSize_,
|
||||
"ValueInternalMap::increment(): attempting to iterate beyond end." );
|
||||
if ( iterator.bucketIndex_ == iterator.map_->bucketsSize_ )
|
||||
iterator.link_ = 0;
|
||||
else
|
||||
iterator.link_ = &(iterator.map_->buckets_[iterator.bucketIndex_]);
|
||||
iterator.itemIndex_ = 0;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ValueInternalMap::increment( IteratorState &iterator )
|
||||
{
|
||||
JSON_ASSERT_MESSAGE( iterator.map_, "Attempting to iterator using invalid iterator." );
|
||||
++iterator.itemIndex_;
|
||||
if ( iterator.itemIndex_ == ValueInternalLink::itemPerLink )
|
||||
{
|
||||
JSON_ASSERT_MESSAGE( iterator.link_ != 0,
|
||||
"ValueInternalMap::increment(): attempting to iterate beyond end." );
|
||||
iterator.link_ = iterator.link_->next_;
|
||||
if ( iterator.link_ == 0 )
|
||||
incrementBucket( iterator );
|
||||
}
|
||||
else if ( iterator.link_->items_[iterator.itemIndex_].isItemAvailable() )
|
||||
{
|
||||
incrementBucket( iterator );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ValueInternalMap::decrement( IteratorState &iterator )
|
||||
{
|
||||
if ( iterator.itemIndex_ == 0 )
|
||||
{
|
||||
JSON_ASSERT_MESSAGE( iterator.map_, "Attempting to iterate using invalid iterator." );
|
||||
if ( iterator.link_ == &iterator.map_->buckets_[iterator.bucketIndex_] )
|
||||
{
|
||||
JSON_ASSERT_MESSAGE( iterator.bucketIndex_ > 0, "Attempting to iterate beyond beginning." );
|
||||
--(iterator.bucketIndex_);
|
||||
}
|
||||
iterator.link_ = iterator.link_->previous_;
|
||||
iterator.itemIndex_ = ValueInternalLink::itemPerLink - 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char *
|
||||
ValueInternalMap::key( const IteratorState &iterator )
|
||||
{
|
||||
JSON_ASSERT_MESSAGE( iterator.link_, "Attempting to iterate using invalid iterator." );
|
||||
return iterator.link_->keys_[iterator.itemIndex_];
|
||||
}
|
||||
|
||||
const char *
|
||||
ValueInternalMap::key( const IteratorState &iterator, bool &isStatic )
|
||||
{
|
||||
JSON_ASSERT_MESSAGE( iterator.link_, "Attempting to iterate using invalid iterator." );
|
||||
isStatic = iterator.link_->items_[iterator.itemIndex_].isMemberNameStatic();
|
||||
return iterator.link_->keys_[iterator.itemIndex_];
|
||||
}
|
||||
|
||||
|
||||
Value &
|
||||
ValueInternalMap::value( const IteratorState &iterator )
|
||||
{
|
||||
JSON_ASSERT_MESSAGE( iterator.link_, "Attempting to iterate using invalid iterator." );
|
||||
return iterator.link_->items_[iterator.itemIndex_];
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
ValueInternalMap::distance( const IteratorState &x, const IteratorState &y )
|
||||
{
|
||||
int offset = 0;
|
||||
IteratorState it = x;
|
||||
while ( !equals( it, y ) )
|
||||
increment( it );
|
||||
return offset;
|
||||
}
|
||||
885
src/cpp/json/json_reader.cpp
Normal file
885
src/cpp/json/json_reader.cpp
Normal file
@@ -0,0 +1,885 @@
|
||||
#include "reader.h"
|
||||
#include "value.h"
|
||||
#include <utility>
|
||||
#include <cstdio>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
#if _MSC_VER >= 1400 // VC++ 8.0
|
||||
#pragma warning( disable : 4996 ) // disable warning about strdup being deprecated.
|
||||
#endif
|
||||
|
||||
namespace Json {
|
||||
|
||||
// Implementation of class Features
|
||||
// ////////////////////////////////
|
||||
|
||||
Features::Features()
|
||||
: allowComments_( true )
|
||||
, strictRoot_( false )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Features
|
||||
Features::all()
|
||||
{
|
||||
return Features();
|
||||
}
|
||||
|
||||
|
||||
Features
|
||||
Features::strictMode()
|
||||
{
|
||||
Features features;
|
||||
features.allowComments_ = false;
|
||||
features.strictRoot_ = true;
|
||||
return features;
|
||||
}
|
||||
|
||||
// Implementation of class Reader
|
||||
// ////////////////////////////////
|
||||
|
||||
|
||||
static inline bool
|
||||
in( Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::Char c4 )
|
||||
{
|
||||
return c == c1 || c == c2 || c == c3 || c == c4;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
in( Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::Char c4, Reader::Char c5 )
|
||||
{
|
||||
return c == c1 || c == c2 || c == c3 || c == c4 || c == c5;
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
containsNewLine( Reader::Location begin,
|
||||
Reader::Location end )
|
||||
{
|
||||
for ( ;begin < end; ++begin )
|
||||
if ( *begin == '\n' || *begin == '\r' )
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::string codePointToUTF8(unsigned int cp)
|
||||
{
|
||||
std::string result;
|
||||
|
||||
// based on description from http://en.wikipedia.org/wiki/UTF-8
|
||||
|
||||
if (cp <= 0x7f)
|
||||
{
|
||||
result.resize(1);
|
||||
result[0] = static_cast<char>(cp);
|
||||
}
|
||||
else if (cp <= 0x7FF)
|
||||
{
|
||||
result.resize(2);
|
||||
result[1] = static_cast<char>(0x80 | (0x3f & cp));
|
||||
result[0] = static_cast<char>(0xC0 | (0x1f & (cp >> 6)));
|
||||
}
|
||||
else if (cp <= 0xFFFF)
|
||||
{
|
||||
result.resize(3);
|
||||
result[2] = static_cast<char>(0x80 | (0x3f & cp));
|
||||
result[1] = 0x80 | static_cast<char>((0x3f & (cp >> 6)));
|
||||
result[0] = 0xE0 | static_cast<char>((0xf & (cp >> 12)));
|
||||
}
|
||||
else if (cp <= 0x10FFFF)
|
||||
{
|
||||
result.resize(4);
|
||||
result[3] = static_cast<char>(0x80 | (0x3f & cp));
|
||||
result[2] = static_cast<char>(0x80 | (0x3f & (cp >> 6)));
|
||||
result[1] = static_cast<char>(0x80 | (0x3f & (cp >> 12)));
|
||||
result[0] = static_cast<char>(0xF0 | (0x7 & (cp >> 18)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Class Reader
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
|
||||
Reader::Reader()
|
||||
: features_( Features::all() )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Reader::Reader( const Features &features )
|
||||
: features_( features )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Reader::parse( const std::string &document,
|
||||
Value &root,
|
||||
bool collectComments )
|
||||
{
|
||||
document_ = document;
|
||||
const char *begin = document_.c_str();
|
||||
const char *end = begin + document_.length();
|
||||
return parse( begin, end, root, collectComments );
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Reader::parse( std::istream& sin,
|
||||
Value &root,
|
||||
bool collectComments )
|
||||
{
|
||||
//std::istream_iterator<char> begin(sin);
|
||||
//std::istream_iterator<char> end;
|
||||
// Those would allow streamed input from a file, if parse() were a
|
||||
// template function.
|
||||
|
||||
// Since std::string is reference-counted, this at least does not
|
||||
// create an extra copy.
|
||||
std::string doc;
|
||||
std::getline(sin, doc, (char)EOF);
|
||||
return parse( doc, root, collectComments );
|
||||
}
|
||||
|
||||
bool
|
||||
Reader::parse( const char *beginDoc, const char *endDoc,
|
||||
Value &root,
|
||||
bool collectComments )
|
||||
{
|
||||
if ( !features_.allowComments_ )
|
||||
{
|
||||
collectComments = false;
|
||||
}
|
||||
|
||||
begin_ = beginDoc;
|
||||
end_ = endDoc;
|
||||
collectComments_ = collectComments;
|
||||
current_ = begin_;
|
||||
lastValueEnd_ = 0;
|
||||
lastValue_ = 0;
|
||||
commentsBefore_ = "";
|
||||
errors_.clear();
|
||||
while ( !nodes_.empty() )
|
||||
nodes_.pop();
|
||||
nodes_.push( &root );
|
||||
|
||||
bool successful = readValue();
|
||||
Token token;
|
||||
skipCommentTokens( token );
|
||||
if ( collectComments_ && !commentsBefore_.empty() )
|
||||
root.setComment( commentsBefore_, commentAfter );
|
||||
if ( features_.strictRoot_ )
|
||||
{
|
||||
if ( !root.isArray() && !root.isObject() )
|
||||
{
|
||||
// Set error location to start of doc, ideally should be first token found in doc
|
||||
token.type_ = tokenError;
|
||||
token.start_ = beginDoc;
|
||||
token.end_ = endDoc;
|
||||
addError( "A valid JSON document must be either an array or an object value.",
|
||||
token );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return successful;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Reader::readValue()
|
||||
{
|
||||
Token token;
|
||||
skipCommentTokens( token );
|
||||
bool successful = true;
|
||||
|
||||
if ( collectComments_ && !commentsBefore_.empty() )
|
||||
{
|
||||
currentValue().setComment( commentsBefore_, commentBefore );
|
||||
commentsBefore_ = "";
|
||||
}
|
||||
|
||||
|
||||
switch ( token.type_ )
|
||||
{
|
||||
case tokenObjectBegin:
|
||||
successful = readObject( token );
|
||||
break;
|
||||
case tokenArrayBegin:
|
||||
successful = readArray( token );
|
||||
break;
|
||||
case tokenNumber:
|
||||
successful = decodeNumber( token );
|
||||
break;
|
||||
case tokenString:
|
||||
successful = decodeString( token );
|
||||
break;
|
||||
case tokenTrue:
|
||||
currentValue() = true;
|
||||
break;
|
||||
case tokenFalse:
|
||||
currentValue() = false;
|
||||
break;
|
||||
case tokenNull:
|
||||
currentValue() = Value();
|
||||
break;
|
||||
default:
|
||||
return addError( "Syntax error: value, object or array expected.", token );
|
||||
}
|
||||
|
||||
if ( collectComments_ )
|
||||
{
|
||||
lastValueEnd_ = current_;
|
||||
lastValue_ = ¤tValue();
|
||||
}
|
||||
|
||||
return successful;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Reader::skipCommentTokens( Token &token )
|
||||
{
|
||||
if ( features_.allowComments_ )
|
||||
{
|
||||
do
|
||||
{
|
||||
readToken( token );
|
||||
}
|
||||
while ( token.type_ == tokenComment );
|
||||
}
|
||||
else
|
||||
{
|
||||
readToken( token );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Reader::expectToken( TokenType type, Token &token, const char *message )
|
||||
{
|
||||
readToken( token );
|
||||
if ( token.type_ != type )
|
||||
return addError( message, token );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Reader::readToken( Token &token )
|
||||
{
|
||||
skipSpaces();
|
||||
token.start_ = current_;
|
||||
Char c = getNextChar();
|
||||
bool ok = true;
|
||||
switch ( c )
|
||||
{
|
||||
case '{':
|
||||
token.type_ = tokenObjectBegin;
|
||||
break;
|
||||
case '}':
|
||||
token.type_ = tokenObjectEnd;
|
||||
break;
|
||||
case '[':
|
||||
token.type_ = tokenArrayBegin;
|
||||
break;
|
||||
case ']':
|
||||
token.type_ = tokenArrayEnd;
|
||||
break;
|
||||
case '"':
|
||||
token.type_ = tokenString;
|
||||
ok = readString();
|
||||
break;
|
||||
case '/':
|
||||
token.type_ = tokenComment;
|
||||
ok = readComment();
|
||||
break;
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
case '-':
|
||||
token.type_ = tokenNumber;
|
||||
readNumber();
|
||||
break;
|
||||
case 't':
|
||||
token.type_ = tokenTrue;
|
||||
ok = match( "rue", 3 );
|
||||
break;
|
||||
case 'f':
|
||||
token.type_ = tokenFalse;
|
||||
ok = match( "alse", 4 );
|
||||
break;
|
||||
case 'n':
|
||||
token.type_ = tokenNull;
|
||||
ok = match( "ull", 3 );
|
||||
break;
|
||||
case ',':
|
||||
token.type_ = tokenArraySeparator;
|
||||
break;
|
||||
case ':':
|
||||
token.type_ = tokenMemberSeparator;
|
||||
break;
|
||||
case 0:
|
||||
token.type_ = tokenEndOfStream;
|
||||
break;
|
||||
default:
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
if ( !ok )
|
||||
token.type_ = tokenError;
|
||||
token.end_ = current_;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Reader::skipSpaces()
|
||||
{
|
||||
while ( current_ != end_ )
|
||||
{
|
||||
Char c = *current_;
|
||||
if ( c == ' ' || c == '\t' || c == '\r' || c == '\n' )
|
||||
++current_;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Reader::match( Location pattern,
|
||||
int patternLength )
|
||||
{
|
||||
if ( end_ - current_ < patternLength )
|
||||
return false;
|
||||
int index = patternLength;
|
||||
while ( index-- )
|
||||
if ( current_[index] != pattern[index] )
|
||||
return false;
|
||||
current_ += patternLength;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Reader::readComment()
|
||||
{
|
||||
Location commentBegin = current_ - 1;
|
||||
Char c = getNextChar();
|
||||
bool successful = false;
|
||||
if ( c == '*' )
|
||||
successful = readCStyleComment();
|
||||
else if ( c == '/' )
|
||||
successful = readCppStyleComment();
|
||||
if ( !successful )
|
||||
return false;
|
||||
|
||||
if ( collectComments_ )
|
||||
{
|
||||
CommentPlacement placement = commentBefore;
|
||||
if ( lastValueEnd_ && !containsNewLine( lastValueEnd_, commentBegin ) )
|
||||
{
|
||||
if ( c != '*' || !containsNewLine( commentBegin, current_ ) )
|
||||
placement = commentAfterOnSameLine;
|
||||
}
|
||||
|
||||
addComment( commentBegin, current_, placement );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Reader::addComment( Location begin,
|
||||
Location end,
|
||||
CommentPlacement placement )
|
||||
{
|
||||
assert( collectComments_ );
|
||||
if ( placement == commentAfterOnSameLine )
|
||||
{
|
||||
assert( lastValue_ != 0 );
|
||||
lastValue_->setComment( std::string( begin, end ), placement );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !commentsBefore_.empty() )
|
||||
commentsBefore_ += "\n";
|
||||
commentsBefore_ += std::string( begin, end );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Reader::readCStyleComment()
|
||||
{
|
||||
while ( current_ != end_ )
|
||||
{
|
||||
Char c = getNextChar();
|
||||
if ( c == '*' && *current_ == '/' )
|
||||
break;
|
||||
}
|
||||
return getNextChar() == '/';
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Reader::readCppStyleComment()
|
||||
{
|
||||
while ( current_ != end_ )
|
||||
{
|
||||
Char c = getNextChar();
|
||||
if ( c == '\r' || c == '\n' )
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Reader::readNumber()
|
||||
{
|
||||
while ( current_ != end_ )
|
||||
{
|
||||
if ( !(*current_ >= '0' && *current_ <= '9') &&
|
||||
!in( *current_, '.', 'e', 'E', '+', '-' ) )
|
||||
break;
|
||||
++current_;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
Reader::readString()
|
||||
{
|
||||
Char c = 0;
|
||||
while ( current_ != end_ )
|
||||
{
|
||||
c = getNextChar();
|
||||
if ( c == '\\' )
|
||||
getNextChar();
|
||||
else if ( c == '"' )
|
||||
break;
|
||||
}
|
||||
return c == '"';
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Reader::readObject( Token &tokenStart )
|
||||
{
|
||||
Token tokenName;
|
||||
std::string name;
|
||||
currentValue() = Value( objectValue );
|
||||
while ( readToken( tokenName ) )
|
||||
{
|
||||
bool initialTokenOk = true;
|
||||
while ( tokenName.type_ == tokenComment && initialTokenOk )
|
||||
initialTokenOk = readToken( tokenName );
|
||||
if ( !initialTokenOk )
|
||||
break;
|
||||
if ( tokenName.type_ == tokenObjectEnd && name.empty() ) // empty object
|
||||
return true;
|
||||
if ( tokenName.type_ != tokenString )
|
||||
break;
|
||||
|
||||
name = "";
|
||||
if ( !decodeString( tokenName, name ) )
|
||||
return recoverFromError( tokenObjectEnd );
|
||||
|
||||
Token colon;
|
||||
if ( !readToken( colon ) || colon.type_ != tokenMemberSeparator )
|
||||
{
|
||||
return addErrorAndRecover( "Missing ':' after object member name",
|
||||
colon,
|
||||
tokenObjectEnd );
|
||||
}
|
||||
Value &value = currentValue()[ name ];
|
||||
nodes_.push( &value );
|
||||
bool ok = readValue();
|
||||
nodes_.pop();
|
||||
if ( !ok ) // error already set
|
||||
return recoverFromError( tokenObjectEnd );
|
||||
|
||||
Token comma;
|
||||
if ( !readToken( comma )
|
||||
|| ( comma.type_ != tokenObjectEnd &&
|
||||
comma.type_ != tokenArraySeparator &&
|
||||
comma.type_ != tokenComment ) )
|
||||
{
|
||||
return addErrorAndRecover( "Missing ',' or '}' in object declaration",
|
||||
comma,
|
||||
tokenObjectEnd );
|
||||
}
|
||||
bool finalizeTokenOk = true;
|
||||
while ( comma.type_ == tokenComment &&
|
||||
finalizeTokenOk )
|
||||
finalizeTokenOk = readToken( comma );
|
||||
if ( comma.type_ == tokenObjectEnd )
|
||||
return true;
|
||||
}
|
||||
return addErrorAndRecover( "Missing '}' or object member name",
|
||||
tokenName,
|
||||
tokenObjectEnd );
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Reader::readArray( Token &tokenStart )
|
||||
{
|
||||
currentValue() = Value( arrayValue );
|
||||
skipSpaces();
|
||||
if ( *current_ == ']' ) // empty array
|
||||
{
|
||||
Token endArray;
|
||||
readToken( endArray );
|
||||
return true;
|
||||
}
|
||||
int index = 0;
|
||||
while ( true )
|
||||
{
|
||||
Value &value = currentValue()[ index++ ];
|
||||
nodes_.push( &value );
|
||||
bool ok = readValue();
|
||||
nodes_.pop();
|
||||
if ( !ok ) // error already set
|
||||
return recoverFromError( tokenArrayEnd );
|
||||
|
||||
Token token;
|
||||
// Accept Comment after last item in the array.
|
||||
ok = readToken( token );
|
||||
while ( token.type_ == tokenComment && ok )
|
||||
{
|
||||
ok = readToken( token );
|
||||
}
|
||||
bool badTokenType = ( token.type_ == tokenArraySeparator &&
|
||||
token.type_ == tokenArrayEnd );
|
||||
if ( !ok || badTokenType )
|
||||
{
|
||||
return addErrorAndRecover( "Missing ',' or ']' in array declaration",
|
||||
token,
|
||||
tokenArrayEnd );
|
||||
}
|
||||
if ( token.type_ == tokenArrayEnd )
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Reader::decodeNumber( Token &token )
|
||||
{
|
||||
bool isDouble = false;
|
||||
for ( Location inspect = token.start_; inspect != token.end_; ++inspect )
|
||||
{
|
||||
isDouble = isDouble
|
||||
|| in( *inspect, '.', 'e', 'E', '+' )
|
||||
|| ( *inspect == '-' && inspect != token.start_ );
|
||||
}
|
||||
if ( isDouble )
|
||||
return decodeDouble( token );
|
||||
Location current = token.start_;
|
||||
bool isNegative = *current == '-';
|
||||
if ( isNegative )
|
||||
++current;
|
||||
Value::UInt threshold = (isNegative ? Value::UInt(-Value::minInt)
|
||||
: Value::maxUInt) / 10;
|
||||
Value::UInt value = 0;
|
||||
while ( current < token.end_ )
|
||||
{
|
||||
Char c = *current++;
|
||||
if ( c < '0' || c > '9' )
|
||||
return addError( "'" + std::string( token.start_, token.end_ ) + "' is not a number.", token );
|
||||
if ( value >= threshold )
|
||||
return decodeDouble( token );
|
||||
value = value * 10 + Value::UInt(c - '0');
|
||||
}
|
||||
if ( isNegative )
|
||||
currentValue() = -Value::Int( value );
|
||||
else if ( value <= Value::UInt(Value::maxInt) )
|
||||
currentValue() = Value::Int( value );
|
||||
else
|
||||
currentValue() = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Reader::decodeDouble( Token &token )
|
||||
{
|
||||
double value = 0;
|
||||
const int bufferSize = 32;
|
||||
int count;
|
||||
int length = int(token.end_ - token.start_);
|
||||
if ( length <= bufferSize )
|
||||
{
|
||||
Char buffer[bufferSize];
|
||||
memcpy( buffer, token.start_, length );
|
||||
buffer[length] = 0;
|
||||
count = sscanf( buffer, "%lf", &value );
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string buffer( token.start_, token.end_ );
|
||||
count = sscanf( buffer.c_str(), "%lf", &value );
|
||||
}
|
||||
|
||||
if ( count != 1 )
|
||||
return addError( "'" + std::string( token.start_, token.end_ ) + "' is not a number.", token );
|
||||
currentValue() = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Reader::decodeString( Token &token )
|
||||
{
|
||||
std::string decoded;
|
||||
if ( !decodeString( token, decoded ) )
|
||||
return false;
|
||||
currentValue() = decoded;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Reader::decodeString( Token &token, std::string &decoded )
|
||||
{
|
||||
decoded.reserve( token.end_ - token.start_ - 2 );
|
||||
Location current = token.start_ + 1; // skip '"'
|
||||
Location end = token.end_ - 1; // do not include '"'
|
||||
while ( current != end )
|
||||
{
|
||||
Char c = *current++;
|
||||
if ( c == '"' )
|
||||
break;
|
||||
else if ( c == '\\' )
|
||||
{
|
||||
if ( current == end )
|
||||
return addError( "Empty escape sequence in string", token, current );
|
||||
Char escape = *current++;
|
||||
switch ( escape )
|
||||
{
|
||||
case '"': decoded += '"'; break;
|
||||
case '/': decoded += '/'; break;
|
||||
case '\\': decoded += '\\'; break;
|
||||
case 'b': decoded += '\b'; break;
|
||||
case 'f': decoded += '\f'; break;
|
||||
case 'n': decoded += '\n'; break;
|
||||
case 'r': decoded += '\r'; break;
|
||||
case 't': decoded += '\t'; break;
|
||||
case 'u':
|
||||
{
|
||||
unsigned int unicode;
|
||||
if ( !decodeUnicodeCodePoint( token, current, end, unicode ) )
|
||||
return false;
|
||||
decoded += codePointToUTF8(unicode);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return addError( "Bad escape sequence in string", token, current );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
decoded += c;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
Reader::decodeUnicodeCodePoint( Token &token,
|
||||
Location ¤t,
|
||||
Location end,
|
||||
unsigned int &unicode )
|
||||
{
|
||||
|
||||
if ( !decodeUnicodeEscapeSequence( token, current, end, unicode ) )
|
||||
return false;
|
||||
if (unicode >= 0xD800 && unicode <= 0xDBFF)
|
||||
{
|
||||
// surrogate pairs
|
||||
if (end - current < 6)
|
||||
return addError( "additional six characters expected to parse unicode surrogate pair.", token, current );
|
||||
unsigned int surrogatePair;
|
||||
if (*(current++) == '\\' && *(current++)== 'u')
|
||||
{
|
||||
if (decodeUnicodeEscapeSequence( token, current, end, surrogatePair ))
|
||||
{
|
||||
unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return addError( "expecting another \\u token to begin the second half of a unicode surrogate pair", token, current );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
Reader::decodeUnicodeEscapeSequence( Token &token,
|
||||
Location ¤t,
|
||||
Location end,
|
||||
unsigned int &unicode )
|
||||
{
|
||||
if ( end - current < 4 )
|
||||
return addError( "Bad unicode escape sequence in string: four digits expected.", token, current );
|
||||
unicode = 0;
|
||||
for ( int index =0; index < 4; ++index )
|
||||
{
|
||||
Char c = *current++;
|
||||
unicode *= 16;
|
||||
if ( c >= '0' && c <= '9' )
|
||||
unicode += c - '0';
|
||||
else if ( c >= 'a' && c <= 'f' )
|
||||
unicode += c - 'a' + 10;
|
||||
else if ( c >= 'A' && c <= 'F' )
|
||||
unicode += c - 'A' + 10;
|
||||
else
|
||||
return addError( "Bad unicode escape sequence in string: hexadecimal digit expected.", token, current );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Reader::addError( const std::string &message,
|
||||
Token &token,
|
||||
Location extra )
|
||||
{
|
||||
ErrorInfo info;
|
||||
info.token_ = token;
|
||||
info.message_ = message;
|
||||
info.extra_ = extra;
|
||||
errors_.push_back( info );
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Reader::recoverFromError( TokenType skipUntilToken )
|
||||
{
|
||||
int errorCount = int(errors_.size());
|
||||
Token skip;
|
||||
while ( true )
|
||||
{
|
||||
if ( !readToken(skip) )
|
||||
errors_.resize( errorCount ); // discard errors caused by recovery
|
||||
if ( skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream )
|
||||
break;
|
||||
}
|
||||
errors_.resize( errorCount );
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Reader::addErrorAndRecover( const std::string &message,
|
||||
Token &token,
|
||||
TokenType skipUntilToken )
|
||||
{
|
||||
addError( message, token );
|
||||
return recoverFromError( skipUntilToken );
|
||||
}
|
||||
|
||||
|
||||
Value &
|
||||
Reader::currentValue()
|
||||
{
|
||||
return *(nodes_.top());
|
||||
}
|
||||
|
||||
|
||||
Reader::Char
|
||||
Reader::getNextChar()
|
||||
{
|
||||
if ( current_ == end_ )
|
||||
return 0;
|
||||
return *current_++;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Reader::getLocationLineAndColumn( Location location,
|
||||
int &line,
|
||||
int &column ) const
|
||||
{
|
||||
Location current = begin_;
|
||||
Location lastLineStart = current;
|
||||
line = 0;
|
||||
while ( current < location && current != end_ )
|
||||
{
|
||||
Char c = *current++;
|
||||
if ( c == '\r' )
|
||||
{
|
||||
if ( *current == '\n' )
|
||||
++current;
|
||||
lastLineStart = current;
|
||||
++line;
|
||||
}
|
||||
else if ( c == '\n' )
|
||||
{
|
||||
lastLineStart = current;
|
||||
++line;
|
||||
}
|
||||
}
|
||||
// column & line start at 1
|
||||
column = int(location - lastLineStart) + 1;
|
||||
++line;
|
||||
}
|
||||
|
||||
|
||||
std::string
|
||||
Reader::getLocationLineAndColumn( Location location ) const
|
||||
{
|
||||
int line, column;
|
||||
getLocationLineAndColumn( location, line, column );
|
||||
char buffer[18+16+16+1];
|
||||
sprintf( buffer, "Line %d, Column %d", line, column );
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
std::string
|
||||
Reader::getFormatedErrorMessages() const
|
||||
{
|
||||
std::string formattedMessage;
|
||||
for ( Errors::const_iterator itError = errors_.begin();
|
||||
itError != errors_.end();
|
||||
++itError )
|
||||
{
|
||||
const ErrorInfo &error = *itError;
|
||||
formattedMessage += "* " + getLocationLineAndColumn( error.token_.start_ ) + "\n";
|
||||
formattedMessage += " " + error.message_ + "\n";
|
||||
if ( error.extra_ )
|
||||
formattedMessage += "See " + getLocationLineAndColumn( error.extra_ ) + " for detail.\n";
|
||||
}
|
||||
return formattedMessage;
|
||||
}
|
||||
|
||||
|
||||
std::istream& operator>>( std::istream &sin, Value &root )
|
||||
{
|
||||
Json::Reader reader;
|
||||
bool ok = reader.parse(sin, root, true);
|
||||
//JSON_ASSERT( ok );
|
||||
if (!ok) throw std::runtime_error(reader.getFormatedErrorMessages());
|
||||
return sin;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Json
|
||||
1724
src/cpp/json/json_value.cpp
Normal file
1724
src/cpp/json/json_value.cpp
Normal file
File diff suppressed because it is too large
Load Diff
292
src/cpp/json/json_valueiterator.inl
Normal file
292
src/cpp/json/json_valueiterator.inl
Normal file
@@ -0,0 +1,292 @@
|
||||
// included by json_value.cpp
|
||||
// everything is within Json namespace
|
||||
|
||||
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// class ValueIteratorBase
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
|
||||
ValueIteratorBase::ValueIteratorBase()
|
||||
#ifndef JSON_VALUE_USE_INTERNAL_MAP
|
||||
: current_()
|
||||
, isNull_( true )
|
||||
{
|
||||
}
|
||||
#else
|
||||
: isArray_( true )
|
||||
, isNull_( true )
|
||||
{
|
||||
iterator_.array_ = ValueInternalArray::IteratorState();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef JSON_VALUE_USE_INTERNAL_MAP
|
||||
ValueIteratorBase::ValueIteratorBase( const Value::ObjectValues::iterator ¤t )
|
||||
: current_( current )
|
||||
, isNull_( false )
|
||||
{
|
||||
}
|
||||
#else
|
||||
ValueIteratorBase::ValueIteratorBase( const ValueInternalArray::IteratorState &state )
|
||||
: isArray_( true )
|
||||
{
|
||||
iterator_.array_ = state;
|
||||
}
|
||||
|
||||
|
||||
ValueIteratorBase::ValueIteratorBase( const ValueInternalMap::IteratorState &state )
|
||||
: isArray_( false )
|
||||
{
|
||||
iterator_.map_ = state;
|
||||
}
|
||||
#endif
|
||||
|
||||
Value &
|
||||
ValueIteratorBase::deref() const
|
||||
{
|
||||
#ifndef JSON_VALUE_USE_INTERNAL_MAP
|
||||
return current_->second;
|
||||
#else
|
||||
if ( isArray_ )
|
||||
return ValueInternalArray::dereference( iterator_.array_ );
|
||||
return ValueInternalMap::value( iterator_.map_ );
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ValueIteratorBase::increment()
|
||||
{
|
||||
#ifndef JSON_VALUE_USE_INTERNAL_MAP
|
||||
++current_;
|
||||
#else
|
||||
if ( isArray_ )
|
||||
ValueInternalArray::increment( iterator_.array_ );
|
||||
ValueInternalMap::increment( iterator_.map_ );
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ValueIteratorBase::decrement()
|
||||
{
|
||||
#ifndef JSON_VALUE_USE_INTERNAL_MAP
|
||||
--current_;
|
||||
#else
|
||||
if ( isArray_ )
|
||||
ValueInternalArray::decrement( iterator_.array_ );
|
||||
ValueInternalMap::decrement( iterator_.map_ );
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
ValueIteratorBase::difference_type
|
||||
ValueIteratorBase::computeDistance( const SelfType &other ) const
|
||||
{
|
||||
#ifndef JSON_VALUE_USE_INTERNAL_MAP
|
||||
# ifdef JSON_USE_CPPTL_SMALLMAP
|
||||
return current_ - other.current_;
|
||||
# else
|
||||
// Iterator for null value are initialized using the default
|
||||
// constructor, which initialize current_ to the default
|
||||
// std::map::iterator. As begin() and end() are two instance
|
||||
// of the default std::map::iterator, they can not be compared.
|
||||
// To allow this, we handle this comparison specifically.
|
||||
if ( isNull_ && other.isNull_ )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Usage of std::distance is not portable (does not compile with Sun Studio 12 RogueWave STL,
|
||||
// which is the one used by default).
|
||||
// Using a portable hand-made version for non random iterator instead:
|
||||
// return difference_type( std::distance( current_, other.current_ ) );
|
||||
difference_type myDistance = 0;
|
||||
for ( Value::ObjectValues::iterator it = current_; it != other.current_; ++it )
|
||||
{
|
||||
++myDistance;
|
||||
}
|
||||
return myDistance;
|
||||
# endif
|
||||
#else
|
||||
if ( isArray_ )
|
||||
return ValueInternalArray::distance( iterator_.array_, other.iterator_.array_ );
|
||||
return ValueInternalMap::distance( iterator_.map_, other.iterator_.map_ );
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
ValueIteratorBase::isEqual( const SelfType &other ) const
|
||||
{
|
||||
#ifndef JSON_VALUE_USE_INTERNAL_MAP
|
||||
if ( isNull_ )
|
||||
{
|
||||
return other.isNull_;
|
||||
}
|
||||
return current_ == other.current_;
|
||||
#else
|
||||
if ( isArray_ )
|
||||
return ValueInternalArray::equals( iterator_.array_, other.iterator_.array_ );
|
||||
return ValueInternalMap::equals( iterator_.map_, other.iterator_.map_ );
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ValueIteratorBase::copy( const SelfType &other )
|
||||
{
|
||||
#ifndef JSON_VALUE_USE_INTERNAL_MAP
|
||||
current_ = other.current_;
|
||||
#else
|
||||
if ( isArray_ )
|
||||
iterator_.array_ = other.iterator_.array_;
|
||||
iterator_.map_ = other.iterator_.map_;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
Value
|
||||
ValueIteratorBase::key() const
|
||||
{
|
||||
#ifndef JSON_VALUE_USE_INTERNAL_MAP
|
||||
const Value::CZString czstring = (*current_).first;
|
||||
if ( czstring.c_str() )
|
||||
{
|
||||
if ( czstring.isStaticString() )
|
||||
return Value( StaticString( czstring.c_str() ) );
|
||||
return Value( czstring.c_str() );
|
||||
}
|
||||
return Value( czstring.index() );
|
||||
#else
|
||||
if ( isArray_ )
|
||||
return Value( ValueInternalArray::indexOf( iterator_.array_ ) );
|
||||
bool isStatic;
|
||||
const char *memberName = ValueInternalMap::key( iterator_.map_, isStatic );
|
||||
if ( isStatic )
|
||||
return Value( StaticString( memberName ) );
|
||||
return Value( memberName );
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
UInt
|
||||
ValueIteratorBase::index() const
|
||||
{
|
||||
#ifndef JSON_VALUE_USE_INTERNAL_MAP
|
||||
const Value::CZString czstring = (*current_).first;
|
||||
if ( !czstring.c_str() )
|
||||
return czstring.index();
|
||||
return Value::UInt( -1 );
|
||||
#else
|
||||
if ( isArray_ )
|
||||
return Value::UInt( ValueInternalArray::indexOf( iterator_.array_ ) );
|
||||
return Value::UInt( -1 );
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
const char *
|
||||
ValueIteratorBase::memberName() const
|
||||
{
|
||||
#ifndef JSON_VALUE_USE_INTERNAL_MAP
|
||||
const char *name = (*current_).first.c_str();
|
||||
return name ? name : "";
|
||||
#else
|
||||
if ( !isArray_ )
|
||||
return ValueInternalMap::key( iterator_.map_ );
|
||||
return "";
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// class ValueConstIterator
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
|
||||
ValueConstIterator::ValueConstIterator()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
#ifndef JSON_VALUE_USE_INTERNAL_MAP
|
||||
ValueConstIterator::ValueConstIterator( const Value::ObjectValues::iterator ¤t )
|
||||
: ValueIteratorBase( current )
|
||||
{
|
||||
}
|
||||
#else
|
||||
ValueConstIterator::ValueConstIterator( const ValueInternalArray::IteratorState &state )
|
||||
: ValueIteratorBase( state )
|
||||
{
|
||||
}
|
||||
|
||||
ValueConstIterator::ValueConstIterator( const ValueInternalMap::IteratorState &state )
|
||||
: ValueIteratorBase( state )
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
ValueConstIterator &
|
||||
ValueConstIterator::operator =( const ValueIteratorBase &other )
|
||||
{
|
||||
copy( other );
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// class ValueIterator
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
|
||||
ValueIterator::ValueIterator()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
#ifndef JSON_VALUE_USE_INTERNAL_MAP
|
||||
ValueIterator::ValueIterator( const Value::ObjectValues::iterator ¤t )
|
||||
: ValueIteratorBase( current )
|
||||
{
|
||||
}
|
||||
#else
|
||||
ValueIterator::ValueIterator( const ValueInternalArray::IteratorState &state )
|
||||
: ValueIteratorBase( state )
|
||||
{
|
||||
}
|
||||
|
||||
ValueIterator::ValueIterator( const ValueInternalMap::IteratorState &state )
|
||||
: ValueIteratorBase( state )
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
ValueIterator::ValueIterator( const ValueConstIterator &other )
|
||||
: ValueIteratorBase( other )
|
||||
{
|
||||
}
|
||||
|
||||
ValueIterator::ValueIterator( const ValueIterator &other )
|
||||
: ValueIteratorBase( other )
|
||||
{
|
||||
}
|
||||
|
||||
ValueIterator &
|
||||
ValueIterator::operator =( const SelfType &other )
|
||||
{
|
||||
copy( other );
|
||||
return *this;
|
||||
}
|
||||
829
src/cpp/json/json_writer.cpp
Normal file
829
src/cpp/json/json_writer.cpp
Normal file
@@ -0,0 +1,829 @@
|
||||
#include "writer.h"
|
||||
#include <utility>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#if _MSC_VER >= 1400 // VC++ 8.0
|
||||
#pragma warning( disable : 4996 ) // disable warning about strdup being deprecated.
|
||||
#endif
|
||||
|
||||
namespace Json {
|
||||
|
||||
static bool isControlCharacter(char ch)
|
||||
{
|
||||
return ch > 0 && ch <= 0x1F;
|
||||
}
|
||||
|
||||
static bool containsControlCharacter( const char* str )
|
||||
{
|
||||
while ( *str )
|
||||
{
|
||||
if ( isControlCharacter( *(str++) ) )
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static void uintToString( unsigned int value,
|
||||
char *¤t )
|
||||
{
|
||||
*--current = 0;
|
||||
do
|
||||
{
|
||||
*--current = (value % 10) + '0';
|
||||
value /= 10;
|
||||
}
|
||||
while ( value != 0 );
|
||||
}
|
||||
|
||||
std::string valueToString( Int value )
|
||||
{
|
||||
char buffer[32];
|
||||
char *current = buffer + sizeof(buffer);
|
||||
bool isNegative = value < 0;
|
||||
if ( isNegative )
|
||||
value = -value;
|
||||
uintToString( UInt(value), current );
|
||||
if ( isNegative )
|
||||
*--current = '-';
|
||||
assert( current >= buffer );
|
||||
return current;
|
||||
}
|
||||
|
||||
|
||||
std::string valueToString( UInt value )
|
||||
{
|
||||
char buffer[32];
|
||||
char *current = buffer + sizeof(buffer);
|
||||
uintToString( value, current );
|
||||
assert( current >= buffer );
|
||||
return current;
|
||||
}
|
||||
|
||||
std::string valueToString( double value )
|
||||
{
|
||||
char buffer[32];
|
||||
#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) // Use secure version with visual studio 2005 to avoid warning.
|
||||
sprintf_s(buffer, sizeof(buffer), "%#.16g", value);
|
||||
#else
|
||||
sprintf(buffer, "%#.16g", value);
|
||||
#endif
|
||||
char* ch = buffer + strlen(buffer) - 1;
|
||||
if (*ch != '0') return buffer; // nothing to truncate, so save time
|
||||
while(ch > buffer && *ch == '0'){
|
||||
--ch;
|
||||
}
|
||||
char* last_nonzero = ch;
|
||||
while(ch >= buffer){
|
||||
switch(*ch){
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
--ch;
|
||||
continue;
|
||||
case '.':
|
||||
// Truncate zeroes to save bytes in output, but keep one.
|
||||
*(last_nonzero+2) = '\0';
|
||||
return buffer;
|
||||
default:
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
std::string valueToString( bool value )
|
||||
{
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
|
||||
std::string valueToQuotedString( const char *value )
|
||||
{
|
||||
// Not sure how to handle unicode...
|
||||
if (strpbrk(value, "\"\\\b\f\n\r\t") == NULL && !containsControlCharacter( value ))
|
||||
return std::string("\"") + value + "\"";
|
||||
// We have to walk value and escape any special characters.
|
||||
// Appending to std::string is not efficient, but this should be rare.
|
||||
// (Note: forward slashes are *not* rare, but I am not escaping them.)
|
||||
unsigned maxsize = strlen(value)*2 + 3; // allescaped+quotes+NULL
|
||||
std::string result;
|
||||
result.reserve(maxsize); // to avoid lots of mallocs
|
||||
result += "\"";
|
||||
for (const char* c=value; *c != 0; ++c)
|
||||
{
|
||||
switch(*c)
|
||||
{
|
||||
case '\"':
|
||||
result += "\\\"";
|
||||
break;
|
||||
case '\\':
|
||||
result += "\\\\";
|
||||
break;
|
||||
case '\b':
|
||||
result += "\\b";
|
||||
break;
|
||||
case '\f':
|
||||
result += "\\f";
|
||||
break;
|
||||
case '\n':
|
||||
result += "\\n";
|
||||
break;
|
||||
case '\r':
|
||||
result += "\\r";
|
||||
break;
|
||||
case '\t':
|
||||
result += "\\t";
|
||||
break;
|
||||
//case '/':
|
||||
// Even though \/ is considered a legal escape in JSON, a bare
|
||||
// slash is also legal, so I see no reason to escape it.
|
||||
// (I hope I am not misunderstanding something.
|
||||
// blep notes: actually escaping \/ may be useful in javascript to avoid </
|
||||
// sequence.
|
||||
// Should add a flag to allow this compatibility mode and prevent this
|
||||
// sequence from occurring.
|
||||
default:
|
||||
if ( isControlCharacter( *c ) )
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "\\u" << std::hex << std::uppercase << std::setfill('0') << std::setw(4) << static_cast<int>(*c);
|
||||
result += oss.str();
|
||||
}
|
||||
else
|
||||
{
|
||||
result += *c;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
result += "\"";
|
||||
return result;
|
||||
}
|
||||
|
||||
// Class Writer
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
Writer::~Writer()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// Class FastWriter
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
|
||||
FastWriter::FastWriter()
|
||||
: yamlCompatiblityEnabled_( false )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FastWriter::enableYAMLCompatibility()
|
||||
{
|
||||
yamlCompatiblityEnabled_ = true;
|
||||
}
|
||||
|
||||
|
||||
std::string
|
||||
FastWriter::write( const Value &root )
|
||||
{
|
||||
document_ = "";
|
||||
writeValue( root );
|
||||
document_ += "\n";
|
||||
return document_;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FastWriter::writeValue( const Value &value )
|
||||
{
|
||||
switch ( value.type() )
|
||||
{
|
||||
case nullValue:
|
||||
document_ += "null";
|
||||
break;
|
||||
case intValue:
|
||||
document_ += valueToString( value.asInt() );
|
||||
break;
|
||||
case uintValue:
|
||||
document_ += valueToString( value.asUInt() );
|
||||
break;
|
||||
case realValue:
|
||||
document_ += valueToString( value.asDouble() );
|
||||
break;
|
||||
case stringValue:
|
||||
document_ += valueToQuotedString( value.asCString() );
|
||||
break;
|
||||
case booleanValue:
|
||||
document_ += valueToString( value.asBool() );
|
||||
break;
|
||||
case arrayValue:
|
||||
{
|
||||
document_ += "[";
|
||||
int size = value.size();
|
||||
for ( int index =0; index < size; ++index )
|
||||
{
|
||||
if ( index > 0 )
|
||||
document_ += ",";
|
||||
writeValue( value[index] );
|
||||
}
|
||||
document_ += "]";
|
||||
}
|
||||
break;
|
||||
case objectValue:
|
||||
{
|
||||
Value::Members members( value.getMemberNames() );
|
||||
document_ += "{";
|
||||
for ( Value::Members::iterator it = members.begin();
|
||||
it != members.end();
|
||||
++it )
|
||||
{
|
||||
const std::string &name = *it;
|
||||
if ( it != members.begin() )
|
||||
document_ += ",";
|
||||
document_ += valueToQuotedString( name.c_str() );
|
||||
document_ += yamlCompatiblityEnabled_ ? ": "
|
||||
: ":";
|
||||
writeValue( value[name] );
|
||||
}
|
||||
document_ += "}";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Class StyledWriter
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
|
||||
StyledWriter::StyledWriter()
|
||||
: rightMargin_( 74 )
|
||||
, indentSize_( 3 )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
std::string
|
||||
StyledWriter::write( const Value &root )
|
||||
{
|
||||
document_ = "";
|
||||
addChildValues_ = false;
|
||||
indentString_ = "";
|
||||
writeCommentBeforeValue( root );
|
||||
writeValue( root );
|
||||
writeCommentAfterValueOnSameLine( root );
|
||||
document_ += "\n";
|
||||
return document_;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
StyledWriter::writeValue( const Value &value )
|
||||
{
|
||||
switch ( value.type() )
|
||||
{
|
||||
case nullValue:
|
||||
pushValue( "null" );
|
||||
break;
|
||||
case intValue:
|
||||
pushValue( valueToString( value.asInt() ) );
|
||||
break;
|
||||
case uintValue:
|
||||
pushValue( valueToString( value.asUInt() ) );
|
||||
break;
|
||||
case realValue:
|
||||
pushValue( valueToString( value.asDouble() ) );
|
||||
break;
|
||||
case stringValue:
|
||||
pushValue( valueToQuotedString( value.asCString() ) );
|
||||
break;
|
||||
case booleanValue:
|
||||
pushValue( valueToString( value.asBool() ) );
|
||||
break;
|
||||
case arrayValue:
|
||||
writeArrayValue( value);
|
||||
break;
|
||||
case objectValue:
|
||||
{
|
||||
Value::Members members( value.getMemberNames() );
|
||||
if ( members.empty() )
|
||||
pushValue( "{}" );
|
||||
else
|
||||
{
|
||||
writeWithIndent( "{" );
|
||||
indent();
|
||||
Value::Members::iterator it = members.begin();
|
||||
while ( true )
|
||||
{
|
||||
const std::string &name = *it;
|
||||
const Value &childValue = value[name];
|
||||
writeCommentBeforeValue( childValue );
|
||||
writeWithIndent( valueToQuotedString( name.c_str() ) );
|
||||
document_ += " : ";
|
||||
writeValue( childValue );
|
||||
if ( ++it == members.end() )
|
||||
{
|
||||
writeCommentAfterValueOnSameLine( childValue );
|
||||
break;
|
||||
}
|
||||
document_ += ",";
|
||||
writeCommentAfterValueOnSameLine( childValue );
|
||||
}
|
||||
unindent();
|
||||
writeWithIndent( "}" );
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
StyledWriter::writeArrayValue( const Value &value )
|
||||
{
|
||||
unsigned size = value.size();
|
||||
if ( size == 0 )
|
||||
pushValue( "[]" );
|
||||
else
|
||||
{
|
||||
bool isArrayMultiLine = isMultineArray( value );
|
||||
if ( isArrayMultiLine )
|
||||
{
|
||||
writeWithIndent( "[" );
|
||||
indent();
|
||||
bool hasChildValue = !childValues_.empty();
|
||||
unsigned index =0;
|
||||
while ( true )
|
||||
{
|
||||
const Value &childValue = value[index];
|
||||
writeCommentBeforeValue( childValue );
|
||||
if ( hasChildValue )
|
||||
writeWithIndent( childValues_[index] );
|
||||
else
|
||||
{
|
||||
writeIndent();
|
||||
writeValue( childValue );
|
||||
}
|
||||
if ( ++index == size )
|
||||
{
|
||||
writeCommentAfterValueOnSameLine( childValue );
|
||||
break;
|
||||
}
|
||||
document_ += ",";
|
||||
writeCommentAfterValueOnSameLine( childValue );
|
||||
}
|
||||
unindent();
|
||||
writeWithIndent( "]" );
|
||||
}
|
||||
else // output on a single line
|
||||
{
|
||||
assert( childValues_.size() == size );
|
||||
document_ += "[ ";
|
||||
for ( unsigned index =0; index < size; ++index )
|
||||
{
|
||||
if ( index > 0 )
|
||||
document_ += ", ";
|
||||
document_ += childValues_[index];
|
||||
}
|
||||
document_ += " ]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
StyledWriter::isMultineArray( const Value &value )
|
||||
{
|
||||
int size = value.size();
|
||||
bool isMultiLine = size*3 >= rightMargin_ ;
|
||||
childValues_.clear();
|
||||
for ( int index =0; index < size && !isMultiLine; ++index )
|
||||
{
|
||||
const Value &childValue = value[index];
|
||||
isMultiLine = isMultiLine ||
|
||||
( (childValue.isArray() || childValue.isObject()) &&
|
||||
childValue.size() > 0 );
|
||||
}
|
||||
if ( !isMultiLine ) // check if line length > max line length
|
||||
{
|
||||
childValues_.reserve( size );
|
||||
addChildValues_ = true;
|
||||
int lineLength = 4 + (size-1)*2; // '[ ' + ', '*n + ' ]'
|
||||
for ( int index =0; index < size && !isMultiLine; ++index )
|
||||
{
|
||||
writeValue( value[index] );
|
||||
lineLength += int( childValues_[index].length() );
|
||||
isMultiLine = isMultiLine && hasCommentForValue( value[index] );
|
||||
}
|
||||
addChildValues_ = false;
|
||||
isMultiLine = isMultiLine || lineLength >= rightMargin_;
|
||||
}
|
||||
return isMultiLine;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
StyledWriter::pushValue( const std::string &value )
|
||||
{
|
||||
if ( addChildValues_ )
|
||||
childValues_.push_back( value );
|
||||
else
|
||||
document_ += value;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
StyledWriter::writeIndent()
|
||||
{
|
||||
if ( !document_.empty() )
|
||||
{
|
||||
char last = document_[document_.length()-1];
|
||||
if ( last == ' ' ) // already indented
|
||||
return;
|
||||
if ( last != '\n' ) // Comments may add new-line
|
||||
document_ += '\n';
|
||||
}
|
||||
document_ += indentString_;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
StyledWriter::writeWithIndent( const std::string &value )
|
||||
{
|
||||
writeIndent();
|
||||
document_ += value;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
StyledWriter::indent()
|
||||
{
|
||||
indentString_ += std::string( indentSize_, ' ' );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
StyledWriter::unindent()
|
||||
{
|
||||
assert( int(indentString_.size()) >= indentSize_ );
|
||||
indentString_.resize( indentString_.size() - indentSize_ );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
StyledWriter::writeCommentBeforeValue( const Value &root )
|
||||
{
|
||||
if ( !root.hasComment( commentBefore ) )
|
||||
return;
|
||||
document_ += normalizeEOL( root.getComment( commentBefore ) );
|
||||
document_ += "\n";
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
StyledWriter::writeCommentAfterValueOnSameLine( const Value &root )
|
||||
{
|
||||
if ( root.hasComment( commentAfterOnSameLine ) )
|
||||
document_ += " " + normalizeEOL( root.getComment( commentAfterOnSameLine ) );
|
||||
|
||||
if ( root.hasComment( commentAfter ) )
|
||||
{
|
||||
document_ += "\n";
|
||||
document_ += normalizeEOL( root.getComment( commentAfter ) );
|
||||
document_ += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
StyledWriter::hasCommentForValue( const Value &value )
|
||||
{
|
||||
return value.hasComment( commentBefore )
|
||||
|| value.hasComment( commentAfterOnSameLine )
|
||||
|| value.hasComment( commentAfter );
|
||||
}
|
||||
|
||||
|
||||
std::string
|
||||
StyledWriter::normalizeEOL( const std::string &text )
|
||||
{
|
||||
std::string normalized;
|
||||
normalized.reserve( text.length() );
|
||||
const char *begin = text.c_str();
|
||||
const char *end = begin + text.length();
|
||||
const char *current = begin;
|
||||
while ( current != end )
|
||||
{
|
||||
char c = *current++;
|
||||
if ( c == '\r' ) // mac or dos EOL
|
||||
{
|
||||
if ( *current == '\n' ) // convert dos EOL
|
||||
++current;
|
||||
normalized += '\n';
|
||||
}
|
||||
else // handle unix EOL & other char
|
||||
normalized += c;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
|
||||
// Class StyledStreamWriter
|
||||
// //////////////////////////////////////////////////////////////////
|
||||
|
||||
StyledStreamWriter::StyledStreamWriter( std::string indentation )
|
||||
: document_(NULL)
|
||||
, rightMargin_( 74 )
|
||||
, indentation_( indentation )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
StyledStreamWriter::write( std::ostream &out, const Value &root )
|
||||
{
|
||||
document_ = &out;
|
||||
addChildValues_ = false;
|
||||
indentString_ = "";
|
||||
writeCommentBeforeValue( root );
|
||||
writeValue( root );
|
||||
writeCommentAfterValueOnSameLine( root );
|
||||
*document_ << "\n";
|
||||
document_ = NULL; // Forget the stream, for safety.
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
StyledStreamWriter::writeValue( const Value &value )
|
||||
{
|
||||
switch ( value.type() )
|
||||
{
|
||||
case nullValue:
|
||||
pushValue( "null" );
|
||||
break;
|
||||
case intValue:
|
||||
pushValue( valueToString( value.asInt() ) );
|
||||
break;
|
||||
case uintValue:
|
||||
pushValue( valueToString( value.asUInt() ) );
|
||||
break;
|
||||
case realValue:
|
||||
pushValue( valueToString( value.asDouble() ) );
|
||||
break;
|
||||
case stringValue:
|
||||
pushValue( valueToQuotedString( value.asCString() ) );
|
||||
break;
|
||||
case booleanValue:
|
||||
pushValue( valueToString( value.asBool() ) );
|
||||
break;
|
||||
case arrayValue:
|
||||
writeArrayValue( value);
|
||||
break;
|
||||
case objectValue:
|
||||
{
|
||||
Value::Members members( value.getMemberNames() );
|
||||
if ( members.empty() )
|
||||
pushValue( "{}" );
|
||||
else
|
||||
{
|
||||
writeWithIndent( "{" );
|
||||
indent();
|
||||
Value::Members::iterator it = members.begin();
|
||||
while ( true )
|
||||
{
|
||||
const std::string &name = *it;
|
||||
const Value &childValue = value[name];
|
||||
writeCommentBeforeValue( childValue );
|
||||
writeWithIndent( valueToQuotedString( name.c_str() ) );
|
||||
*document_ << " : ";
|
||||
writeValue( childValue );
|
||||
if ( ++it == members.end() )
|
||||
{
|
||||
writeCommentAfterValueOnSameLine( childValue );
|
||||
break;
|
||||
}
|
||||
*document_ << ",";
|
||||
writeCommentAfterValueOnSameLine( childValue );
|
||||
}
|
||||
unindent();
|
||||
writeWithIndent( "}" );
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
StyledStreamWriter::writeArrayValue( const Value &value )
|
||||
{
|
||||
unsigned size = value.size();
|
||||
if ( size == 0 )
|
||||
pushValue( "[]" );
|
||||
else
|
||||
{
|
||||
bool isArrayMultiLine = isMultineArray( value );
|
||||
if ( isArrayMultiLine )
|
||||
{
|
||||
writeWithIndent( "[" );
|
||||
indent();
|
||||
bool hasChildValue = !childValues_.empty();
|
||||
unsigned index =0;
|
||||
while ( true )
|
||||
{
|
||||
const Value &childValue = value[index];
|
||||
writeCommentBeforeValue( childValue );
|
||||
if ( hasChildValue )
|
||||
writeWithIndent( childValues_[index] );
|
||||
else
|
||||
{
|
||||
writeIndent();
|
||||
writeValue( childValue );
|
||||
}
|
||||
if ( ++index == size )
|
||||
{
|
||||
writeCommentAfterValueOnSameLine( childValue );
|
||||
break;
|
||||
}
|
||||
*document_ << ",";
|
||||
writeCommentAfterValueOnSameLine( childValue );
|
||||
}
|
||||
unindent();
|
||||
writeWithIndent( "]" );
|
||||
}
|
||||
else // output on a single line
|
||||
{
|
||||
assert( childValues_.size() == size );
|
||||
*document_ << "[ ";
|
||||
for ( unsigned index =0; index < size; ++index )
|
||||
{
|
||||
if ( index > 0 )
|
||||
*document_ << ", ";
|
||||
*document_ << childValues_[index];
|
||||
}
|
||||
*document_ << " ]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
StyledStreamWriter::isMultineArray( const Value &value )
|
||||
{
|
||||
int size = value.size();
|
||||
bool isMultiLine = size*3 >= rightMargin_ ;
|
||||
childValues_.clear();
|
||||
for ( int index =0; index < size && !isMultiLine; ++index )
|
||||
{
|
||||
const Value &childValue = value[index];
|
||||
isMultiLine = isMultiLine ||
|
||||
( (childValue.isArray() || childValue.isObject()) &&
|
||||
childValue.size() > 0 );
|
||||
}
|
||||
if ( !isMultiLine ) // check if line length > max line length
|
||||
{
|
||||
childValues_.reserve( size );
|
||||
addChildValues_ = true;
|
||||
int lineLength = 4 + (size-1)*2; // '[ ' + ', '*n + ' ]'
|
||||
for ( int index =0; index < size && !isMultiLine; ++index )
|
||||
{
|
||||
writeValue( value[index] );
|
||||
lineLength += int( childValues_[index].length() );
|
||||
isMultiLine = isMultiLine && hasCommentForValue( value[index] );
|
||||
}
|
||||
addChildValues_ = false;
|
||||
isMultiLine = isMultiLine || lineLength >= rightMargin_;
|
||||
}
|
||||
return isMultiLine;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
StyledStreamWriter::pushValue( const std::string &value )
|
||||
{
|
||||
if ( addChildValues_ )
|
||||
childValues_.push_back( value );
|
||||
else
|
||||
*document_ << value;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
StyledStreamWriter::writeIndent()
|
||||
{
|
||||
/*
|
||||
Some comments in this method would have been nice. ;-)
|
||||
|
||||
if ( !document_.empty() )
|
||||
{
|
||||
char last = document_[document_.length()-1];
|
||||
if ( last == ' ' ) // already indented
|
||||
return;
|
||||
if ( last != '\n' ) // Comments may add new-line
|
||||
*document_ << '\n';
|
||||
}
|
||||
*/
|
||||
*document_ << '\n' << indentString_;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
StyledStreamWriter::writeWithIndent( const std::string &value )
|
||||
{
|
||||
writeIndent();
|
||||
*document_ << value;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
StyledStreamWriter::indent()
|
||||
{
|
||||
indentString_ += indentation_;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
StyledStreamWriter::unindent()
|
||||
{
|
||||
assert( indentString_.size() >= indentation_.size() );
|
||||
indentString_.resize( indentString_.size() - indentation_.size() );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
StyledStreamWriter::writeCommentBeforeValue( const Value &root )
|
||||
{
|
||||
if ( !root.hasComment( commentBefore ) )
|
||||
return;
|
||||
*document_ << normalizeEOL( root.getComment( commentBefore ) );
|
||||
*document_ << "\n";
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
StyledStreamWriter::writeCommentAfterValueOnSameLine( const Value &root )
|
||||
{
|
||||
if ( root.hasComment( commentAfterOnSameLine ) )
|
||||
*document_ << " " + normalizeEOL( root.getComment( commentAfterOnSameLine ) );
|
||||
|
||||
if ( root.hasComment( commentAfter ) )
|
||||
{
|
||||
*document_ << "\n";
|
||||
*document_ << normalizeEOL( root.getComment( commentAfter ) );
|
||||
*document_ << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
StyledStreamWriter::hasCommentForValue( const Value &value )
|
||||
{
|
||||
return value.hasComment( commentBefore )
|
||||
|| value.hasComment( commentAfterOnSameLine )
|
||||
|| value.hasComment( commentAfter );
|
||||
}
|
||||
|
||||
|
||||
std::string
|
||||
StyledStreamWriter::normalizeEOL( const std::string &text )
|
||||
{
|
||||
std::string normalized;
|
||||
normalized.reserve( text.length() );
|
||||
const char *begin = text.c_str();
|
||||
const char *end = begin + text.length();
|
||||
const char *current = begin;
|
||||
while ( current != end )
|
||||
{
|
||||
char c = *current++;
|
||||
if ( c == '\r' ) // mac or dos EOL
|
||||
{
|
||||
if ( *current == '\n' ) // convert dos EOL
|
||||
++current;
|
||||
normalized += '\n';
|
||||
}
|
||||
else // handle unix EOL & other char
|
||||
normalized += c;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
|
||||
std::ostream& operator<<( std::ostream &sout, const Value &root )
|
||||
{
|
||||
Json::StyledStreamWriter writer;
|
||||
writer.write(sout, root);
|
||||
return sout;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Json
|
||||
196
src/cpp/json/reader.h
Normal file
196
src/cpp/json/reader.h
Normal file
@@ -0,0 +1,196 @@
|
||||
#ifndef CPPTL_JSON_READER_H_INCLUDED
|
||||
# define CPPTL_JSON_READER_H_INCLUDED
|
||||
|
||||
# include "features.h"
|
||||
# include "value.h"
|
||||
# include <deque>
|
||||
# include <stack>
|
||||
# include <string>
|
||||
# include <iostream>
|
||||
|
||||
namespace Json {
|
||||
|
||||
/** \brief Unserialize a <a HREF="http://www.json.org">JSON</a> document into a Value.
|
||||
*
|
||||
*/
|
||||
class JSON_API Reader
|
||||
{
|
||||
public:
|
||||
typedef char Char;
|
||||
typedef const Char *Location;
|
||||
|
||||
/** \brief Constructs a Reader allowing all features
|
||||
* for parsing.
|
||||
*/
|
||||
Reader();
|
||||
|
||||
/** \brief Constructs a Reader allowing the specified feature set
|
||||
* for parsing.
|
||||
*/
|
||||
Reader( const Features &features );
|
||||
|
||||
/** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a> document.
|
||||
* \param document UTF-8 encoded string containing the document to read.
|
||||
* \param root [out] Contains the root value of the document if it was
|
||||
* successfully parsed.
|
||||
* \param collectComments \c true to collect comment and allow writing them back during
|
||||
* serialization, \c false to discard comments.
|
||||
* This parameter is ignored if Features::allowComments_
|
||||
* is \c false.
|
||||
* \return \c true if the document was successfully parsed, \c false if an error occurred.
|
||||
*/
|
||||
bool parse( const std::string &document,
|
||||
Value &root,
|
||||
bool collectComments = true );
|
||||
|
||||
/** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a> document.
|
||||
* \param document UTF-8 encoded string containing the document to read.
|
||||
* \param root [out] Contains the root value of the document if it was
|
||||
* successfully parsed.
|
||||
* \param collectComments \c true to collect comment and allow writing them back during
|
||||
* serialization, \c false to discard comments.
|
||||
* This parameter is ignored if Features::allowComments_
|
||||
* is \c false.
|
||||
* \return \c true if the document was successfully parsed, \c false if an error occurred.
|
||||
*/
|
||||
bool parse( const char *beginDoc, const char *endDoc,
|
||||
Value &root,
|
||||
bool collectComments = true );
|
||||
|
||||
/// \brief Parse from input stream.
|
||||
/// \see Json::operator>>(std::istream&, Json::Value&).
|
||||
bool parse( std::istream &is,
|
||||
Value &root,
|
||||
bool collectComments = true );
|
||||
|
||||
/** \brief Returns a user friendly string that list errors in the parsed document.
|
||||
* \return Formatted error message with the list of errors with their location in
|
||||
* the parsed document. An empty string is returned if no error occurred
|
||||
* during parsing.
|
||||
*/
|
||||
std::string getFormatedErrorMessages() const;
|
||||
|
||||
private:
|
||||
enum TokenType
|
||||
{
|
||||
tokenEndOfStream = 0,
|
||||
tokenObjectBegin,
|
||||
tokenObjectEnd,
|
||||
tokenArrayBegin,
|
||||
tokenArrayEnd,
|
||||
tokenString,
|
||||
tokenNumber,
|
||||
tokenTrue,
|
||||
tokenFalse,
|
||||
tokenNull,
|
||||
tokenArraySeparator,
|
||||
tokenMemberSeparator,
|
||||
tokenComment,
|
||||
tokenError
|
||||
};
|
||||
|
||||
class Token
|
||||
{
|
||||
public:
|
||||
TokenType type_;
|
||||
Location start_;
|
||||
Location end_;
|
||||
};
|
||||
|
||||
class ErrorInfo
|
||||
{
|
||||
public:
|
||||
Token token_;
|
||||
std::string message_;
|
||||
Location extra_;
|
||||
};
|
||||
|
||||
typedef std::deque<ErrorInfo> Errors;
|
||||
|
||||
bool expectToken( TokenType type, Token &token, const char *message );
|
||||
bool readToken( Token &token );
|
||||
void skipSpaces();
|
||||
bool match( Location pattern,
|
||||
int patternLength );
|
||||
bool readComment();
|
||||
bool readCStyleComment();
|
||||
bool readCppStyleComment();
|
||||
bool readString();
|
||||
void readNumber();
|
||||
bool readValue();
|
||||
bool readObject( Token &token );
|
||||
bool readArray( Token &token );
|
||||
bool decodeNumber( Token &token );
|
||||
bool decodeString( Token &token );
|
||||
bool decodeString( Token &token, std::string &decoded );
|
||||
bool decodeDouble( Token &token );
|
||||
bool decodeUnicodeCodePoint( Token &token,
|
||||
Location ¤t,
|
||||
Location end,
|
||||
unsigned int &unicode );
|
||||
bool decodeUnicodeEscapeSequence( Token &token,
|
||||
Location ¤t,
|
||||
Location end,
|
||||
unsigned int &unicode );
|
||||
bool addError( const std::string &message,
|
||||
Token &token,
|
||||
Location extra = 0 );
|
||||
bool recoverFromError( TokenType skipUntilToken );
|
||||
bool addErrorAndRecover( const std::string &message,
|
||||
Token &token,
|
||||
TokenType skipUntilToken );
|
||||
void skipUntilSpace();
|
||||
Value ¤tValue();
|
||||
Char getNextChar();
|
||||
void getLocationLineAndColumn( Location location,
|
||||
int &line,
|
||||
int &column ) const;
|
||||
std::string getLocationLineAndColumn( Location location ) const;
|
||||
void addComment( Location begin,
|
||||
Location end,
|
||||
CommentPlacement placement );
|
||||
void skipCommentTokens( Token &token );
|
||||
|
||||
typedef std::stack<Value *> Nodes;
|
||||
Nodes nodes_;
|
||||
Errors errors_;
|
||||
std::string document_;
|
||||
Location begin_;
|
||||
Location end_;
|
||||
Location current_;
|
||||
Location lastValueEnd_;
|
||||
Value *lastValue_;
|
||||
std::string commentsBefore_;
|
||||
Features features_;
|
||||
bool collectComments_;
|
||||
};
|
||||
|
||||
/** \brief Read from 'sin' into 'root'.
|
||||
|
||||
Always keep comments from the input JSON.
|
||||
|
||||
This can be used to read a file into a particular sub-object.
|
||||
For example:
|
||||
\code
|
||||
Json::Value root;
|
||||
cin >> root["dir"]["file"];
|
||||
cout << root;
|
||||
\endcode
|
||||
Result:
|
||||
\verbatim
|
||||
{
|
||||
"dir": {
|
||||
"file": {
|
||||
// The input stream JSON would be nested here.
|
||||
}
|
||||
}
|
||||
}
|
||||
\endverbatim
|
||||
\throw std::exception on parse error.
|
||||
\see Json::operator<<()
|
||||
*/
|
||||
std::istream& operator>>( std::istream&, Value& );
|
||||
|
||||
} // namespace Json
|
||||
|
||||
#endif // CPPTL_JSON_READER_H_INCLUDED
|
||||
1069
src/cpp/json/value.h
Normal file
1069
src/cpp/json/value.h
Normal file
File diff suppressed because it is too large
Load Diff
1
src/cpp/json/version
Normal file
1
src/cpp/json/version
Normal file
@@ -0,0 +1 @@
|
||||
0.5.0
|
||||
174
src/cpp/json/writer.h
Normal file
174
src/cpp/json/writer.h
Normal file
@@ -0,0 +1,174 @@
|
||||
#ifndef JSON_WRITER_H_INCLUDED
|
||||
# define JSON_WRITER_H_INCLUDED
|
||||
|
||||
# include "value.h"
|
||||
# include <vector>
|
||||
# include <string>
|
||||
# include <iostream>
|
||||
|
||||
namespace Json {
|
||||
|
||||
class Value;
|
||||
|
||||
/** \brief Abstract class for writers.
|
||||
*/
|
||||
class JSON_API Writer
|
||||
{
|
||||
public:
|
||||
virtual ~Writer();
|
||||
|
||||
virtual std::string write( const Value &root ) = 0;
|
||||
};
|
||||
|
||||
/** \brief Outputs a Value in <a HREF="http://www.json.org">JSON</a> format without formatting (not human friendly).
|
||||
*
|
||||
* The JSON document is written in a single line. It is not intended for 'human' consumption,
|
||||
* but may be usefull to support feature such as RPC where bandwith is limited.
|
||||
* \sa Reader, Value
|
||||
*/
|
||||
class JSON_API FastWriter : public Writer
|
||||
{
|
||||
public:
|
||||
FastWriter();
|
||||
virtual ~FastWriter(){}
|
||||
|
||||
void enableYAMLCompatibility();
|
||||
|
||||
public: // overridden from Writer
|
||||
virtual std::string write( const Value &root );
|
||||
|
||||
private:
|
||||
void writeValue( const Value &value );
|
||||
|
||||
std::string document_;
|
||||
bool yamlCompatiblityEnabled_;
|
||||
};
|
||||
|
||||
/** \brief Writes a Value in <a HREF="http://www.json.org">JSON</a> format in a human friendly way.
|
||||
*
|
||||
* The rules for line break and indent are as follow:
|
||||
* - Object value:
|
||||
* - if empty then print {} without indent and line break
|
||||
* - if not empty the print '{', line break & indent, print one value per line
|
||||
* and then unindent and line break and print '}'.
|
||||
* - Array value:
|
||||
* - if empty then print [] without indent and line break
|
||||
* - if the array contains no object value, empty array or some other value types,
|
||||
* and all the values fit on one lines, then print the array on a single line.
|
||||
* - otherwise, it the values do not fit on one line, or the array contains
|
||||
* object or non empty array, then print one value per line.
|
||||
*
|
||||
* If the Value have comments then they are outputed according to their #CommentPlacement.
|
||||
*
|
||||
* \sa Reader, Value, Value::setComment()
|
||||
*/
|
||||
class JSON_API StyledWriter: public Writer
|
||||
{
|
||||
public:
|
||||
StyledWriter();
|
||||
virtual ~StyledWriter(){}
|
||||
|
||||
public: // overridden from Writer
|
||||
/** \brief Serialize a Value in <a HREF="http://www.json.org">JSON</a> format.
|
||||
* \param root Value to serialize.
|
||||
* \return String containing the JSON document that represents the root value.
|
||||
*/
|
||||
virtual std::string write( const Value &root );
|
||||
|
||||
private:
|
||||
void writeValue( const Value &value );
|
||||
void writeArrayValue( const Value &value );
|
||||
bool isMultineArray( const Value &value );
|
||||
void pushValue( const std::string &value );
|
||||
void writeIndent();
|
||||
void writeWithIndent( const std::string &value );
|
||||
void indent();
|
||||
void unindent();
|
||||
void writeCommentBeforeValue( const Value &root );
|
||||
void writeCommentAfterValueOnSameLine( const Value &root );
|
||||
bool hasCommentForValue( const Value &value );
|
||||
static std::string normalizeEOL( const std::string &text );
|
||||
|
||||
typedef std::vector<std::string> ChildValues;
|
||||
|
||||
ChildValues childValues_;
|
||||
std::string document_;
|
||||
std::string indentString_;
|
||||
int rightMargin_;
|
||||
int indentSize_;
|
||||
bool addChildValues_;
|
||||
};
|
||||
|
||||
/** \brief Writes a Value in <a HREF="http://www.json.org">JSON</a> format in a human friendly way,
|
||||
to a stream rather than to a string.
|
||||
*
|
||||
* The rules for line break and indent are as follow:
|
||||
* - Object value:
|
||||
* - if empty then print {} without indent and line break
|
||||
* - if not empty the print '{', line break & indent, print one value per line
|
||||
* and then unindent and line break and print '}'.
|
||||
* - Array value:
|
||||
* - if empty then print [] without indent and line break
|
||||
* - if the array contains no object value, empty array or some other value types,
|
||||
* and all the values fit on one lines, then print the array on a single line.
|
||||
* - otherwise, it the values do not fit on one line, or the array contains
|
||||
* object or non empty array, then print one value per line.
|
||||
*
|
||||
* If the Value have comments then they are outputed according to their #CommentPlacement.
|
||||
*
|
||||
* \param indentation Each level will be indented by this amount extra.
|
||||
* \sa Reader, Value, Value::setComment()
|
||||
*/
|
||||
class JSON_API StyledStreamWriter
|
||||
{
|
||||
public:
|
||||
StyledStreamWriter( std::string indentation="\t" );
|
||||
~StyledStreamWriter(){}
|
||||
|
||||
public:
|
||||
/** \brief Serialize a Value in <a HREF="http://www.json.org">JSON</a> format.
|
||||
* \param out Stream to write to. (Can be ostringstream, e.g.)
|
||||
* \param root Value to serialize.
|
||||
* \note There is no point in deriving from Writer, since write() should not return a value.
|
||||
*/
|
||||
void write( std::ostream &out, const Value &root );
|
||||
|
||||
private:
|
||||
void writeValue( const Value &value );
|
||||
void writeArrayValue( const Value &value );
|
||||
bool isMultineArray( const Value &value );
|
||||
void pushValue( const std::string &value );
|
||||
void writeIndent();
|
||||
void writeWithIndent( const std::string &value );
|
||||
void indent();
|
||||
void unindent();
|
||||
void writeCommentBeforeValue( const Value &root );
|
||||
void writeCommentAfterValueOnSameLine( const Value &root );
|
||||
bool hasCommentForValue( const Value &value );
|
||||
static std::string normalizeEOL( const std::string &text );
|
||||
|
||||
typedef std::vector<std::string> ChildValues;
|
||||
|
||||
ChildValues childValues_;
|
||||
std::ostream* document_;
|
||||
std::string indentString_;
|
||||
int rightMargin_;
|
||||
std::string indentation_;
|
||||
bool addChildValues_;
|
||||
};
|
||||
|
||||
std::string JSON_API valueToString( Int value );
|
||||
std::string JSON_API valueToString( UInt value );
|
||||
std::string JSON_API valueToString( double value );
|
||||
std::string JSON_API valueToString( bool value );
|
||||
std::string JSON_API valueToQuotedString( const char *value );
|
||||
|
||||
/// \brief Output using the StyledStreamWriter.
|
||||
/// \see Json::operator>>()
|
||||
std::ostream& operator<<( std::ostream&, const Value &root );
|
||||
|
||||
} // namespace Json
|
||||
|
||||
|
||||
|
||||
#endif // JSON_WRITER_H_INCLUDED
|
||||
67
src/cpp/ripple/AccountState.cpp
Normal file
67
src/cpp/ripple/AccountState.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
|
||||
#include "AccountState.h"
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/make_shared.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "../json/writer.h"
|
||||
|
||||
#include "Ledger.h"
|
||||
#include "Serializer.h"
|
||||
#include "Log.h"
|
||||
|
||||
AccountState::AccountState(const RippleAddress& naAccountID) : mAccountID(naAccountID), mValid(false)
|
||||
{
|
||||
if (!naAccountID.isValid()) return;
|
||||
|
||||
mLedgerEntry = boost::make_shared<SerializedLedgerEntry>(ltACCOUNT_ROOT);
|
||||
mLedgerEntry->setIndex(Ledger::getAccountRootIndex(naAccountID));
|
||||
mLedgerEntry->setFieldAccount(sfAccount, naAccountID.getAccountID());
|
||||
|
||||
mValid = true;
|
||||
}
|
||||
|
||||
AccountState::AccountState(SLE::ref ledgerEntry, const RippleAddress& naAccountID) :
|
||||
mAccountID(naAccountID), mLedgerEntry(ledgerEntry), mValid(false)
|
||||
{
|
||||
if (!mLedgerEntry)
|
||||
return;
|
||||
if (mLedgerEntry->getType() != ltACCOUNT_ROOT)
|
||||
return;
|
||||
|
||||
mValid = true;
|
||||
}
|
||||
|
||||
std::string AccountState::createGravatarUrl(uint128 uEmailHash)
|
||||
{
|
||||
std::vector<unsigned char> vucMD5(uEmailHash.begin(), uEmailHash.end());
|
||||
std::string strMD5Lower = strHex(vucMD5);
|
||||
boost::to_lower(strMD5Lower);
|
||||
|
||||
return str(boost::format("http://www.gravatar.com/avatar/%s") % strMD5Lower);
|
||||
}
|
||||
|
||||
void AccountState::addJson(Json::Value& val)
|
||||
{
|
||||
val = mLedgerEntry->getJson(0);
|
||||
|
||||
if (mValid)
|
||||
{
|
||||
if (mLedgerEntry->isFieldPresent(sfEmailHash))
|
||||
val["UrlGravatar"] = createGravatarUrl(mLedgerEntry->getFieldH128(sfEmailHash));
|
||||
}
|
||||
else
|
||||
{
|
||||
val["Invalid"] = true;
|
||||
}
|
||||
}
|
||||
|
||||
void AccountState::dump()
|
||||
{
|
||||
Json::Value j(Json::objectValue);
|
||||
addJson(j);
|
||||
Log(lsINFO) << j;
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
59
src/cpp/ripple/AccountState.h
Normal file
59
src/cpp/ripple/AccountState.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#ifndef __ACCOUNTSTATE__
|
||||
#define __ACCOUNTSTATE__
|
||||
|
||||
//
|
||||
// Provide abstract access to an account's state, such that access to the serialized format is hidden.
|
||||
//
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include "../json/value.h"
|
||||
|
||||
#include "types.h"
|
||||
#include "RippleAddress.h"
|
||||
#include "SerializedLedger.h"
|
||||
|
||||
class AccountState
|
||||
{
|
||||
public:
|
||||
typedef boost::shared_ptr<AccountState> pointer;
|
||||
|
||||
private:
|
||||
RippleAddress mAccountID;
|
||||
RippleAddress mAuthorizedKey;
|
||||
SerializedLedgerEntry::pointer mLedgerEntry;
|
||||
|
||||
bool mValid;
|
||||
|
||||
public:
|
||||
AccountState(const RippleAddress& naAccountID); // For new accounts
|
||||
AccountState(SLE::ref ledgerEntry,const RippleAddress& naAccountI); // For accounts in a ledger
|
||||
|
||||
bool bHaveAuthorizedKey()
|
||||
{
|
||||
return mLedgerEntry->isFieldPresent(sfAuthorizedKey);
|
||||
}
|
||||
|
||||
RippleAddress getAuthorizedKey()
|
||||
{
|
||||
return mLedgerEntry->getFieldAccount(sfAuthorizedKey);
|
||||
}
|
||||
|
||||
STAmount getBalance() const { return mLedgerEntry->getFieldAmount(sfBalance); }
|
||||
uint32 getSeq() const { return mLedgerEntry->getFieldU32(sfSequence); }
|
||||
|
||||
SerializedLedgerEntry::pointer getSLE() { return mLedgerEntry; }
|
||||
const SerializedLedgerEntry& peekSLE() const { return *mLedgerEntry; }
|
||||
SerializedLedgerEntry& peekSLE() { return *mLedgerEntry; }
|
||||
|
||||
std::vector<unsigned char> getRaw() const;
|
||||
void addJson(Json::Value& value);
|
||||
void dump();
|
||||
|
||||
static std::string createGravatarUrl(uint128 uEmailHash);
|
||||
};
|
||||
|
||||
#endif
|
||||
// vim:ts=4
|
||||
1426
src/cpp/ripple/Amount.cpp
Normal file
1426
src/cpp/ripple/Amount.cpp
Normal file
File diff suppressed because it is too large
Load Diff
308
src/cpp/ripple/Application.cpp
Normal file
308
src/cpp/ripple/Application.cpp
Normal file
@@ -0,0 +1,308 @@
|
||||
|
||||
#include "Application.h"
|
||||
#include "Config.h"
|
||||
#include "PeerDoor.h"
|
||||
#include "RPCDoor.h"
|
||||
#include "BitcoinUtil.h"
|
||||
#include "key.h"
|
||||
#include "utils.h"
|
||||
#include "TaggedCache.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include "../database/SqliteDatabase.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
SETUP_LOG();
|
||||
LogPartition TaggedCachePartition("TaggedCache");
|
||||
Application* theApp = NULL;
|
||||
|
||||
DatabaseCon::DatabaseCon(const std::string& strName, const char *initStrings[], int initCount)
|
||||
{
|
||||
boost::filesystem::path pPath = theConfig.DATA_DIR / strName;
|
||||
|
||||
mDatabase = new SqliteDatabase(pPath.string().c_str());
|
||||
mDatabase->connect();
|
||||
for(int i = 0; i < initCount; ++i)
|
||||
mDatabase->executeSQL(initStrings[i], true);
|
||||
}
|
||||
|
||||
DatabaseCon::~DatabaseCon()
|
||||
{
|
||||
mDatabase->disconnect();
|
||||
delete mDatabase;
|
||||
}
|
||||
|
||||
Application::Application() :
|
||||
mIOWork(mIOService), mAuxWork(mAuxService), mUNL(mIOService), mNetOps(mIOService, &mMasterLedger),
|
||||
mTempNodeCache("NodeCache", 16384, 90), mHashedObjectStore(16384, 300),
|
||||
mSNTPClient(mAuxService), mRPCHandler(&mNetOps),
|
||||
mRpcDB(NULL), mTxnDB(NULL), mLedgerDB(NULL), mWalletDB(NULL), mHashNodeDB(NULL), mNetNodeDB(NULL),
|
||||
mConnectionPool(mIOService), mPeerDoor(NULL), mRPCDoor(NULL), mWSPublicDoor(NULL), mWSPrivateDoor(NULL),
|
||||
mSweepTimer(mAuxService)
|
||||
{
|
||||
RAND_bytes(mNonce256.begin(), mNonce256.size());
|
||||
RAND_bytes(reinterpret_cast<unsigned char *>(&mNonceST), sizeof(mNonceST));
|
||||
mJobQueue.setThreadCount();
|
||||
mSweepTimer.expires_from_now(boost::posix_time::seconds(60));
|
||||
mSweepTimer.async_wait(boost::bind(&Application::sweep, this));
|
||||
}
|
||||
|
||||
extern const char *RpcDBInit[], *TxnDBInit[], *LedgerDBInit[], *WalletDBInit[], *HashNodeDBInit[], *NetNodeDBInit[];
|
||||
extern int RpcDBCount, TxnDBCount, LedgerDBCount, WalletDBCount, HashNodeDBCount, NetNodeDBCount;
|
||||
|
||||
void Application::stop()
|
||||
{
|
||||
mIOService.stop();
|
||||
mJobQueue.shutdown();
|
||||
mHashedObjectStore.bulkWrite();
|
||||
mValidations.flush();
|
||||
mAuxService.stop();
|
||||
|
||||
cLog(lsINFO) << "Stopped: " << mIOService.stopped();
|
||||
}
|
||||
|
||||
static void InitDB(DatabaseCon** dbCon, const char *fileName, const char *dbInit[], int dbCount)
|
||||
{
|
||||
*dbCon = new DatabaseCon(fileName, dbInit, dbCount);
|
||||
}
|
||||
|
||||
void Application::run()
|
||||
{
|
||||
assert(mTxnDB == NULL);
|
||||
if (!theConfig.DEBUG_LOGFILE.empty())
|
||||
{ // Let DEBUG messages go to the file but only WARNING or higher to regular output (unless verbose)
|
||||
Log::setLogFile(theConfig.DEBUG_LOGFILE);
|
||||
if (Log::getMinSeverity() > lsDEBUG)
|
||||
LogPartition::setSeverity(lsDEBUG);
|
||||
}
|
||||
|
||||
boost::thread auxThread(boost::bind(&boost::asio::io_service::run, &mAuxService));
|
||||
auxThread.detach();
|
||||
|
||||
|
||||
if (!theConfig.RUN_STANDALONE)
|
||||
mSNTPClient.init(theConfig.SNTP_SERVERS);
|
||||
|
||||
//
|
||||
// Construct databases.
|
||||
//
|
||||
boost::thread t1(boost::bind(&InitDB, &mRpcDB, "rpc.db", RpcDBInit, RpcDBCount));
|
||||
boost::thread t2(boost::bind(&InitDB, &mTxnDB, "transaction.db", TxnDBInit, TxnDBCount));
|
||||
boost::thread t3(boost::bind(&InitDB, &mLedgerDB, "ledger.db", LedgerDBInit, LedgerDBCount));
|
||||
boost::thread t4(boost::bind(&InitDB, &mWalletDB, "wallet.db", WalletDBInit, WalletDBCount));
|
||||
boost::thread t5(boost::bind(&InitDB, &mHashNodeDB, "hashnode.db", HashNodeDBInit, HashNodeDBCount));
|
||||
boost::thread t6(boost::bind(&InitDB, &mNetNodeDB, "netnode.db", NetNodeDBInit, NetNodeDBCount));
|
||||
t1.join(); t2.join(); t3.join(); t4.join(); t5.join(); t6.join();
|
||||
|
||||
if (theConfig.START_UP == Config::FRESH)
|
||||
{
|
||||
cLog(lsINFO) << "Starting new Ledger";
|
||||
startNewLedger();
|
||||
}
|
||||
else if (theConfig.START_UP == Config::LOAD)
|
||||
{
|
||||
cLog(lsINFO) << "Loading Old Ledger";
|
||||
loadOldLedger();
|
||||
}
|
||||
else if (theConfig.START_UP == Config::NETWORK)
|
||||
{ // This should probably become the default once we have a stable network
|
||||
if (!theConfig.RUN_STANDALONE)
|
||||
mNetOps.needNetworkLedger();
|
||||
startNewLedger();
|
||||
}
|
||||
else
|
||||
startNewLedger();
|
||||
|
||||
if (theConfig.FULL_HISTORY && (theConfig.START_UP != Config::LOAD))
|
||||
{
|
||||
Ledger::pointer ledger = Ledger::getLastFullLedger();
|
||||
if (ledger)
|
||||
mMasterLedger.setLedgerRangePresent(0, ledger->getLedgerSeq());
|
||||
}
|
||||
|
||||
//
|
||||
// Begin validation and ip maintenance.
|
||||
// - Wallet maintains local information: including identity and network connection persistence information.
|
||||
//
|
||||
mWallet.start();
|
||||
|
||||
//
|
||||
// Set up UNL.
|
||||
//
|
||||
if (!theConfig.RUN_STANDALONE)
|
||||
getUNL().nodeBootstrap();
|
||||
|
||||
|
||||
//
|
||||
// Allow peer connections.
|
||||
//
|
||||
if (!theConfig.RUN_STANDALONE && !theConfig.PEER_IP.empty() && theConfig.PEER_PORT)
|
||||
{
|
||||
mPeerDoor = new PeerDoor(mIOService);
|
||||
}
|
||||
else
|
||||
{
|
||||
cLog(lsINFO) << "Peer interface: disabled";
|
||||
}
|
||||
|
||||
//
|
||||
// Allow RPC connections.
|
||||
//
|
||||
if (!theConfig.RPC_IP.empty() && theConfig.RPC_PORT)
|
||||
{
|
||||
mRPCDoor = new RPCDoor(mIOService);
|
||||
}
|
||||
else
|
||||
{
|
||||
cLog(lsINFO) << "RPC interface: disabled";
|
||||
}
|
||||
|
||||
//
|
||||
// Allow private WS connections.
|
||||
//
|
||||
if (!theConfig.WEBSOCKET_IP.empty() && theConfig.WEBSOCKET_PORT)
|
||||
{
|
||||
mWSPrivateDoor = WSDoor::createWSDoor(theConfig.WEBSOCKET_IP, theConfig.WEBSOCKET_PORT, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
cLog(lsINFO) << "WS private interface: disabled";
|
||||
}
|
||||
|
||||
//
|
||||
// Allow public WS connections.
|
||||
//
|
||||
if (!theConfig.WEBSOCKET_PUBLIC_IP.empty() && theConfig.WEBSOCKET_PUBLIC_PORT)
|
||||
{
|
||||
mWSPublicDoor = WSDoor::createWSDoor(theConfig.WEBSOCKET_PUBLIC_IP, theConfig.WEBSOCKET_PUBLIC_PORT, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
cLog(lsINFO) << "WS public interface: disabled";
|
||||
}
|
||||
|
||||
//
|
||||
// Begin connecting to network.
|
||||
//
|
||||
if (!theConfig.RUN_STANDALONE)
|
||||
mConnectionPool.start();
|
||||
|
||||
|
||||
if (theConfig.RUN_STANDALONE)
|
||||
{
|
||||
cLog(lsWARNING) << "Running in standalone mode";
|
||||
mNetOps.setStandAlone();
|
||||
}
|
||||
else
|
||||
mNetOps.setStateTimer();
|
||||
|
||||
mIOService.run(); // This blocks
|
||||
|
||||
if (mWSPublicDoor)
|
||||
mWSPublicDoor->stop();
|
||||
|
||||
if (mWSPrivateDoor)
|
||||
mWSPrivateDoor->stop();
|
||||
|
||||
cLog(lsINFO) << "Done.";
|
||||
}
|
||||
|
||||
void Application::sweep()
|
||||
{
|
||||
mMasterTransaction.sweep();
|
||||
mHashedObjectStore.sweep();
|
||||
mMasterLedger.sweep();
|
||||
mTempNodeCache.sweep();
|
||||
mValidations.sweep();
|
||||
mSweepTimer.expires_from_now(boost::posix_time::seconds(60));
|
||||
mSweepTimer.async_wait(boost::bind(&Application::sweep, this));
|
||||
}
|
||||
|
||||
Application::~Application()
|
||||
{
|
||||
delete mTxnDB;
|
||||
delete mLedgerDB;
|
||||
delete mWalletDB;
|
||||
delete mHashNodeDB;
|
||||
delete mNetNodeDB;
|
||||
}
|
||||
|
||||
void Application::startNewLedger()
|
||||
{
|
||||
// New stuff.
|
||||
RippleAddress rootSeedMaster = RippleAddress::createSeedGeneric("masterpassphrase");
|
||||
RippleAddress rootGeneratorMaster = RippleAddress::createGeneratorPublic(rootSeedMaster);
|
||||
RippleAddress rootAddress = RippleAddress::createAccountPublic(rootGeneratorMaster, 0);
|
||||
|
||||
// Print enough information to be able to claim root account.
|
||||
cLog(lsINFO) << "Root master seed: " << rootSeedMaster.humanSeed();
|
||||
cLog(lsINFO) << "Root account: " << rootAddress.humanAccountID();
|
||||
|
||||
{
|
||||
Ledger::pointer firstLedger = boost::make_shared<Ledger>(rootAddress, SYSTEM_CURRENCY_START);
|
||||
assert(!!firstLedger->getAccountState(rootAddress));
|
||||
firstLedger->updateHash();
|
||||
firstLedger->setClosed();
|
||||
firstLedger->setAccepted();
|
||||
mMasterLedger.pushLedger(firstLedger);
|
||||
|
||||
Ledger::pointer secondLedger = boost::make_shared<Ledger>(true, boost::ref(*firstLedger));
|
||||
secondLedger->setClosed();
|
||||
secondLedger->setAccepted();
|
||||
mMasterLedger.pushLedger(secondLedger, boost::make_shared<Ledger>(true, boost::ref(*secondLedger)), false);
|
||||
assert(!!secondLedger->getAccountState(rootAddress));
|
||||
mNetOps.setLastCloseTime(secondLedger->getCloseTimeNC());
|
||||
}
|
||||
}
|
||||
|
||||
void Application::loadOldLedger()
|
||||
{
|
||||
try
|
||||
{
|
||||
Ledger::pointer lastLedger = Ledger::getLastFullLedger();
|
||||
|
||||
if (!lastLedger)
|
||||
{
|
||||
std::cout << "No Ledger found?" << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
lastLedger->setClosed();
|
||||
|
||||
cLog(lsINFO) << "Loading ledger " << lastLedger->getHash() << " seq:" << lastLedger->getLedgerSeq();
|
||||
|
||||
if (lastLedger->getAccountHash().isZero())
|
||||
{
|
||||
cLog(lsFATAL) << "Ledger is empty.";
|
||||
assert(false);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (!lastLedger->walkLedger())
|
||||
{
|
||||
cLog(lsFATAL) << "Ledger is missing nodes.";
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (!lastLedger->assertSane())
|
||||
{
|
||||
cLog(lsFATAL) << "Ledger is not sane.";
|
||||
exit(-1);
|
||||
}
|
||||
mMasterLedger.setLedgerRangePresent(0, lastLedger->getLedgerSeq());
|
||||
|
||||
Ledger::pointer openLedger = boost::make_shared<Ledger>(false, boost::ref(*lastLedger));
|
||||
mMasterLedger.switchLedgers(lastLedger, openLedger);
|
||||
mNetOps.setLastCloseTime(lastLedger->getCloseTimeNC());
|
||||
}
|
||||
catch (SHAMapMissingNode& mn)
|
||||
{
|
||||
cLog(lsFATAL) << "Cannot load ledger. " << mn;
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
// vim:ts=4
|
||||
129
src/cpp/ripple/Application.h
Normal file
129
src/cpp/ripple/Application.h
Normal file
@@ -0,0 +1,129 @@
|
||||
#ifndef __APPLICATION__
|
||||
#define __APPLICATION__
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#include "LedgerMaster.h"
|
||||
#include "UniqueNodeList.h"
|
||||
#include "ConnectionPool.h"
|
||||
#include "ScopedLock.h"
|
||||
#include "LedgerAcquire.h"
|
||||
#include "TransactionMaster.h"
|
||||
#include "Wallet.h"
|
||||
#include "Peer.h"
|
||||
#include "NetworkOPs.h"
|
||||
#include "WSDoor.h"
|
||||
#include "TaggedCache.h"
|
||||
#include "ValidationCollection.h"
|
||||
#include "Suppression.h"
|
||||
#include "SNTPClient.h"
|
||||
#include "../database/database.h"
|
||||
#include "JobQueue.h"
|
||||
#include "RPCHandler.h"
|
||||
|
||||
class RPCDoor;
|
||||
class PeerDoor;
|
||||
typedef TaggedCache< uint256, std::vector<unsigned char> > NodeCache;
|
||||
|
||||
class DatabaseCon
|
||||
{
|
||||
protected:
|
||||
Database* mDatabase;
|
||||
boost::recursive_mutex mLock;
|
||||
|
||||
public:
|
||||
DatabaseCon(const std::string& name, const char *initString[], int countInit);
|
||||
~DatabaseCon();
|
||||
Database* getDB() { return mDatabase; }
|
||||
ScopedLock getDBLock() { return ScopedLock(mLock); }
|
||||
};
|
||||
|
||||
class Application
|
||||
{
|
||||
boost::asio::io_service mIOService, mAuxService;
|
||||
boost::asio::io_service::work mIOWork, mAuxWork;
|
||||
|
||||
Wallet mWallet;
|
||||
UniqueNodeList mUNL;
|
||||
LedgerMaster mMasterLedger;
|
||||
LedgerAcquireMaster mMasterLedgerAcquire;
|
||||
TransactionMaster mMasterTransaction;
|
||||
NetworkOPs mNetOps;
|
||||
NodeCache mTempNodeCache;
|
||||
ValidationCollection mValidations;
|
||||
SuppressionTable mSuppressions;
|
||||
HashedObjectStore mHashedObjectStore;
|
||||
SNTPClient mSNTPClient;
|
||||
JobQueue mJobQueue;
|
||||
RPCHandler mRPCHandler;
|
||||
|
||||
DatabaseCon *mRpcDB, *mTxnDB, *mLedgerDB, *mWalletDB, *mHashNodeDB, *mNetNodeDB;
|
||||
|
||||
ConnectionPool mConnectionPool;
|
||||
PeerDoor* mPeerDoor;
|
||||
RPCDoor* mRPCDoor;
|
||||
WSDoor* mWSPublicDoor;
|
||||
WSDoor* mWSPrivateDoor;
|
||||
|
||||
uint256 mNonce256;
|
||||
std::size_t mNonceST;
|
||||
|
||||
boost::asio::deadline_timer mSweepTimer;
|
||||
|
||||
std::map<std::string, Peer::pointer> mPeerMap;
|
||||
boost::recursive_mutex mPeerMapLock;
|
||||
|
||||
void startNewLedger();
|
||||
void loadOldLedger();
|
||||
|
||||
public:
|
||||
Application();
|
||||
~Application();
|
||||
|
||||
ConnectionPool& getConnectionPool() { return mConnectionPool; }
|
||||
|
||||
UniqueNodeList& getUNL() { return mUNL; }
|
||||
|
||||
Wallet& getWallet() { return mWallet ; }
|
||||
NetworkOPs& getOPs() { return mNetOps; }
|
||||
|
||||
boost::asio::io_service& getIOService() { return mIOService; }
|
||||
boost::asio::io_service& getAuxService() { return mAuxService; }
|
||||
|
||||
LedgerMaster& getMasterLedger() { return mMasterLedger; }
|
||||
LedgerAcquireMaster& getMasterLedgerAcquire() { return mMasterLedgerAcquire; }
|
||||
TransactionMaster& getMasterTransaction() { return mMasterTransaction; }
|
||||
NodeCache& getTempNodeCache() { return mTempNodeCache; }
|
||||
HashedObjectStore& getHashedObjectStore() { return mHashedObjectStore; }
|
||||
ValidationCollection& getValidations() { return mValidations; }
|
||||
JobQueue& getJobQueue() { return mJobQueue; }
|
||||
SuppressionTable& getSuppression() { return mSuppressions; }
|
||||
RPCHandler& getRPCHandler() { return mRPCHandler; }
|
||||
|
||||
|
||||
bool isNew(const uint256& s) { return mSuppressions.addSuppression(s); }
|
||||
bool isNew(const uint256& s, uint64 p) { return mSuppressions.addSuppressionPeer(s, p); }
|
||||
bool isNew(const uint256& s, uint64 p, int& f) { return mSuppressions.addSuppressionPeer(s, p, f); }
|
||||
bool isNewFlag(const uint256& s, int f) { return mSuppressions.setFlag(s, f); }
|
||||
bool running() { return mTxnDB != NULL; }
|
||||
bool getSystemTimeOffset(int& offset) { return mSNTPClient.getOffset(offset); }
|
||||
|
||||
DatabaseCon* getRpcDB() { return mRpcDB; }
|
||||
DatabaseCon* getTxnDB() { return mTxnDB; }
|
||||
DatabaseCon* getLedgerDB() { return mLedgerDB; }
|
||||
DatabaseCon* getWalletDB() { return mWalletDB; }
|
||||
DatabaseCon* getHashNodeDB() { return mHashNodeDB; }
|
||||
DatabaseCon* getNetNodeDB() { return mNetNodeDB; }
|
||||
|
||||
uint256 getNonce256() { return mNonce256; }
|
||||
std::size_t getNonceST() { return mNonceST; }
|
||||
|
||||
void run();
|
||||
void stop();
|
||||
void sweep();
|
||||
};
|
||||
|
||||
extern Application* theApp;
|
||||
|
||||
#endif
|
||||
// vim:ts=4
|
||||
107
src/cpp/ripple/BitcoinUtil.cpp
Normal file
107
src/cpp/ripple/BitcoinUtil.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
#include "BitcoinUtil.h"
|
||||
#include <cstdarg>
|
||||
#include <openssl/rand.h>
|
||||
#include <ctime>
|
||||
|
||||
#if defined(WIN32) || defined(WIN64)
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
|
||||
std::string gFormatStr("v1");
|
||||
|
||||
std::string FormatFullVersion()
|
||||
{
|
||||
return(gFormatStr);
|
||||
}
|
||||
|
||||
|
||||
string strprintf(const char* format, ...)
|
||||
{
|
||||
char buffer[50000];
|
||||
char* p = buffer;
|
||||
int limit = sizeof(buffer);
|
||||
int ret;
|
||||
loop
|
||||
{
|
||||
va_list arg_ptr;
|
||||
va_start(arg_ptr, format);
|
||||
ret = _vsnprintf(p, limit, format, arg_ptr);
|
||||
va_end(arg_ptr);
|
||||
if (ret >= 0 && ret < limit)
|
||||
break;
|
||||
if (p != buffer)
|
||||
delete[] p;
|
||||
limit *= 2;
|
||||
p = new char[limit];
|
||||
if (p == NULL)
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
string str(p, p+ret);
|
||||
if (p != buffer)
|
||||
delete[] p;
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
inline int64 GetPerformanceCounter()
|
||||
{
|
||||
int64 nCounter = 0;
|
||||
#if defined(WIN32) || defined(WIN64)
|
||||
QueryPerformanceCounter((LARGE_INTEGER*)&nCounter);
|
||||
#else
|
||||
timeval t;
|
||||
gettimeofday(&t, NULL);
|
||||
nCounter = t.tv_sec * 1000000 + t.tv_usec;
|
||||
#endif
|
||||
return nCounter;
|
||||
}
|
||||
|
||||
void RandAddSeed()
|
||||
{
|
||||
// Seed with CPU performance counter
|
||||
int64 nCounter = GetPerformanceCounter();
|
||||
RAND_add(&nCounter, sizeof(nCounter), 1.5);
|
||||
memset(&nCounter, 0, sizeof(nCounter));
|
||||
}
|
||||
//
|
||||
// "Never go to sea with two chronometers; take one or three."
|
||||
// Our three time sources are:
|
||||
// - System clock
|
||||
// - Median of other nodes's clocks
|
||||
// - The user (asking the user to fix the system clock if the first two disagree)
|
||||
//
|
||||
int64 GetTime()
|
||||
{
|
||||
return time(NULL);
|
||||
}
|
||||
|
||||
void RandAddSeedPerfmon()
|
||||
{
|
||||
RandAddSeed();
|
||||
|
||||
// This can take up to 2 seconds, so only do it every 10 minutes
|
||||
static int64 nLastPerfmon;
|
||||
if (GetTime() < nLastPerfmon + 10 * 60)
|
||||
return;
|
||||
nLastPerfmon = GetTime();
|
||||
|
||||
#ifdef WIN32
|
||||
// Don't need this on Linux, OpenSSL automatically uses /dev/urandom
|
||||
// Seed with the entire set of perfmon data
|
||||
unsigned char pdata[250000];
|
||||
memset(pdata, 0, sizeof(pdata));
|
||||
unsigned long nSize = sizeof(pdata);
|
||||
long ret = RegQueryValueExA(HKEY_PERFORMANCE_DATA, "Global", NULL, NULL, pdata, &nSize);
|
||||
RegCloseKey(HKEY_PERFORMANCE_DATA);
|
||||
if (ret == ERROR_SUCCESS)
|
||||
{
|
||||
RAND_add(pdata, nSize, nSize/100.0);
|
||||
memset(pdata, 0, nSize);
|
||||
//printf("%s RandAddSeed() %d bytes\n", DateTimeStrFormat("%x %H:%M", GetTime()).c_str(), nSize);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
106
src/cpp/ripple/BitcoinUtil.h
Normal file
106
src/cpp/ripple/BitcoinUtil.h
Normal file
@@ -0,0 +1,106 @@
|
||||
#ifndef __BITCOIN_UTIL__
|
||||
#define __BITCOIN_UTIL__
|
||||
|
||||
// TODO: these things should all go somewhere
|
||||
|
||||
#include <string>
|
||||
#include "types.h"
|
||||
#include "uint256.h"
|
||||
#include <openssl/ripemd.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
std::string strprintf(const char* format, ...);
|
||||
std::string FormatFullVersion();
|
||||
void RandAddSeedPerfmon();
|
||||
|
||||
static const unsigned int MAX_SIZE = 0x02000000;
|
||||
|
||||
#define loop for (;;)
|
||||
#define PAIR(t1, t2) pair<t1, t2>
|
||||
|
||||
#if !defined(WIN32) && !defined(WIN64)
|
||||
#define _vsnprintf(a,b,c,d) vsnprintf(a,b,c,d)
|
||||
#endif
|
||||
|
||||
|
||||
template<typename T1>
|
||||
inline uint256 SHA256Hash(const T1 pbegin, const T1 pend)
|
||||
{
|
||||
static unsigned char pblank[1];
|
||||
uint256 hash1;
|
||||
SHA256((pbegin == pend ? pblank : (unsigned char*)&pbegin[0]), (pend - pbegin) * sizeof(pbegin[0]), (unsigned char*)&hash1);
|
||||
uint256 hash2;
|
||||
SHA256((unsigned char*)&hash1, sizeof(hash1), (unsigned char*)&hash2);
|
||||
return hash2;
|
||||
}
|
||||
|
||||
template<typename T1, typename T2>
|
||||
inline uint256 SHA256Hash(const T1 p1begin, const T1 p1end,
|
||||
const T2 p2begin, const T2 p2end)
|
||||
{
|
||||
static unsigned char pblank[1];
|
||||
uint256 hash1;
|
||||
SHA256_CTX ctx;
|
||||
SHA256_Init(&ctx);
|
||||
SHA256_Update(&ctx, (p1begin == p1end ? pblank : (unsigned char*)&p1begin[0]), (p1end - p1begin) * sizeof(p1begin[0]));
|
||||
SHA256_Update(&ctx, (p2begin == p2end ? pblank : (unsigned char*)&p2begin[0]), (p2end - p2begin) * sizeof(p2begin[0]));
|
||||
SHA256_Final((unsigned char*)&hash1, &ctx);
|
||||
uint256 hash2;
|
||||
SHA256((unsigned char*)&hash1, sizeof(hash1), (unsigned char*)&hash2);
|
||||
return hash2;
|
||||
}
|
||||
|
||||
template<typename T1, typename T2, typename T3>
|
||||
inline uint256 SHA256Hash(const T1 p1begin, const T1 p1end,
|
||||
const T2 p2begin, const T2 p2end,
|
||||
const T3 p3begin, const T3 p3end)
|
||||
{
|
||||
static unsigned char pblank[1];
|
||||
uint256 hash1;
|
||||
SHA256_CTX ctx;
|
||||
SHA256_Init(&ctx);
|
||||
SHA256_Update(&ctx, (p1begin == p1end ? pblank : (unsigned char*)&p1begin[0]), (p1end - p1begin) * sizeof(p1begin[0]));
|
||||
SHA256_Update(&ctx, (p2begin == p2end ? pblank : (unsigned char*)&p2begin[0]), (p2end - p2begin) * sizeof(p2begin[0]));
|
||||
SHA256_Update(&ctx, (p3begin == p3end ? pblank : (unsigned char*)&p3begin[0]), (p3end - p3begin) * sizeof(p3begin[0]));
|
||||
SHA256_Final((unsigned char*)&hash1, &ctx);
|
||||
uint256 hash2;
|
||||
SHA256((unsigned char*)&hash1, sizeof(hash1), (unsigned char*)&hash2);
|
||||
return hash2;
|
||||
}
|
||||
|
||||
inline uint160 Hash160(const std::vector<unsigned char>& vch)
|
||||
{
|
||||
uint256 hash1;
|
||||
SHA256(&vch[0], vch.size(), (unsigned char*)&hash1);
|
||||
uint160 hash2;
|
||||
RIPEMD160((unsigned char*)&hash1, sizeof(hash1), (unsigned char*)&hash2);
|
||||
return hash2;
|
||||
}
|
||||
|
||||
/*
|
||||
#ifdef WIN32
|
||||
// This is used to attempt to keep keying material out of swap
|
||||
// Note that VirtualLock does not provide this as a guarantee on Windows,
|
||||
// but, in practice, memory that has been VirtualLock'd almost never gets written to
|
||||
// the pagefile except in rare circumstances where memory is extremely low.
|
||||
#include <windows.h>
|
||||
#define mlock(p, n) VirtualLock((p), (n));
|
||||
#define munlock(p, n) VirtualUnlock((p), (n));
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#include <limits.h>
|
||||
// This comes from limits.h if it's not defined there set a sane default
|
||||
#ifndef PAGESIZE
|
||||
#include <unistd.h>
|
||||
#define PAGESIZE sysconf(_SC_PAGESIZE)
|
||||
#endif
|
||||
#define mlock(a,b) \
|
||||
mlock(((void *)(((size_t)(a)) & (~((PAGESIZE)-1)))),\
|
||||
(((((size_t)(a)) + (b) - 1) | ((PAGESIZE) - 1)) + 1) - (((size_t)(a)) & (~((PAGESIZE) - 1))))
|
||||
#define munlock(a,b) \
|
||||
munlock(((void *)(((size_t)(a)) & (~((PAGESIZE)-1)))),\
|
||||
(((((size_t)(a)) + (b) - 1) | ((PAGESIZE) - 1)) + 1) - (((size_t)(a)) & (~((PAGESIZE) - 1))))
|
||||
#endif
|
||||
*/
|
||||
|
||||
#endif
|
||||
160
src/cpp/ripple/CallRPC.cpp
Normal file
160
src/cpp/ripple/CallRPC.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/iostreams/concepts.hpp>
|
||||
#include <boost/iostreams/stream.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <openssl/buffer.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
#include "../json/value.h"
|
||||
#include "../json/reader.h"
|
||||
|
||||
#include "CallRPC.h"
|
||||
#include "RPC.h"
|
||||
#include "Config.h"
|
||||
#include "BitcoinUtil.h"
|
||||
|
||||
static inline bool isSwitchChar(char c)
|
||||
{
|
||||
#ifdef __WXMSW__
|
||||
return c == '-' || c == '/';
|
||||
#else
|
||||
return c == '-';
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string EncodeBase64(const std::string& s)
|
||||
{ // FIXME: This performs terribly
|
||||
BIO *b64, *bmem;
|
||||
BUF_MEM *bptr;
|
||||
|
||||
b64 = BIO_new(BIO_f_base64());
|
||||
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
||||
bmem = BIO_new(BIO_s_mem());
|
||||
b64 = BIO_push(b64, bmem);
|
||||
BIO_write(b64, s.data(), s.size());
|
||||
(void) BIO_flush(b64);
|
||||
BIO_get_mem_ptr(b64, &bptr);
|
||||
|
||||
std::string result(bptr->data, bptr->length);
|
||||
BIO_free_all(b64);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int commandLineRPC(const std::vector<std::string>& vCmd)
|
||||
{
|
||||
std::string strPrint;
|
||||
int nRet = 0;
|
||||
try
|
||||
{
|
||||
if (vCmd.empty()) return 1;
|
||||
|
||||
std::string strMethod = vCmd[0];
|
||||
|
||||
// Parameters default to strings
|
||||
Json::Value params(Json::arrayValue);
|
||||
for (int i = 1; i != vCmd.size(); i++)
|
||||
params.append(vCmd[i]);
|
||||
|
||||
// Execute
|
||||
Json::Value reply = callRPC(strMethod, params);
|
||||
|
||||
// Parse reply
|
||||
Json::Value result = reply.get("result", Json::Value());
|
||||
Json::Value error = reply.get("error", Json::Value());
|
||||
|
||||
if (result.isString() && (result.asString() == "unknown command"))
|
||||
nRet=1;
|
||||
|
||||
if (!error.isNull())
|
||||
{ // Error
|
||||
strPrint = "error: " + error.toStyledString();
|
||||
int code = error["code"].asInt();
|
||||
nRet = abs(code);
|
||||
}
|
||||
else
|
||||
{ // Result
|
||||
if (result.isNull())
|
||||
strPrint = "";
|
||||
else if (result.isString())
|
||||
strPrint = result.asString();
|
||||
else
|
||||
strPrint = result.toStyledString();
|
||||
}
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
strPrint = std::string("error: ") + e.what();
|
||||
nRet = 87;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
std::cout << "Exception CommandLineRPC()" << std::endl;
|
||||
}
|
||||
|
||||
if (strPrint != "")
|
||||
{
|
||||
std::cout << strPrint << std::endl;
|
||||
}
|
||||
return nRet;
|
||||
}
|
||||
|
||||
|
||||
Json::Value callRPC(const std::string& strMethod, const Json::Value& params)
|
||||
{
|
||||
if (theConfig.RPC_USER.empty() && theConfig.RPC_PASSWORD.empty())
|
||||
throw std::runtime_error("You must set rpcpassword=<password> in the configuration file"
|
||||
"If the file does not exist, create it with owner-readable-only file permissions.");
|
||||
|
||||
// Connect to localhost
|
||||
std::cout << "Connecting to: " << theConfig.RPC_IP << ":" << theConfig.RPC_PORT << std::endl;
|
||||
|
||||
boost::asio::ip::tcp::endpoint
|
||||
endpoint(boost::asio::ip::address::from_string(theConfig.RPC_IP), theConfig.RPC_PORT);
|
||||
boost::asio::ip::tcp::iostream stream;
|
||||
stream.connect(endpoint);
|
||||
if (stream.fail())
|
||||
throw std::runtime_error("couldn't connect to server");
|
||||
|
||||
// HTTP basic authentication
|
||||
std::string strUserPass64 = EncodeBase64(theConfig.RPC_USER + ":" + theConfig.RPC_PASSWORD);
|
||||
std::map<std::string, std::string> mapRequestHeaders;
|
||||
mapRequestHeaders["Authorization"] = std::string("Basic ") + strUserPass64;
|
||||
|
||||
// Send request
|
||||
std::string strRequest = JSONRPCRequest(strMethod, params, Json::Value(1));
|
||||
std::cout << "send request " << strMethod << " : " << strRequest << std::endl;
|
||||
std::string strPost = createHTTPPost(strRequest, mapRequestHeaders);
|
||||
stream << strPost << std::flush;
|
||||
|
||||
// std::cout << "post " << strPost << std::endl;
|
||||
|
||||
// Receive reply
|
||||
std::map<std::string, std::string> mapHeaders;
|
||||
std::string strReply;
|
||||
int nStatus = ReadHTTP(stream, mapHeaders, strReply);
|
||||
if (nStatus == 401)
|
||||
throw std::runtime_error("incorrect rpcuser or rpcpassword (authorization failed)");
|
||||
else if ((nStatus >= 400) && (nStatus != 400) && (nStatus != 404) && (nStatus != 500)) // ?
|
||||
throw std::runtime_error(strprintf("server returned HTTP error %d", nStatus));
|
||||
else if (strReply.empty())
|
||||
throw std::runtime_error("no response from server");
|
||||
|
||||
// Parse reply
|
||||
std::cout << "RPC reply: " << strReply << std::endl;
|
||||
Json::Reader reader;
|
||||
Json::Value valReply;
|
||||
if (!reader.parse(strReply, valReply))
|
||||
throw std::runtime_error("couldn't parse reply from server");
|
||||
if (valReply.isNull())
|
||||
throw std::runtime_error("expected reply to have result, error and id properties");
|
||||
|
||||
return valReply;
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
7
src/cpp/ripple/CallRPC.h
Normal file
7
src/cpp/ripple/CallRPC.h
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "../json/value.h"
|
||||
|
||||
extern int commandLineRPC(const std::vector<std::string>& vCmd);
|
||||
extern Json::Value callRPC(const std::string& strMethod, const Json::Value& params);
|
||||
55
src/cpp/ripple/CanonicalTXSet.cpp
Normal file
55
src/cpp/ripple/CanonicalTXSet.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
|
||||
#include "CanonicalTXSet.h"
|
||||
|
||||
bool CanonicalTXKey::operator<(const CanonicalTXKey& key) const
|
||||
{
|
||||
if (mAccount < key.mAccount) return true;
|
||||
if (mAccount > key.mAccount) return false;
|
||||
if (mSeq < key.mSeq) return true;
|
||||
if (mSeq > key.mSeq) return false;
|
||||
return mTXid < key.mTXid;
|
||||
}
|
||||
|
||||
bool CanonicalTXKey::operator>(const CanonicalTXKey& key) const
|
||||
{
|
||||
if (mAccount > key.mAccount) return true;
|
||||
if (mAccount < key.mAccount) return false;
|
||||
if (mSeq > key.mSeq) return true;
|
||||
if (mSeq < key.mSeq) return false;
|
||||
return mTXid > key.mTXid;
|
||||
}
|
||||
|
||||
bool CanonicalTXKey::operator<=(const CanonicalTXKey& key) const
|
||||
{
|
||||
if (mAccount < key.mAccount) return true;
|
||||
if (mAccount > key.mAccount) return false;
|
||||
if (mSeq < key.mSeq) return true;
|
||||
if (mSeq > key.mSeq) return false;
|
||||
return mTXid <= key.mTXid;
|
||||
}
|
||||
|
||||
bool CanonicalTXKey::operator>=(const CanonicalTXKey& key)const
|
||||
{
|
||||
if (mAccount > key.mAccount) return true;
|
||||
if (mAccount < key.mAccount) return false;
|
||||
if (mSeq > key.mSeq) return true;
|
||||
if (mSeq < key.mSeq) return false;
|
||||
return mTXid >= key.mTXid;
|
||||
}
|
||||
|
||||
void CanonicalTXSet::push_back(SerializedTransaction::ref txn)
|
||||
{
|
||||
uint256 effectiveAccount = mSetHash;
|
||||
effectiveAccount ^= txn->getSourceAccount().getAccountID().to256();
|
||||
mMap.insert(std::make_pair(CanonicalTXKey(effectiveAccount, txn->getSequence(), txn->getTransactionID()), txn));
|
||||
}
|
||||
|
||||
CanonicalTXSet::iterator CanonicalTXSet::erase(const iterator& it)
|
||||
{
|
||||
iterator tmp = it;
|
||||
++tmp;
|
||||
mMap.erase(it);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
|
||||
59
src/cpp/ripple/CanonicalTXSet.h
Normal file
59
src/cpp/ripple/CanonicalTXSet.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#ifndef __CANONICAL_TX_SET_
|
||||
#define __CANONICAL_TX_SET_
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "uint256.h"
|
||||
#include "SerializedTransaction.h"
|
||||
|
||||
class CanonicalTXKey
|
||||
{
|
||||
protected:
|
||||
uint256 mAccount, mTXid;
|
||||
uint32 mSeq;
|
||||
|
||||
public:
|
||||
CanonicalTXKey(const uint256& account, uint32 seq, const uint256& id)
|
||||
: mAccount(account), mTXid(id), mSeq(seq) { ; }
|
||||
|
||||
bool operator<(const CanonicalTXKey&) const;
|
||||
bool operator>(const CanonicalTXKey&) const;
|
||||
bool operator<=(const CanonicalTXKey&) const;
|
||||
bool operator>=(const CanonicalTXKey&) const;
|
||||
|
||||
bool operator==(const CanonicalTXKey& k) const { return mTXid == k.mTXid; }
|
||||
bool operator!=(const CanonicalTXKey& k) const { return mTXid != k.mTXid; }
|
||||
};
|
||||
|
||||
class CanonicalTXSet
|
||||
{
|
||||
public:
|
||||
typedef std::map<CanonicalTXKey, SerializedTransaction::pointer>::iterator iterator;
|
||||
typedef std::map<CanonicalTXKey, SerializedTransaction::pointer>::const_iterator const_iterator;
|
||||
|
||||
protected:
|
||||
uint256 mSetHash;
|
||||
std::map<CanonicalTXKey, SerializedTransaction::pointer> mMap;
|
||||
|
||||
public:
|
||||
CanonicalTXSet(const uint256& lclHash) : mSetHash(lclHash) { ; }
|
||||
|
||||
void push_back(SerializedTransaction::ref txn);
|
||||
|
||||
void reset(const uint256& newLCL)
|
||||
{
|
||||
mSetHash = newLCL;
|
||||
mMap.clear();
|
||||
}
|
||||
|
||||
iterator erase(const iterator& it);
|
||||
|
||||
iterator begin() { return mMap.begin(); }
|
||||
iterator end() { return mMap.end(); }
|
||||
const_iterator begin() const { return mMap.begin(); }
|
||||
const_iterator end() const { return mMap.end(); }
|
||||
size_t size() const { return mMap.size(); }
|
||||
bool empty() const { return mMap.empty(); }
|
||||
};
|
||||
|
||||
#endif
|
||||
304
src/cpp/ripple/Config.cpp
Normal file
304
src/cpp/ripple/Config.cpp
Normal file
@@ -0,0 +1,304 @@
|
||||
#include "Config.h"
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
#define SECTION_ACCOUNT_PROBE_MAX "account_probe_max"
|
||||
#define SECTION_DEBUG_LOGFILE "debug_logfile"
|
||||
#define SECTION_FEE_ACCOUNT_CREATE "fee_account_create"
|
||||
#define SECTION_FEE_DEFAULT "fee_default"
|
||||
#define SECTION_FEE_NICKNAME_CREATE "fee_nickname_create"
|
||||
#define SECTION_FEE_OFFER "fee_offer"
|
||||
#define SECTION_FEE_OPERATION "fee_operation"
|
||||
#define SECTION_FULL_HISTORY "full_history"
|
||||
#define SECTION_IPS "ips"
|
||||
#define SECTION_NETWORK_QUORUM "network_quorum"
|
||||
#define SECTION_PEER_CONNECT_LOW_WATER "peer_connect_low_water"
|
||||
#define SECTION_PEER_IP "peer_ip"
|
||||
#define SECTION_PEER_PORT "peer_port"
|
||||
#define SECTION_PEER_SCAN_INTERVAL_MIN "peer_scan_interval_min"
|
||||
#define SECTION_PEER_SSL_CIPHER_LIST "peer_ssl_cipher_list"
|
||||
#define SECTION_PEER_START_MAX "peer_start_max"
|
||||
#define SECTION_RPC_ALLOW_REMOTE "rpc_allow_remote"
|
||||
#define SECTION_RPC_IP "rpc_ip"
|
||||
#define SECTION_RPC_PORT "rpc_port"
|
||||
#define SECTION_SNTP "sntp_servers"
|
||||
#define SECTION_UNL_DEFAULT "unl_default"
|
||||
#define SECTION_VALIDATION_QUORUM "validation_quorum"
|
||||
#define SECTION_VALIDATION_SEED "validation_seed"
|
||||
#define SECTION_WEBSOCKET_PUBLIC_IP "websocket_public_ip"
|
||||
#define SECTION_WEBSOCKET_PUBLIC_PORT "websocket_public_port"
|
||||
#define SECTION_WEBSOCKET_IP "websocket_ip"
|
||||
#define SECTION_WEBSOCKET_PORT "websocket_port"
|
||||
#define SECTION_VALIDATORS "validators"
|
||||
#define SECTION_VALIDATORS_SITE "validators_site"
|
||||
|
||||
// Fees are in XRP.
|
||||
#define DEFAULT_FEE_DEFAULT 10
|
||||
#define DEFAULT_FEE_ACCOUNT_CREATE 1000*SYSTEM_CURRENCY_PARTS
|
||||
#define DEFAULT_FEE_NICKNAME_CREATE 1000
|
||||
#define DEFAULT_FEE_OFFER DEFAULT_FEE_DEFAULT
|
||||
#define DEFAULT_FEE_OPERATION 1
|
||||
|
||||
Config theConfig;
|
||||
|
||||
void Config::setup(const std::string& strConf)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
|
||||
//
|
||||
// Determine the config and data directories.
|
||||
// If the config file is found in the current working directory, use the current working directory as the config directory and
|
||||
// that with "db" as the data directory.
|
||||
//
|
||||
|
||||
if (!strConf.empty())
|
||||
{
|
||||
// --conf=<path> : everything is relative that file.
|
||||
CONFIG_FILE = strConf;
|
||||
CONFIG_DIR = CONFIG_FILE;
|
||||
CONFIG_DIR.remove_filename();
|
||||
DATA_DIR = CONFIG_DIR / "db";
|
||||
}
|
||||
else
|
||||
{
|
||||
CONFIG_DIR = boost::filesystem::current_path();
|
||||
CONFIG_FILE = CONFIG_DIR / CONFIG_FILE_NAME;
|
||||
DATA_DIR = CONFIG_DIR / "db";
|
||||
|
||||
if (exists(CONFIG_FILE)
|
||||
// Can we figure out XDG dirs?
|
||||
|| (!getenv("HOME") && (!getenv("XDG_CONFIG_HOME") || !getenv("XDG_DATA_HOME"))))
|
||||
{
|
||||
// Current working directory is fine, put dbs in a subdir.
|
||||
nothing();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Construct XDG config and data home.
|
||||
// http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
||||
std::string strHome = strGetEnv("HOME");
|
||||
std::string strXdgConfigHome = strGetEnv("XDG_CONFIG_HOME");
|
||||
std::string strXdgDataHome = strGetEnv("XDG_DATA_HOME");
|
||||
|
||||
if (strXdgConfigHome.empty())
|
||||
{
|
||||
// $XDG_CONFIG_HOME was not set, use default based on $HOME.
|
||||
strXdgConfigHome = str(boost::format("%s/.config") % strHome);
|
||||
}
|
||||
|
||||
if (strXdgDataHome.empty())
|
||||
{
|
||||
// $XDG_DATA_HOME was not set, use default based on $HOME.
|
||||
strXdgDataHome = str(boost::format("%s/.local/share") % strHome);
|
||||
}
|
||||
|
||||
CONFIG_DIR = str(boost::format("%s/" SYSTEM_NAME) % strXdgConfigHome);
|
||||
CONFIG_FILE = CONFIG_DIR / CONFIG_FILE_NAME;
|
||||
DATA_DIR = str(boost::format("%s/" SYSTEM_NAME) % strXdgDataHome);
|
||||
|
||||
boost::filesystem::create_directories(CONFIG_DIR, ec);
|
||||
|
||||
if (ec)
|
||||
throw std::runtime_error(str(boost::format("Can not create %s") % CONFIG_DIR));
|
||||
}
|
||||
}
|
||||
|
||||
boost::filesystem::create_directories(DATA_DIR, ec);
|
||||
|
||||
if (ec)
|
||||
throw std::runtime_error(str(boost::format("Can not create %s") % DATA_DIR));
|
||||
|
||||
// std::cerr << "CONFIG FILE: " << CONFIG_FILE << std::endl;
|
||||
// std::cerr << "CONFIG DIR: " << CONFIG_DIR << std::endl;
|
||||
// std::cerr << "DATA DIR: " << DATA_DIR << std::endl;
|
||||
|
||||
//
|
||||
// Defaults
|
||||
//
|
||||
|
||||
NETWORK_START_TIME = 1319844908;
|
||||
|
||||
PEER_PORT = SYSTEM_PEER_PORT;
|
||||
RPC_PORT = 5001;
|
||||
WEBSOCKET_PORT = SYSTEM_WEBSOCKET_PORT;
|
||||
WEBSOCKET_PUBLIC_PORT = SYSTEM_WEBSOCKET_PUBLIC_PORT;
|
||||
NUMBER_CONNECTIONS = 30;
|
||||
|
||||
// a new ledger every minute
|
||||
LEDGER_SECONDS = 60;
|
||||
LEDGER_CREATOR = false;
|
||||
|
||||
RPC_USER = "admin";
|
||||
RPC_PASSWORD = "pass";
|
||||
RPC_ALLOW_REMOTE = false;
|
||||
|
||||
PEER_SSL_CIPHER_LIST = DEFAULT_PEER_SSL_CIPHER_LIST;
|
||||
PEER_SCAN_INTERVAL_MIN = DEFAULT_PEER_SCAN_INTERVAL_MIN;
|
||||
|
||||
PEER_START_MAX = DEFAULT_PEER_START_MAX;
|
||||
PEER_CONNECT_LOW_WATER = DEFAULT_PEER_CONNECT_LOW_WATER;
|
||||
|
||||
TRANSACTION_FEE_BASE = 1000;
|
||||
|
||||
NETWORK_QUORUM = 0; // Don't need to see other nodes
|
||||
VALIDATION_QUORUM = 1; // Only need one node to vouch
|
||||
|
||||
FEE_ACCOUNT_CREATE = DEFAULT_FEE_ACCOUNT_CREATE;
|
||||
FEE_NICKNAME_CREATE = DEFAULT_FEE_NICKNAME_CREATE;
|
||||
FEE_OFFER = DEFAULT_FEE_OFFER;
|
||||
FEE_DEFAULT = DEFAULT_FEE_DEFAULT;
|
||||
FEE_CONTRACT_OPERATION = DEFAULT_FEE_OPERATION;
|
||||
|
||||
FULL_HISTORY = false;
|
||||
|
||||
ACCOUNT_PROBE_MAX = 10;
|
||||
|
||||
VALIDATORS_SITE = DEFAULT_VALIDATORS_SITE;
|
||||
|
||||
RUN_STANDALONE = false;
|
||||
START_UP = NORMAL;
|
||||
|
||||
load();
|
||||
}
|
||||
|
||||
void Config::load()
|
||||
{
|
||||
std::cout << "Loading: " << CONFIG_FILE << std::endl;
|
||||
|
||||
std::ifstream ifsConfig(CONFIG_FILE.c_str(), std::ios::in);
|
||||
|
||||
if (!ifsConfig)
|
||||
{
|
||||
std::cerr << "Failed to open '" << CONFIG_FILE << "'." << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string strConfigFile;
|
||||
|
||||
strConfigFile.assign((std::istreambuf_iterator<char>(ifsConfig)),
|
||||
std::istreambuf_iterator<char>());
|
||||
|
||||
if (ifsConfig.bad())
|
||||
{
|
||||
std::cerr << "Failed to read '" << CONFIG_FILE << "'." << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
section secConfig = ParseSection(strConfigFile, true);
|
||||
std::string strTemp;
|
||||
|
||||
// XXX Leak
|
||||
section::mapped_type* smtTmp;
|
||||
|
||||
smtTmp = sectionEntries(secConfig, SECTION_VALIDATORS);
|
||||
if (smtTmp)
|
||||
{
|
||||
VALIDATORS = *smtTmp;
|
||||
// sectionEntriesPrint(&VALIDATORS, SECTION_VALIDATORS);
|
||||
}
|
||||
|
||||
smtTmp = sectionEntries(secConfig, SECTION_IPS);
|
||||
if (smtTmp)
|
||||
{
|
||||
IPS = *smtTmp;
|
||||
// sectionEntriesPrint(&IPS, SECTION_IPS);
|
||||
}
|
||||
|
||||
smtTmp = sectionEntries(secConfig, SECTION_SNTP);
|
||||
if (smtTmp)
|
||||
{
|
||||
SNTP_SERVERS = *smtTmp;
|
||||
}
|
||||
|
||||
(void) sectionSingleB(secConfig, SECTION_VALIDATORS_SITE, VALIDATORS_SITE);
|
||||
|
||||
(void) sectionSingleB(secConfig, SECTION_PEER_IP, PEER_IP);
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_PEER_PORT, strTemp))
|
||||
PEER_PORT = boost::lexical_cast<int>(strTemp);
|
||||
|
||||
(void) sectionSingleB(secConfig, SECTION_RPC_IP, RPC_IP);
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_RPC_PORT, strTemp))
|
||||
RPC_PORT = boost::lexical_cast<int>(strTemp);
|
||||
|
||||
if (sectionSingleB(secConfig, "ledger_creator" , strTemp))
|
||||
LEDGER_CREATOR = boost::lexical_cast<bool>(strTemp);
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_RPC_ALLOW_REMOTE, strTemp))
|
||||
RPC_ALLOW_REMOTE = boost::lexical_cast<bool>(strTemp);
|
||||
|
||||
(void) sectionSingleB(secConfig, SECTION_WEBSOCKET_IP, WEBSOCKET_IP);
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_WEBSOCKET_PORT, strTemp))
|
||||
WEBSOCKET_PORT = boost::lexical_cast<int>(strTemp);
|
||||
|
||||
(void) sectionSingleB(secConfig, SECTION_WEBSOCKET_PUBLIC_IP, WEBSOCKET_PUBLIC_IP);
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_WEBSOCKET_PUBLIC_PORT, strTemp))
|
||||
WEBSOCKET_PUBLIC_PORT = boost::lexical_cast<int>(strTemp);
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_VALIDATION_SEED, strTemp))
|
||||
{
|
||||
VALIDATION_SEED.setSeedGeneric(strTemp);
|
||||
if (VALIDATION_SEED.isValid())
|
||||
{
|
||||
VALIDATION_PUB = RippleAddress::createNodePublic(VALIDATION_SEED);
|
||||
VALIDATION_PRIV = RippleAddress::createNodePrivate(VALIDATION_SEED);
|
||||
}
|
||||
}
|
||||
|
||||
(void) sectionSingleB(secConfig, SECTION_PEER_SSL_CIPHER_LIST, PEER_SSL_CIPHER_LIST);
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_PEER_SCAN_INTERVAL_MIN, strTemp))
|
||||
// Minimum for min is 60 seconds.
|
||||
PEER_SCAN_INTERVAL_MIN = std::max(60, boost::lexical_cast<int>(strTemp));
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_PEER_START_MAX, strTemp))
|
||||
PEER_START_MAX = std::max(1, boost::lexical_cast<int>(strTemp));
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_PEER_CONNECT_LOW_WATER, strTemp))
|
||||
PEER_CONNECT_LOW_WATER = std::max(1, boost::lexical_cast<int>(strTemp));
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_NETWORK_QUORUM, strTemp))
|
||||
NETWORK_QUORUM = std::max(0, boost::lexical_cast<int>(strTemp));
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_VALIDATION_QUORUM, strTemp))
|
||||
VALIDATION_QUORUM = std::max(0, boost::lexical_cast<int>(strTemp));
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_FEE_ACCOUNT_CREATE, strTemp))
|
||||
FEE_ACCOUNT_CREATE = boost::lexical_cast<int>(strTemp);
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_FEE_NICKNAME_CREATE, strTemp))
|
||||
FEE_NICKNAME_CREATE = boost::lexical_cast<int>(strTemp);
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_FEE_OFFER, strTemp))
|
||||
FEE_OFFER = boost::lexical_cast<int>(strTemp);
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_FEE_DEFAULT, strTemp))
|
||||
FEE_DEFAULT = boost::lexical_cast<int>(strTemp);
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_FEE_OPERATION, strTemp))
|
||||
FEE_CONTRACT_OPERATION = boost::lexical_cast<int>(strTemp);
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_FULL_HISTORY, strTemp))
|
||||
FULL_HISTORY = boost::lexical_cast<bool>(strTemp);
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_ACCOUNT_PROBE_MAX, strTemp))
|
||||
ACCOUNT_PROBE_MAX = boost::lexical_cast<int>(strTemp);
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_UNL_DEFAULT, strTemp))
|
||||
UNL_DEFAULT = strTemp;
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_DEBUG_LOGFILE, strTemp))
|
||||
DEBUG_LOGFILE = strTemp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
122
src/cpp/ripple/Config.h
Normal file
122
src/cpp/ripple/Config.h
Normal file
@@ -0,0 +1,122 @@
|
||||
#ifndef __CONFIG__
|
||||
#define __CONFIG__
|
||||
|
||||
#include "types.h"
|
||||
#include "RippleAddress.h"
|
||||
#include "ParseSection.h"
|
||||
#include "SerializedTypes.h"
|
||||
|
||||
#include <string>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#define SYSTEM_NAME "ripple"
|
||||
#define SYSTEM_CURRENCY_CODE "XRP"
|
||||
#define SYSTEM_CURRENCY_PRECISION 6
|
||||
#define SYSTEM_CURRENCY_CODE_RIPPLE "XRR"
|
||||
|
||||
#define SYSTEM_CURRENCY_GIFT 1000ull
|
||||
#define SYSTEM_CURRENCY_USERS 100000000ull
|
||||
#define SYSTEM_CURRENCY_PARTS 1000000ull // 10^SYSTEM_CURRENCY_PRECISION
|
||||
#define SYSTEM_CURRENCY_START (SYSTEM_CURRENCY_GIFT*SYSTEM_CURRENCY_USERS*SYSTEM_CURRENCY_PARTS)
|
||||
|
||||
#define CONFIG_FILE_NAME SYSTEM_NAME "d.cfg" // rippled.cfg
|
||||
|
||||
#define DEFAULT_VALIDATORS_SITE "redstem.com"
|
||||
#define VALIDATORS_FILE_NAME "validators.txt"
|
||||
|
||||
const int SYSTEM_PEER_PORT = 6561;
|
||||
const int SYSTEM_WEBSOCKET_PORT = 6562;
|
||||
const int SYSTEM_WEBSOCKET_PUBLIC_PORT = 6563; // XXX Going away.
|
||||
|
||||
// Allow anonymous DH.
|
||||
#define DEFAULT_PEER_SSL_CIPHER_LIST "ALL:!LOW:!EXP:!MD5:@STRENGTH"
|
||||
|
||||
// Normal, recommend 1 hour.
|
||||
// #define DEFAULT_PEER_SCAN_INTERVAL_MIN (60*60)
|
||||
// Testing, recommend 1 minute.
|
||||
#define DEFAULT_PEER_SCAN_INTERVAL_MIN (60)
|
||||
|
||||
// Maximum number of peers to try to connect to as client at once.
|
||||
#define DEFAULT_PEER_START_MAX 5
|
||||
|
||||
// Might connect with fewer for testing.
|
||||
#define DEFAULT_PEER_CONNECT_LOW_WATER 4
|
||||
|
||||
class Config
|
||||
{
|
||||
public:
|
||||
// Configuration parameters
|
||||
boost::filesystem::path CONFIG_FILE;
|
||||
boost::filesystem::path CONFIG_DIR;
|
||||
boost::filesystem::path DATA_DIR;
|
||||
boost::filesystem::path DEBUG_LOGFILE;
|
||||
boost::filesystem::path UNL_DEFAULT;
|
||||
|
||||
std::string VALIDATORS_SITE; // Where to find validators.txt on the Internet.
|
||||
std::vector<std::string> VALIDATORS; // Validators from rippled.cfg.
|
||||
std::vector<std::string> IPS; // Peer IPs from rippled.cfg.
|
||||
std::vector<std::string> SNTP_SERVERS; // SNTP servers from rippled.cfg.
|
||||
|
||||
enum StartUpType { FRESH, NORMAL, LOAD, NETWORK };
|
||||
StartUpType START_UP;
|
||||
|
||||
// Network parameters
|
||||
int NETWORK_START_TIME; // The Unix time we start ledger 0.
|
||||
int TRANSACTION_FEE_BASE;
|
||||
int LEDGER_SECONDS;
|
||||
int LEDGER_PROPOSAL_DELAY_SECONDS;
|
||||
int LEDGER_AVALANCHE_SECONDS;
|
||||
bool LEDGER_CREATOR; // should be false unless we are starting a new ledger
|
||||
bool RUN_STANDALONE;
|
||||
|
||||
// Note: The following parameters do not relate to the UNL or trust at all
|
||||
unsigned int NETWORK_QUORUM; // Minimum number of nodes to consider the network present
|
||||
int VALIDATION_QUORUM; // Minimum validations to consider ledger authoritative
|
||||
|
||||
// Peer networking parameters
|
||||
std::string PEER_IP;
|
||||
int PEER_PORT;
|
||||
int NUMBER_CONNECTIONS;
|
||||
std::string PEER_SSL_CIPHER_LIST;
|
||||
int PEER_SCAN_INTERVAL_MIN;
|
||||
int PEER_START_MAX;
|
||||
unsigned int PEER_CONNECT_LOW_WATER;
|
||||
|
||||
// Websocket networking parameters
|
||||
std::string WEBSOCKET_PUBLIC_IP; // XXX Going away. Merge with the inbound peer connction.
|
||||
int WEBSOCKET_PUBLIC_PORT;
|
||||
|
||||
std::string WEBSOCKET_IP;
|
||||
int WEBSOCKET_PORT;
|
||||
|
||||
// RPC parameters
|
||||
std::string RPC_IP;
|
||||
int RPC_PORT;
|
||||
std::string RPC_USER;
|
||||
std::string RPC_PASSWORD;
|
||||
bool RPC_ALLOW_REMOTE;
|
||||
|
||||
// Validation
|
||||
RippleAddress VALIDATION_SEED, VALIDATION_PUB, VALIDATION_PRIV;
|
||||
|
||||
// Fees
|
||||
uint64 FEE_DEFAULT; // Default fee.
|
||||
uint64 FEE_ACCOUNT_CREATE; // Fee to create an account.
|
||||
uint64 FEE_NICKNAME_CREATE; // Fee to create a nickname.
|
||||
uint64 FEE_OFFER; // Rate per day.
|
||||
int FEE_CONTRACT_OPERATION; // fee for each contract operation
|
||||
|
||||
// Node storage configuration
|
||||
bool FULL_HISTORY;
|
||||
|
||||
// Client behavior
|
||||
int ACCOUNT_PROBE_MAX; // How far to scan for accounts.
|
||||
|
||||
void setup(const std::string& strConf);
|
||||
void load();
|
||||
};
|
||||
|
||||
extern Config theConfig;
|
||||
#endif
|
||||
|
||||
// vim:ts=4
|
||||
764
src/cpp/ripple/ConnectionPool.cpp
Normal file
764
src/cpp/ripple/ConnectionPool.cpp
Normal file
@@ -0,0 +1,764 @@
|
||||
|
||||
#include "ConnectionPool.h"
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
#include "Config.h"
|
||||
#include "Peer.h"
|
||||
#include "Application.h"
|
||||
#include "utils.h"
|
||||
#include "Log.h"
|
||||
|
||||
SETUP_LOG();
|
||||
|
||||
// How often to enforce policies.
|
||||
#define POLICY_INTERVAL_SECONDS 5
|
||||
|
||||
void splitIpPort(const std::string& strIpPort, std::string& strIp, int& iPort)
|
||||
{
|
||||
std::vector<std::string> vIpPort;
|
||||
boost::split(vIpPort, strIpPort, boost::is_any_of(" "));
|
||||
|
||||
strIp = vIpPort[0];
|
||||
iPort = boost::lexical_cast<int>(vIpPort[1]);
|
||||
}
|
||||
|
||||
ConnectionPool::ConnectionPool(boost::asio::io_service& io_service) :
|
||||
mLastPeer(0),
|
||||
mCtx(boost::asio::ssl::context::sslv23),
|
||||
mScanTimer(io_service),
|
||||
mPolicyTimer(io_service)
|
||||
{
|
||||
mCtx.set_options(
|
||||
boost::asio::ssl::context::default_workarounds
|
||||
| boost::asio::ssl::context::no_sslv2
|
||||
| boost::asio::ssl::context::single_dh_use);
|
||||
|
||||
if (1 != SSL_CTX_set_cipher_list(mCtx.native_handle(), theConfig.PEER_SSL_CIPHER_LIST.c_str()))
|
||||
std::runtime_error("Error setting cipher list (no valid ciphers).");
|
||||
}
|
||||
|
||||
void ConnectionPool::start()
|
||||
{
|
||||
if (theConfig.RUN_STANDALONE)
|
||||
return;
|
||||
|
||||
// Start running policy.
|
||||
policyEnforce();
|
||||
|
||||
// Start scanning.
|
||||
scanRefresh();
|
||||
}
|
||||
|
||||
bool ConnectionPool::getTopNAddrs(int n,std::vector<std::string>& addrs)
|
||||
{
|
||||
// XXX Filter out other local addresses (like ipv6)
|
||||
Database* db = theApp->getWalletDB()->getDB();
|
||||
ScopedLock sl(theApp->getWalletDB()->getDBLock());
|
||||
|
||||
SQL_FOREACH(db, str(boost::format("SELECT IpPort FROM PeerIps LIMIT %d") % n) )
|
||||
{
|
||||
std::string str;
|
||||
|
||||
db->getStr(0,str);
|
||||
|
||||
addrs.push_back(str);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConnectionPool::savePeer(const std::string& strIp, int iPort, char code)
|
||||
{
|
||||
bool bNew = false;
|
||||
|
||||
Database* db = theApp->getWalletDB()->getDB();
|
||||
|
||||
std::string ipPort = sqlEscape(str(boost::format("%s %d") % strIp % iPort));
|
||||
|
||||
ScopedLock sl(theApp->getWalletDB()->getDBLock());
|
||||
std::string sql = str(boost::format("SELECT COUNT(*) FROM PeerIps WHERE IpPort=%s;") % ipPort);
|
||||
if (db->executeSQL(sql) && db->startIterRows())
|
||||
{
|
||||
if (!db->getInt(0))
|
||||
{
|
||||
db->executeSQL(str(boost::format("INSERT INTO PeerIps (IpPort,Score,Source) values (%s,0,'%c');") % ipPort % code));
|
||||
bNew = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We already had this peer.
|
||||
// We will eventually verify its address if it is possible.
|
||||
// YYY If it is vsInbound, then we might make verification immediate so we can connect back sooner if the connection
|
||||
// is lost.
|
||||
nothing();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "Error saving Peer" << std::endl;
|
||||
}
|
||||
|
||||
if (bNew)
|
||||
scanRefresh();
|
||||
|
||||
return bNew;
|
||||
}
|
||||
|
||||
Peer::pointer ConnectionPool::getPeerById(const uint64& id)
|
||||
{
|
||||
boost::mutex::scoped_lock sl(mPeerLock);
|
||||
const boost::unordered_map<uint64, Peer::pointer>::iterator& it = mPeerIdMap.find(id);
|
||||
if (it == mPeerIdMap.end())
|
||||
return Peer::pointer();
|
||||
return it->second;
|
||||
}
|
||||
|
||||
bool ConnectionPool::hasPeer(const uint64& id)
|
||||
{
|
||||
boost::mutex::scoped_lock sl(mPeerLock);
|
||||
return mPeerIdMap.find(id) != mPeerIdMap.end();
|
||||
}
|
||||
|
||||
// An available peer is one we had no trouble connect to last time and that we are not currently knowingly connected or connecting
|
||||
// too.
|
||||
//
|
||||
// <-- true, if a peer is available to connect to
|
||||
bool ConnectionPool::peerAvailable(std::string& strIp, int& iPort)
|
||||
{
|
||||
Database* db = theApp->getWalletDB()->getDB();
|
||||
std::vector<std::string> vstrIpPort;
|
||||
|
||||
// Convert mIpMap (list of open connections) to a vector of "<ip> <port>".
|
||||
{
|
||||
boost::mutex::scoped_lock sl(mPeerLock);
|
||||
|
||||
vstrIpPort.reserve(mIpMap.size());
|
||||
|
||||
BOOST_FOREACH(pipPeer ipPeer, mIpMap)
|
||||
{
|
||||
const std::string& strIp = ipPeer.first.first;
|
||||
int iPort = ipPeer.first.second;
|
||||
|
||||
vstrIpPort.push_back(sqlEscape(str(boost::format("%s %d") % strIp % iPort)));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the first IpPort entry which is not in vector and which is not scheduled for scanning.
|
||||
std::string strIpPort;
|
||||
|
||||
{
|
||||
ScopedLock sl(theApp->getWalletDB()->getDBLock());
|
||||
|
||||
if (db->executeSQL(str(boost::format("SELECT IpPort FROM PeerIps WHERE ScanNext IS NULL AND IpPort NOT IN (%s) LIMIT 1;")
|
||||
% strJoin(vstrIpPort.begin(), vstrIpPort.end(), ",")))
|
||||
&& db->startIterRows())
|
||||
{
|
||||
strIpPort = db->getStrBinary("IpPort");
|
||||
}
|
||||
}
|
||||
|
||||
bool bAvailable = !strIpPort.empty();
|
||||
|
||||
if (bAvailable)
|
||||
splitIpPort(strIpPort, strIp, iPort);
|
||||
|
||||
return bAvailable;
|
||||
}
|
||||
|
||||
// Make sure we have at least low water connections.
|
||||
void ConnectionPool::policyLowWater()
|
||||
{
|
||||
std::string strIp;
|
||||
int iPort;
|
||||
|
||||
// Find an entry to connect to.
|
||||
if (mConnectedMap.size() > theConfig.PEER_CONNECT_LOW_WATER)
|
||||
{
|
||||
// Above low water mark, don't need more connections.
|
||||
cLog(lsTRACE) << "Pool: Low water: sufficient connections: " << mConnectedMap.size() << "/" << theConfig.PEER_CONNECT_LOW_WATER;
|
||||
|
||||
nothing();
|
||||
}
|
||||
#if 0
|
||||
else if (miConnectStarting == theConfig.PEER_START_MAX)
|
||||
{
|
||||
// Too many connections starting to start another.
|
||||
nothing();
|
||||
}
|
||||
#endif
|
||||
else if (!peerAvailable(strIp, iPort))
|
||||
{
|
||||
// No more connections available to start.
|
||||
cLog(lsTRACE) << "Pool: Low water: no peers available.";
|
||||
|
||||
// XXX Might ask peers for more ips.
|
||||
nothing();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try to start connection.
|
||||
cLog(lsTRACE) << "Pool: Low water: start connection.";
|
||||
|
||||
if (!peerConnect(strIp, iPort))
|
||||
{
|
||||
cLog(lsINFO) << "Pool: Low water: already connected.";
|
||||
}
|
||||
|
||||
// Check if we need more.
|
||||
policyLowWater();
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionPool::policyEnforce()
|
||||
{
|
||||
// Cancel any in progress timer.
|
||||
(void) mPolicyTimer.cancel();
|
||||
|
||||
// Enforce policies.
|
||||
policyLowWater();
|
||||
|
||||
// Schedule next enforcement.
|
||||
mPolicyTimer.expires_at(boost::posix_time::second_clock::universal_time()+boost::posix_time::seconds(POLICY_INTERVAL_SECONDS));
|
||||
mPolicyTimer.async_wait(boost::bind(&ConnectionPool::policyHandler, this, _1));
|
||||
}
|
||||
|
||||
void ConnectionPool::policyHandler(const boost::system::error_code& ecResult)
|
||||
{
|
||||
if (ecResult == boost::asio::error::operation_aborted)
|
||||
{
|
||||
nothing();
|
||||
}
|
||||
else if (!ecResult)
|
||||
{
|
||||
policyEnforce();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Internal error: unexpected deadline error.");
|
||||
}
|
||||
}
|
||||
|
||||
// YYY: Should probably do this in the background.
|
||||
// YYY: Might end up sending to disconnected peer?
|
||||
int ConnectionPool::relayMessage(Peer* fromPeer, const PackedMessage::pointer& msg)
|
||||
{
|
||||
int sentTo = 0;
|
||||
boost::mutex::scoped_lock sl(mPeerLock);
|
||||
|
||||
BOOST_FOREACH(naPeer pair, mConnectedMap)
|
||||
{
|
||||
Peer::ref peer = pair.second;
|
||||
if (!peer)
|
||||
std::cerr << "CP::RM null peer in list" << std::endl;
|
||||
else if ((!fromPeer || !(peer.get() == fromPeer)) && peer->isConnected())
|
||||
{
|
||||
++sentTo;
|
||||
peer->sendPacket(msg);
|
||||
}
|
||||
}
|
||||
|
||||
return sentTo;
|
||||
}
|
||||
|
||||
void ConnectionPool::relayMessageBut(const std::set<uint64>& fromPeers, const PackedMessage::pointer& msg)
|
||||
{ // Relay message to all but the specified peers
|
||||
boost::mutex::scoped_lock sl(mPeerLock);
|
||||
|
||||
BOOST_FOREACH(naPeer pair, mConnectedMap)
|
||||
{
|
||||
Peer::ref peer = pair.second;
|
||||
if (peer->isConnected() && (fromPeers.count(peer->getPeerId()) == 0))
|
||||
peer->sendPacket(msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ConnectionPool::relayMessageTo(const std::set<uint64>& fromPeers, const PackedMessage::pointer& msg)
|
||||
{ // Relay message to the specified peers
|
||||
boost::mutex::scoped_lock sl(mPeerLock);
|
||||
|
||||
BOOST_FOREACH(const uint64& peerID, fromPeers)
|
||||
{
|
||||
const boost::unordered_map<uint64, Peer::pointer>::iterator& it = mPeerIdMap.find(peerID);
|
||||
if ((it != mPeerIdMap.end()) && it->second->isConnected())
|
||||
it->second->sendPacket(msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Schedule a connection via scanning.
|
||||
//
|
||||
// Add or modify into PeerIps as a manual entry for immediate scanning.
|
||||
// Requires sane IP and port.
|
||||
void ConnectionPool::connectTo(const std::string& strIp, int iPort)
|
||||
{
|
||||
if (theConfig.RUN_STANDALONE)
|
||||
return;
|
||||
{
|
||||
Database* db = theApp->getWalletDB()->getDB();
|
||||
ScopedLock sl(theApp->getWalletDB()->getDBLock());
|
||||
|
||||
db->executeSQL(str(boost::format("REPLACE INTO PeerIps (IpPort,Score,Source,ScanNext) values (%s,%d,'%c',0);")
|
||||
% sqlEscape(str(boost::format("%s %d") % strIp % iPort))
|
||||
% theApp->getUNL().iSourceScore(UniqueNodeList::vsManual)
|
||||
% char(UniqueNodeList::vsManual)));
|
||||
}
|
||||
|
||||
scanRefresh();
|
||||
}
|
||||
|
||||
// Start a connection, if not already known connected or connecting.
|
||||
//
|
||||
// <-- true, if already connected.
|
||||
Peer::pointer ConnectionPool::peerConnect(const std::string& strIp, int iPort)
|
||||
{
|
||||
ipPort pipPeer = make_pair(strIp, iPort);
|
||||
Peer::pointer ppResult;
|
||||
|
||||
boost::unordered_map<ipPort, Peer::pointer>::iterator it;
|
||||
|
||||
{
|
||||
boost::mutex::scoped_lock sl(mPeerLock);
|
||||
|
||||
if ((it = mIpMap.find(pipPeer)) == mIpMap.end())
|
||||
{
|
||||
Peer::pointer ppNew(Peer::create(theApp->getIOService(), mCtx, ++mLastPeer));
|
||||
|
||||
// Did not find it. Not already connecting or connected.
|
||||
ppNew->connect(strIp, iPort);
|
||||
|
||||
mIpMap[pipPeer] = ppNew;
|
||||
|
||||
ppResult = ppNew;
|
||||
// ++miConnectStarting;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Found it. Already connected.
|
||||
|
||||
nothing();
|
||||
}
|
||||
}
|
||||
|
||||
if (ppResult)
|
||||
{
|
||||
//cLog(lsINFO) << "Pool: Connecting: " << ADDRESS_SHARED(ppResult) << ": " << strIp << " " << iPort;
|
||||
}
|
||||
else
|
||||
{
|
||||
//cLog(lsINFO) << "Pool: Already connected: " << strIp << " " << iPort;
|
||||
}
|
||||
|
||||
return ppResult;
|
||||
}
|
||||
|
||||
// Returns information on verified peers.
|
||||
Json::Value ConnectionPool::getPeersJson()
|
||||
{
|
||||
Json::Value ret(Json::arrayValue);
|
||||
std::vector<Peer::pointer> vppPeers = getPeerVector();
|
||||
|
||||
BOOST_FOREACH(Peer::pointer peer, vppPeers)
|
||||
{
|
||||
ret.append(peer->getJson());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ConnectionPool::getPeerCount()
|
||||
{
|
||||
boost::mutex::scoped_lock sl(mPeerLock);
|
||||
|
||||
return mConnectedMap.size();
|
||||
}
|
||||
|
||||
std::vector<Peer::pointer> ConnectionPool::getPeerVector()
|
||||
{
|
||||
std::vector<Peer::pointer> ret;
|
||||
|
||||
boost::mutex::scoped_lock sl(mPeerLock);
|
||||
|
||||
ret.reserve(mConnectedMap.size());
|
||||
|
||||
BOOST_FOREACH(naPeer pair, mConnectedMap)
|
||||
{
|
||||
assert(!!pair.second);
|
||||
ret.push_back(pair.second);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint64 ConnectionPool::assignPeerId()
|
||||
{
|
||||
boost::mutex::scoped_lock sl(mPeerLock);
|
||||
return ++mLastPeer;
|
||||
}
|
||||
|
||||
// Now know peer's node public key. Determine if we want to stay connected.
|
||||
// <-- bNew: false = redundant
|
||||
bool ConnectionPool::peerConnected(Peer::ref peer, const RippleAddress& naPeer,
|
||||
const std::string& strIP, int iPort)
|
||||
{
|
||||
bool bNew = false;
|
||||
|
||||
assert(!!peer);
|
||||
|
||||
if (naPeer == theApp->getWallet().getNodePublic())
|
||||
{
|
||||
cLog(lsINFO) << "Pool: Connected: self: " << ADDRESS_SHARED(peer) << ": " << naPeer.humanNodePublic() << " " << strIP << " " << iPort;
|
||||
}
|
||||
else
|
||||
{
|
||||
boost::mutex::scoped_lock sl(mPeerLock);
|
||||
const boost::unordered_map<RippleAddress, Peer::pointer>::iterator& itCm = mConnectedMap.find(naPeer);
|
||||
|
||||
if (itCm == mConnectedMap.end())
|
||||
{
|
||||
// New connection.
|
||||
//cLog(lsINFO) << "Pool: Connected: new: " << ADDRESS_SHARED(peer) << ": " << naPeer.humanNodePublic() << " " << strIP << " " << iPort;
|
||||
|
||||
mConnectedMap[naPeer] = peer;
|
||||
bNew = true;
|
||||
|
||||
assert(peer->getPeerId() != 0);
|
||||
mPeerIdMap.insert(std::make_pair(peer->getPeerId(), peer));
|
||||
}
|
||||
// Found in map, already connected.
|
||||
else if (!strIP.empty())
|
||||
{
|
||||
// Was an outbound connection, we know IP and port.
|
||||
// Note in previous connection how to reconnect.
|
||||
if (itCm->second->getIP().empty())
|
||||
{
|
||||
// Old peer did not know it's IP.
|
||||
//cLog(lsINFO) << "Pool: Connected: redundant: outbound: " << ADDRESS_SHARED(peer) << " discovered: " << ADDRESS_SHARED(itCm->second) << ": " << strIP << " " << iPort;
|
||||
|
||||
itCm->second->setIpPort(strIP, iPort);
|
||||
|
||||
// Add old connection to identified connection list.
|
||||
mIpMap[make_pair(strIP, iPort)] = itCm->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Old peer knew its IP. Do nothing.
|
||||
//cLog(lsINFO) << "Pool: Connected: redundant: outbound: rediscovered: " << ADDRESS_SHARED(peer) << " " << strIP << " " << iPort;
|
||||
|
||||
nothing();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//cLog(lsINFO) << "Pool: Connected: redundant: inbound: " << ADDRESS_SHARED(peer) << " " << strIP << " " << iPort;
|
||||
|
||||
nothing();
|
||||
}
|
||||
}
|
||||
|
||||
return bNew;
|
||||
}
|
||||
|
||||
// We maintain a map of public key to peer for connected and verified peers. Maintain it.
|
||||
void ConnectionPool::peerDisconnected(Peer::ref peer, const RippleAddress& naPeer)
|
||||
{
|
||||
boost::mutex::scoped_lock sl(mPeerLock);
|
||||
|
||||
if (naPeer.isValid())
|
||||
{
|
||||
const boost::unordered_map<RippleAddress, Peer::pointer>::iterator& itCm = mConnectedMap.find(naPeer);
|
||||
|
||||
if (itCm == mConnectedMap.end())
|
||||
{
|
||||
// Did not find it. Not already connecting or connected.
|
||||
cLog(lsWARNING) << "Pool: disconnected: Internal Error: mConnectedMap was inconsistent.";
|
||||
// XXX Maybe bad error, considering we have racing connections, may not so bad.
|
||||
}
|
||||
else if (itCm->second != peer)
|
||||
{
|
||||
cLog(lsWARNING) << "Pool: disconected: non canonical entry";
|
||||
|
||||
nothing();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Found it. Delete it.
|
||||
mConnectedMap.erase(itCm);
|
||||
|
||||
//cLog(lsINFO) << "Pool: disconnected: " << naPeer.humanNodePublic() << " " << peer->getIP() << " " << peer->getPort();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//cLog(lsINFO) << "Pool: disconnected: anonymous: " << peer->getIP() << " " << peer->getPort();
|
||||
}
|
||||
|
||||
assert(peer->getPeerId() != 0);
|
||||
mPeerIdMap.erase(peer->getPeerId());
|
||||
}
|
||||
|
||||
// Schedule for immediate scanning, if not already scheduled.
|
||||
//
|
||||
// <-- true, scanRefresh needed.
|
||||
bool ConnectionPool::peerScanSet(const std::string& strIp, int iPort)
|
||||
{
|
||||
std::string strIpPort = str(boost::format("%s %d") % strIp % iPort);
|
||||
bool bScanDirty = false;
|
||||
|
||||
ScopedLock sl(theApp->getWalletDB()->getDBLock());
|
||||
Database* db = theApp->getWalletDB()->getDB();
|
||||
|
||||
if (db->executeSQL(str(boost::format("SELECT ScanNext FROM PeerIps WHERE IpPort=%s;")
|
||||
% sqlEscape(strIpPort)))
|
||||
&& db->startIterRows())
|
||||
{
|
||||
if (db->getNull("ScanNext"))
|
||||
{
|
||||
// Non-scanning connection terminated. Schedule for scanning.
|
||||
int iInterval = theConfig.PEER_SCAN_INTERVAL_MIN;
|
||||
boost::posix_time::ptime tpNow = boost::posix_time::second_clock::universal_time();
|
||||
boost::posix_time::ptime tpNext = tpNow + boost::posix_time::seconds(iInterval);
|
||||
|
||||
//cLog(lsINFO) << str(boost::format("Pool: Scan: schedule create: %s %s (next %s, delay=%d)")
|
||||
// % mScanIp % mScanPort % tpNext % (tpNext-tpNow).total_seconds());
|
||||
|
||||
db->executeSQL(str(boost::format("UPDATE PeerIps SET ScanNext=%d,ScanInterval=%d WHERE IpPort=%s;")
|
||||
% iToSeconds(tpNext)
|
||||
% iInterval
|
||||
% db->escape(strIpPort)));
|
||||
|
||||
bScanDirty = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Scan connection terminated, already scheduled for retry.
|
||||
// boost::posix_time::ptime tpNow = boost::posix_time::second_clock::universal_time();
|
||||
// boost::posix_time::ptime tpNext = ptFromSeconds(db->getInt("ScanNext"));
|
||||
|
||||
//cLog(lsINFO) << str(boost::format("Pool: Scan: schedule exists: %s %s (next %s, delay=%d)")
|
||||
// % mScanIp % mScanPort % tpNext % (tpNext-tpNow).total_seconds());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//cLog(lsWARNING) << "Pool: Scan: peer wasn't in PeerIps: " << strIp << " " << iPort;
|
||||
}
|
||||
|
||||
return bScanDirty;
|
||||
}
|
||||
|
||||
// --> strIp: not empty
|
||||
void ConnectionPool::peerClosed(Peer::ref peer, const std::string& strIp, int iPort)
|
||||
{
|
||||
ipPort ipPeer = make_pair(strIp, iPort);
|
||||
bool bScanRefresh = false;
|
||||
|
||||
// If the connection was our scan, we are no longer scanning.
|
||||
if (mScanning && mScanning == peer)
|
||||
{
|
||||
//cLog(lsINFO) << "Pool: Scan: scan fail: " << strIp << " " << iPort;
|
||||
|
||||
mScanning.reset(); // No longer scanning.
|
||||
bScanRefresh = true; // Look for more to scan.
|
||||
}
|
||||
|
||||
// Determine if closed peer was redundant.
|
||||
bool bRedundant = true;
|
||||
{
|
||||
boost::mutex::scoped_lock sl(mPeerLock);
|
||||
const boost::unordered_map<ipPort, Peer::pointer>::iterator& itIp = mIpMap.find(ipPeer);
|
||||
|
||||
if (itIp == mIpMap.end())
|
||||
{
|
||||
// Did not find it. Not already connecting or connected.
|
||||
cLog(lsWARNING) << "Pool: Closed: UNEXPECTED: " << ADDRESS_SHARED(peer) << ": " << strIp << " " << iPort;
|
||||
// XXX Internal error.
|
||||
}
|
||||
else if (mIpMap[ipPeer] == peer)
|
||||
{
|
||||
// We were the identified connection.
|
||||
//cLog(lsINFO) << "Pool: Closed: identified: " << ADDRESS_SHARED(peer) << ": " << strIp << " " << iPort;
|
||||
|
||||
// Delete our entry.
|
||||
mIpMap.erase(itIp);
|
||||
|
||||
bRedundant = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Found it. But, we were redundant.
|
||||
//cLog(lsINFO) << "Pool: Closed: redundant: " << ADDRESS_SHARED(peer) << ": " << strIp << " " << iPort;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bRedundant)
|
||||
{
|
||||
// If closed was not redundant schedule if not already scheduled.
|
||||
bScanRefresh = peerScanSet(ipPeer.first, ipPeer.second) || bScanRefresh;
|
||||
}
|
||||
|
||||
if (bScanRefresh)
|
||||
scanRefresh();
|
||||
}
|
||||
|
||||
void ConnectionPool::peerVerified(Peer::ref peer)
|
||||
{
|
||||
if (mScanning && mScanning == peer)
|
||||
{
|
||||
// Scan completed successfully.
|
||||
std::string strIp = peer->getIP();
|
||||
int iPort = peer->getPort();
|
||||
|
||||
std::string strIpPort = str(boost::format("%s %d") % strIp % iPort);
|
||||
|
||||
//cLog(lsINFO) << str(boost::format("Pool: Scan: connected: %s %s %s (scanned)") % ADDRESS_SHARED(peer) % strIp % iPort);
|
||||
|
||||
if (peer->getNodePublic() == theApp->getWallet().getNodePublic())
|
||||
{
|
||||
// Talking to ourself. We will just back off. This lets us maybe advertise our outside address.
|
||||
|
||||
nothing(); // Do nothing, leave scheduled scanning.
|
||||
}
|
||||
else
|
||||
{
|
||||
// Talking with a different peer.
|
||||
ScopedLock sl(theApp->getWalletDB()->getDBLock());
|
||||
Database *db=theApp->getWalletDB()->getDB();
|
||||
|
||||
db->executeSQL(str(boost::format("UPDATE PeerIps SET ScanNext=NULL,ScanInterval=0 WHERE IpPort=%s;")
|
||||
% db->escape(strIpPort)));
|
||||
// XXX Check error.
|
||||
}
|
||||
|
||||
mScanning.reset();
|
||||
|
||||
scanRefresh(); // Continue scanning.
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionPool::scanHandler(const boost::system::error_code& ecResult)
|
||||
{
|
||||
if (ecResult == boost::asio::error::operation_aborted)
|
||||
{
|
||||
nothing();
|
||||
}
|
||||
else if (!ecResult)
|
||||
{
|
||||
scanRefresh();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Internal error: unexpected deadline error.");
|
||||
}
|
||||
}
|
||||
|
||||
// Scan ips as per db entries.
|
||||
void ConnectionPool::scanRefresh()
|
||||
{
|
||||
if (mScanning)
|
||||
{
|
||||
// Currently scanning, will scan again after completion.
|
||||
cLog(lsTRACE) << "Pool: Scan: already scanning";
|
||||
|
||||
nothing();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Discover if there are entries that need scanning.
|
||||
boost::posix_time::ptime tpNext;
|
||||
boost::posix_time::ptime tpNow;
|
||||
std::string strIpPort;
|
||||
int iInterval;
|
||||
|
||||
{
|
||||
ScopedLock sl(theApp->getWalletDB()->getDBLock());
|
||||
Database* db = theApp->getWalletDB()->getDB();
|
||||
|
||||
if (db->executeSQL("SELECT * FROM PeerIps INDEXED BY PeerScanIndex WHERE ScanNext NOT NULL ORDER BY ScanNext LIMIT 1;")
|
||||
&& db->startIterRows())
|
||||
{
|
||||
// Have an entry to scan.
|
||||
int iNext = db->getInt("ScanNext");
|
||||
|
||||
tpNext = ptFromSeconds(iNext);
|
||||
tpNow = boost::posix_time::second_clock::universal_time();
|
||||
|
||||
db->getStr("IpPort", strIpPort);
|
||||
iInterval = db->getInt("ScanInterval");
|
||||
}
|
||||
else
|
||||
{
|
||||
// No entries to scan.
|
||||
tpNow = boost::posix_time::ptime(boost::posix_time::not_a_date_time);
|
||||
}
|
||||
}
|
||||
|
||||
if (tpNow.is_not_a_date_time())
|
||||
{
|
||||
//cLog(lsINFO) << "Pool: Scan: stop.";
|
||||
|
||||
(void) mScanTimer.cancel();
|
||||
}
|
||||
else if (tpNext <= tpNow)
|
||||
{
|
||||
// Scan it.
|
||||
splitIpPort(strIpPort, mScanIp, mScanPort);
|
||||
|
||||
(void) mScanTimer.cancel();
|
||||
|
||||
iInterval = std::max(iInterval, theConfig.PEER_SCAN_INTERVAL_MIN);
|
||||
|
||||
tpNext = tpNow + boost::posix_time::seconds(iInterval);
|
||||
|
||||
//cLog(lsINFO) << str(boost::format("Pool: Scan: Now: %s %s (next %s, delay=%d)")
|
||||
// % mScanIp % mScanPort % tpNext % (tpNext-tpNow).total_seconds());
|
||||
|
||||
iInterval *= 2;
|
||||
|
||||
{
|
||||
ScopedLock sl(theApp->getWalletDB()->getDBLock());
|
||||
Database *db=theApp->getWalletDB()->getDB();
|
||||
|
||||
db->executeSQL(str(boost::format("UPDATE PeerIps SET ScanNext=%d,ScanInterval=%d WHERE IpPort=%s;")
|
||||
% iToSeconds(tpNext)
|
||||
% iInterval
|
||||
% db->escape(strIpPort)));
|
||||
// XXX Check error.
|
||||
}
|
||||
|
||||
mScanning = peerConnect(mScanIp, mScanPort);
|
||||
if (!mScanning)
|
||||
{
|
||||
// Already connected. Try again.
|
||||
scanRefresh();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//cLog(lsINFO) << str(boost::format("Pool: Scan: Next: %s (next %s, delay=%d)")
|
||||
// % strIpPort % tpNext % (tpNext-tpNow).total_seconds());
|
||||
|
||||
mScanTimer.expires_at(tpNext);
|
||||
mScanTimer.async_wait(boost::bind(&ConnectionPool::scanHandler, this, _1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
bool ConnectionPool::isMessageKnown(PackedMessage::pointer msg)
|
||||
{
|
||||
for(unsigned int n=0; n<mBroadcastMessages.size(); n++)
|
||||
{
|
||||
if(msg==mBroadcastMessages[n].first) return(false);
|
||||
}
|
||||
return(false);
|
||||
}
|
||||
#endif
|
||||
|
||||
// vim:ts=4
|
||||
120
src/cpp/ripple/ConnectionPool.h
Normal file
120
src/cpp/ripple/ConnectionPool.h
Normal file
@@ -0,0 +1,120 @@
|
||||
#ifndef __CONNECTION_POOL__
|
||||
#define __CONNECTION_POOL__
|
||||
|
||||
#include <set>
|
||||
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <boost/thread/mutex.hpp>
|
||||
|
||||
#include "Peer.h"
|
||||
#include "PackedMessage.h"
|
||||
#include "types.h"
|
||||
|
||||
//
|
||||
// Access to the Ripple network.
|
||||
//
|
||||
class ConnectionPool
|
||||
{
|
||||
private:
|
||||
boost::mutex mPeerLock;
|
||||
uint64 mLastPeer;
|
||||
|
||||
typedef std::pair<RippleAddress, Peer::pointer> naPeer;
|
||||
typedef std::pair<ipPort, Peer::pointer> pipPeer;
|
||||
|
||||
// Peers we are connecting with and non-thin peers we are connected to.
|
||||
// Only peers we know the connection ip for are listed.
|
||||
// We know the ip and port for:
|
||||
// - All outbound connections
|
||||
// - Some inbound connections (which we figured out).
|
||||
boost::unordered_map<ipPort, Peer::pointer> mIpMap;
|
||||
|
||||
// Non-thin peers which we are connected to.
|
||||
// Peers we have the public key for.
|
||||
boost::unordered_map<RippleAddress, Peer::pointer> mConnectedMap;
|
||||
|
||||
// Connections with have a 64-bit identifier
|
||||
boost::unordered_map<uint64, Peer::pointer> mPeerIdMap;
|
||||
|
||||
boost::asio::ssl::context mCtx;
|
||||
|
||||
Peer::pointer mScanning;
|
||||
boost::asio::deadline_timer mScanTimer;
|
||||
std::string mScanIp;
|
||||
int mScanPort;
|
||||
|
||||
void scanHandler(const boost::system::error_code& ecResult);
|
||||
|
||||
boost::asio::deadline_timer mPolicyTimer;
|
||||
|
||||
void policyHandler(const boost::system::error_code& ecResult);
|
||||
|
||||
// Peers we are establishing a connection with as a client.
|
||||
// int miConnectStarting;
|
||||
|
||||
bool peerAvailable(std::string& strIp, int& iPort);
|
||||
bool peerScanSet(const std::string& strIp, int iPort);
|
||||
|
||||
Peer::pointer peerConnect(const std::string& strIp, int iPort);
|
||||
|
||||
public:
|
||||
ConnectionPool(boost::asio::io_service& io_service);
|
||||
|
||||
// Begin enforcing connection policy.
|
||||
void start();
|
||||
|
||||
// Send message to network.
|
||||
int relayMessage(Peer* fromPeer, const PackedMessage::pointer& msg);
|
||||
void relayMessageTo(const std::set<uint64>& fromPeers, const PackedMessage::pointer& msg);
|
||||
void relayMessageBut(const std::set<uint64>& fromPeers, const PackedMessage::pointer& msg);
|
||||
|
||||
// Manual connection request.
|
||||
// Queue for immediate scanning.
|
||||
void connectTo(const std::string& strIp, int iPort);
|
||||
|
||||
//
|
||||
// Peer connectivity notification.
|
||||
//
|
||||
bool getTopNAddrs(int n,std::vector<std::string>& addrs);
|
||||
bool savePeer(const std::string& strIp, int iPort, char code);
|
||||
|
||||
// We know peers node public key.
|
||||
// <-- bool: false=reject
|
||||
bool peerConnected(Peer::ref peer, const RippleAddress& naPeer, const std::string& strIP, int iPort);
|
||||
|
||||
// No longer connected.
|
||||
void peerDisconnected(Peer::ref peer, const RippleAddress& naPeer);
|
||||
|
||||
// As client accepted.
|
||||
void peerVerified(Peer::ref peer);
|
||||
|
||||
// As client failed connect and be accepted.
|
||||
void peerClosed(Peer::ref peer, const std::string& strIp, int iPort);
|
||||
|
||||
int getPeerCount();
|
||||
Json::Value getPeersJson();
|
||||
std::vector<Peer::pointer> getPeerVector();
|
||||
|
||||
// Peer 64-bit ID function
|
||||
uint64 assignPeerId();
|
||||
Peer::pointer getPeerById(const uint64& id);
|
||||
bool hasPeer(const uint64& id);
|
||||
|
||||
//
|
||||
// Scanning
|
||||
//
|
||||
|
||||
void scanRefresh();
|
||||
|
||||
//
|
||||
// Connection policy
|
||||
//
|
||||
void policyLowWater();
|
||||
void policyEnforce();
|
||||
|
||||
};
|
||||
|
||||
extern void splitIpPort(const std::string& strIpPort, std::string& strIp, int& iPort);
|
||||
|
||||
#endif
|
||||
// vim:ts=4
|
||||
35
src/cpp/ripple/Contract.cpp
Normal file
35
src/cpp/ripple/Contract.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#include "Contract.h"
|
||||
#include "Interpreter.h"
|
||||
|
||||
using namespace Script;
|
||||
/*
|
||||
JED: V III
|
||||
*/
|
||||
|
||||
Contract::Contract()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Contract::executeCreate()
|
||||
{
|
||||
|
||||
}
|
||||
void Contract::executeRemove()
|
||||
{
|
||||
|
||||
}
|
||||
void Contract::executeFund()
|
||||
{
|
||||
|
||||
}
|
||||
void Contract::executeAccept()
|
||||
{
|
||||
//std::vector<char> code;
|
||||
|
||||
//Interpreter interpreter;
|
||||
//interpreter.interpret(this,code);
|
||||
}
|
||||
|
||||
|
||||
30
src/cpp/ripple/Contract.h
Normal file
30
src/cpp/ripple/Contract.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef __CONTRACT__
|
||||
#define __CONTRACT__
|
||||
|
||||
#include "SerializedLedger.h"
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include "ScriptData.h"
|
||||
/*
|
||||
Encapsulates the SLE for a Contract
|
||||
*/
|
||||
|
||||
class Contract
|
||||
{
|
||||
public:
|
||||
Contract();
|
||||
|
||||
uint160& getIssuer();
|
||||
uint160& getOwner();
|
||||
STAmount& getRippleEscrow();
|
||||
uint32 getEscrow();
|
||||
uint32 getBond();
|
||||
|
||||
Script::Data getData(int index);
|
||||
|
||||
void executeCreate();
|
||||
void executeRemove();
|
||||
void executeFund();
|
||||
void executeAccept();
|
||||
};
|
||||
|
||||
#endif
|
||||
288
src/cpp/ripple/DBInit.cpp
Normal file
288
src/cpp/ripple/DBInit.cpp
Normal file
@@ -0,0 +1,288 @@
|
||||
#include "utils.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
// Transaction database holds transactions and public keys
|
||||
const char *TxnDBInit[] = {
|
||||
"BEGIN TRANSACTION;",
|
||||
|
||||
"CREATE TABLE Transactions ( \
|
||||
TransID CHARACTER(64) PRIMARY KEY, \
|
||||
TransType CHARACTER(24), \
|
||||
FromAcct CHARACTER(35), \
|
||||
FromSeq BIGINT UNSIGNED, \
|
||||
LedgerSeq BIGINT UNSIGNED, \
|
||||
Status CHARACTER(1), \
|
||||
RawTxn BLOB \
|
||||
TxnMeta BLOB \
|
||||
);",
|
||||
"CREATE TABLE PubKeys ( \
|
||||
ID CHARACTER(35) PRIMARY KEY, \
|
||||
PubKey BLOB \
|
||||
);",
|
||||
"CREATE TABLE AccountTransactions ( \
|
||||
TransID CHARACTER(64), \
|
||||
Account CHARACTER(64), \
|
||||
LedgerSeq BIGINT UNSIGNED \
|
||||
);",
|
||||
"CREATE INDEX AcctTxindex ON \
|
||||
AccountTransactions(Account, LedgerSeq, TransID);",
|
||||
"CREATE INDEX AcctLgrIndex ON \
|
||||
AccountTransactions(LedgerSeq, Account, TransID);",
|
||||
|
||||
"END TRANSACTION;"
|
||||
};
|
||||
|
||||
int TxnDBCount = NUMBER(TxnDBInit);
|
||||
|
||||
// Ledger database holds ledgers and ledger confirmations
|
||||
const char *LedgerDBInit[] = {
|
||||
"BEGIN TRANSACTION;",
|
||||
|
||||
"CREATE TABLE Ledgers ( \
|
||||
LedgerHash CHARACTER(64) PRIMARY KEY, \
|
||||
LedgerSeq BIGINT UNSIGNED, \
|
||||
PrevHash CHARACTER(64), \
|
||||
TotalCoins BIGINT UNSIGNED, \
|
||||
ClosingTime BIGINT UNSIGNED, \
|
||||
PrevClosingTime BIGINT UNSIGNED, \
|
||||
CloseTimeRes BIGINT UNSIGNED, \
|
||||
CloseFlags BIGINT UNSIGNED, \
|
||||
AccountSetHash CHARACTER(64), \
|
||||
TransSetHash CHARACTER(64) \
|
||||
);",
|
||||
"CREATE INDEX SeqLedger ON Ledgers(LedgerSeq);",
|
||||
|
||||
"CREATE TABLE LedgerValidations ( \
|
||||
LedgerHash CHARACTER(64), \
|
||||
NodePubKey CHARACTER(56), \
|
||||
Flags BIGINT UNSIGNED, \
|
||||
SignTime BIGINT UNSIGNED, \
|
||||
Signature BLOB \
|
||||
);",
|
||||
"CREATE INDEX ValidationByHash ON \
|
||||
LedgerValidations(LedgerHash);",
|
||||
|
||||
"END TRANSACTION;"
|
||||
};
|
||||
|
||||
int LedgerDBCount = NUMBER(LedgerDBInit);
|
||||
|
||||
// RPC database holds persistent data for RPC clients.
|
||||
const char *RpcDBInit[] = {
|
||||
|
||||
// Local persistence of the RPC client
|
||||
"CREATE TABLE RPCData ( \
|
||||
Key TEXT PRIMARY Key, \
|
||||
Value TEXT \
|
||||
);",
|
||||
};
|
||||
|
||||
int RpcDBCount = NUMBER(RpcDBInit);
|
||||
|
||||
// Wallet database holds local accounts and trusted nodes
|
||||
const char *WalletDBInit[] = {
|
||||
// Node identity must be persisted for CAS routing and responsibilities.
|
||||
"BEGIN TRANSACTION;",
|
||||
|
||||
"CREATE TABLE NodeIdentity ( \
|
||||
PublicKey CHARACTER(53), \
|
||||
PrivateKey CHARACTER(52), \
|
||||
Dh512 TEXT, \
|
||||
Dh1024 TEXT \
|
||||
);",
|
||||
|
||||
// Miscellaneous persistent information
|
||||
// Integer: 1 : Used to simplify SQL.
|
||||
// ScoreUpdated: when scores was last updated.
|
||||
// FetchUpdated: when last fetch succeeded.
|
||||
"CREATE TABLE Misc ( \
|
||||
Magic INTEGER UNIQUE NOT NULL, \
|
||||
ScoreUpdated DATETIME, \
|
||||
FetchUpdated DATETIME \
|
||||
);",
|
||||
|
||||
// Scoring and other information for domains.
|
||||
//
|
||||
// Domain:
|
||||
// Domain source for https.
|
||||
// PublicKey:
|
||||
// Set if ever succeeded.
|
||||
// XXX Use NULL in place of ""
|
||||
// Source:
|
||||
// 'M' = Manually added. : 1500
|
||||
// 'V' = validators.txt : 1000
|
||||
// 'W' = Web browsing. : 200
|
||||
// 'R' = Referral : 0
|
||||
// Next:
|
||||
// Time of next fetch attempt.
|
||||
// Scan:
|
||||
// Time of last fetch attempt.
|
||||
// Fetch:
|
||||
// Time of last successful fetch.
|
||||
// Sha256:
|
||||
// Checksum of last fetch.
|
||||
// Comment:
|
||||
// User supplied comment.
|
||||
// Table of Domains user has asked to trust.
|
||||
"CREATE TABLE SeedDomains ( \
|
||||
Domain TEXT PRIMARY KEY NOT NULL, \
|
||||
PublicKey CHARACTER(53), \
|
||||
Source CHARACTER(1) NOT NULL, \
|
||||
Next DATETIME, \
|
||||
Scan DATETIME, \
|
||||
Fetch DATETIME, \
|
||||
Sha256 CHARACTER[64], \
|
||||
Comment TEXT \
|
||||
);",
|
||||
|
||||
// Allow us to easily find the next SeedDomain to fetch.
|
||||
"CREATE INDEX SeedDomainNext ON SeedDomains (Next);",
|
||||
|
||||
// Table of PublicKeys user has asked to trust.
|
||||
// Fetches are made to the CAS. This gets the ripple.txt so even validators without a web server can publish a ripple.txt.
|
||||
// Source:
|
||||
// 'M' = Manually added. : 1500
|
||||
// 'V' = validators.txt : 1000
|
||||
// 'W' = Web browsing. : 200
|
||||
// 'R' = Referral : 0
|
||||
// Next:
|
||||
// Time of next fetch attempt.
|
||||
// Scan:
|
||||
// Time of last fetch attempt.
|
||||
// Fetch:
|
||||
// Time of last successful fetch.
|
||||
// Sha256:
|
||||
// Checksum of last fetch.
|
||||
// Comment:
|
||||
// User supplied comment.
|
||||
"CREATE TABLE SeedNodes ( \
|
||||
PublicKey CHARACTER(53) PRIMARY KEY NOT NULL, \
|
||||
Source CHARACTER(1) NOT NULL, \
|
||||
Next DATETIME, \
|
||||
Scan DATETIME, \
|
||||
Fetch DATETIME, \
|
||||
Sha256 CHARACTER[64], \
|
||||
Comment TEXT \
|
||||
);",
|
||||
|
||||
// Allow us to easily find the next SeedNode to fetch.
|
||||
"CREATE INDEX SeedNodeNext ON SeedNodes (Next);",
|
||||
|
||||
// Nodes we trust to not grossly collude against us. Derived from SeedDomains, SeedNodes, and ValidatorReferrals.
|
||||
//
|
||||
// Score:
|
||||
// Computed trust score. Higher is better.
|
||||
// Seen:
|
||||
// Last validation received.
|
||||
"CREATE TABLE TrustedNodes ( \
|
||||
PublicKey CHARACTER(53) PRIMARY KEY NOT NULL, \
|
||||
Score INTEGER DEFAULT 0 NOT NULL, \
|
||||
Seen DATETIME, \
|
||||
Comment TEXT \
|
||||
);",
|
||||
|
||||
// List of referrals.
|
||||
// - There may be multiple sources for a Validator. The last source is used.
|
||||
// Validator:
|
||||
// Public key of referrer.
|
||||
// Entry:
|
||||
// Entry index in [validators] table.
|
||||
// Referral:
|
||||
// This is the form provided by the ripple.txt:
|
||||
// - Public key for CAS based referral.
|
||||
// - Domain for domain based referral.
|
||||
// XXX Do garbage collection when validators have no references.
|
||||
"CREATE TABLE ValidatorReferrals ( \
|
||||
Validator CHARACTER(53) NOT NULL, \
|
||||
Entry INTEGER NOT NULL, \
|
||||
Referral TEXT NOT NULL, \
|
||||
PRIMARY KEY (Validator,Entry) \
|
||||
);",
|
||||
|
||||
// List of referrals from ripple.txt files.
|
||||
// Validator:
|
||||
// Public key of referree.
|
||||
// Entry:
|
||||
// Entry index in [validators] table.
|
||||
// IP:
|
||||
// IP of referred.
|
||||
// Port:
|
||||
// -1 = Default
|
||||
// XXX Do garbage collection when ips have no references.
|
||||
"CREATE TABLE IpReferrals ( \
|
||||
Validator CHARACTER(53) NOT NULL, \
|
||||
Entry INTEGER NOT NULL, \
|
||||
IP TEXT NOT NULL, \
|
||||
Port INTEGER NOT NULL DEFAULT -1, \
|
||||
PRIMARY KEY (Validator,Entry) \
|
||||
);",
|
||||
|
||||
// Table of IPs to contact the network.
|
||||
// IP:
|
||||
// IP address to contact.
|
||||
// Port:
|
||||
// Port to contact.
|
||||
// -1 = Default
|
||||
// Score:
|
||||
// Computed trust score. Higher is better.
|
||||
// Source:
|
||||
// 'V' = Validation file
|
||||
// 'M' = Manually added.
|
||||
// 'I' = Inbound connection.
|
||||
// 'T' = Told by other peer
|
||||
// 'O' = Other.
|
||||
// ScanNext:
|
||||
// When to next scan. Null=not scanning.
|
||||
// ScanInterval:
|
||||
// Delay between scans.
|
||||
"CREATE TABLE PeerIps ( \
|
||||
IpPort TEXT NOT NULL PRIMARY KEY, \
|
||||
Score INTEGER NOT NULL DEFAULT 0, \
|
||||
Source CHARACTER(1) NOT NULL, \
|
||||
ScanNext DATETIME DEFAULT 0, \
|
||||
ScanInterval INTEGER NOT NULL DEFAULT 0 \
|
||||
);",
|
||||
|
||||
"CREATE INDEX PeerScanIndex ON \
|
||||
PeerIps(ScanNext);",
|
||||
|
||||
"END TRANSACTION;"
|
||||
};
|
||||
|
||||
int WalletDBCount = NUMBER(WalletDBInit);
|
||||
|
||||
// Hash node database holds nodes indexed by hash
|
||||
const char *HashNodeDBInit[] = {
|
||||
"BEGIN TRANSACTION;",
|
||||
|
||||
"CREATE TABLE CommittedObjects ( \
|
||||
Hash CHARACTER(64) PRIMARY KEY, \
|
||||
ObjType CHAR(1) NOT NULL, \
|
||||
LedgerIndex BIGINT UNSIGNED, \
|
||||
Object BLOB \
|
||||
);",
|
||||
|
||||
"CREATE INDEX ObjectLocate ON \
|
||||
CommittedObjects(LedgerIndex, ObjType);",
|
||||
|
||||
"END TRANSACTION;"
|
||||
};
|
||||
|
||||
int HashNodeDBCount = NUMBER(HashNodeDBInit);
|
||||
|
||||
// Net node database holds nodes seen on the network
|
||||
// XXX Not really used needs replacement.
|
||||
const char *NetNodeDBInit[] = {
|
||||
"CREATE TABLE KnownNodes ( \
|
||||
Hanko CHARACTER(35) PRIMARY KEY, \
|
||||
LastSeen TEXT, \
|
||||
HaveContactInfo CHARACTER(1), \
|
||||
ContactObject BLOB \
|
||||
);"
|
||||
};
|
||||
|
||||
|
||||
int NetNodeDBCount = NUMBER(NetNodeDBInit);
|
||||
|
||||
// vim:ts=4
|
||||
340
src/cpp/ripple/DeterministicKeys.cpp
Normal file
340
src/cpp/ripple/DeterministicKeys.cpp
Normal file
@@ -0,0 +1,340 @@
|
||||
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/ecdsa.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
// #define EC_DEBUG
|
||||
|
||||
// Functions to add CKey support for deterministic EC keys
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include "Serializer.h"
|
||||
#include "Log.h"
|
||||
|
||||
// <-- seed
|
||||
uint128 CKey::PassPhraseToKey(const std::string& passPhrase)
|
||||
{
|
||||
Serializer s;
|
||||
|
||||
s.addRaw(passPhrase);
|
||||
uint256 hash256 = s.getSHA512Half();
|
||||
uint128 ret(hash256);
|
||||
|
||||
s.secureErase();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// --> seed
|
||||
// <-- private root generator + public root generator
|
||||
EC_KEY* CKey::GenerateRootDeterministicKey(const uint128& seed)
|
||||
{
|
||||
BN_CTX* ctx=BN_CTX_new();
|
||||
if(!ctx) return NULL;
|
||||
|
||||
EC_KEY* pkey=EC_KEY_new_by_curve_name(NID_secp256k1);
|
||||
if(!pkey)
|
||||
{
|
||||
BN_CTX_free(ctx);
|
||||
return NULL;
|
||||
}
|
||||
EC_KEY_set_conv_form(pkey, POINT_CONVERSION_COMPRESSED);
|
||||
|
||||
BIGNUM* order=BN_new();
|
||||
if(!order)
|
||||
{
|
||||
BN_CTX_free(ctx);
|
||||
EC_KEY_free(pkey);
|
||||
return NULL;
|
||||
}
|
||||
if(!EC_GROUP_get_order(EC_KEY_get0_group(pkey), order, ctx))
|
||||
{
|
||||
assert(false);
|
||||
BN_free(order);
|
||||
EC_KEY_free(pkey);
|
||||
BN_CTX_free(ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
BIGNUM *privKey=NULL;
|
||||
int seq=0;
|
||||
do
|
||||
{ // private key must be non-zero and less than the curve's order
|
||||
Serializer s((128+32)/8);
|
||||
s.add128(seed);
|
||||
s.add32(seq++);
|
||||
uint256 root=s.getSHA512Half();
|
||||
s.secureErase();
|
||||
privKey=BN_bin2bn((const unsigned char *) &root, sizeof(root), privKey);
|
||||
if(privKey==NULL)
|
||||
{
|
||||
EC_KEY_free(pkey);
|
||||
BN_free(order);
|
||||
BN_CTX_free(ctx);
|
||||
}
|
||||
root.zero();
|
||||
} while(BN_is_zero(privKey) || (BN_cmp(privKey, order)>=0));
|
||||
|
||||
BN_free(order);
|
||||
|
||||
if(!EC_KEY_set_private_key(pkey, privKey))
|
||||
{ // set the random point as the private key
|
||||
assert(false);
|
||||
EC_KEY_free(pkey);
|
||||
BN_clear_free(privKey);
|
||||
BN_CTX_free(ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
EC_POINT *pubKey=EC_POINT_new(EC_KEY_get0_group(pkey));
|
||||
if(!EC_POINT_mul(EC_KEY_get0_group(pkey), pubKey, privKey, NULL, NULL, ctx))
|
||||
{ // compute the corresponding public key point
|
||||
assert(false);
|
||||
BN_clear_free(privKey);
|
||||
EC_POINT_free(pubKey);
|
||||
EC_KEY_free(pkey);
|
||||
BN_CTX_free(ctx);
|
||||
return NULL;
|
||||
}
|
||||
BN_clear_free(privKey);
|
||||
if(!EC_KEY_set_public_key(pkey, pubKey))
|
||||
{
|
||||
assert(false);
|
||||
EC_POINT_free(pubKey);
|
||||
EC_KEY_free(pkey);
|
||||
BN_CTX_free(ctx);
|
||||
return NULL;
|
||||
}
|
||||
EC_POINT_free(pubKey);
|
||||
|
||||
BN_CTX_free(ctx);
|
||||
|
||||
#ifdef EC_DEBUG
|
||||
assert(EC_KEY_check_key(pkey)==1); // CAUTION: This check is *very* expensive
|
||||
#endif
|
||||
return pkey;
|
||||
}
|
||||
|
||||
// Take ripple address.
|
||||
// --> root public generator (consumes)
|
||||
// <-- root public generator in EC format
|
||||
EC_KEY* CKey::GenerateRootPubKey(BIGNUM* pubGenerator)
|
||||
{
|
||||
if (pubGenerator == NULL)
|
||||
{
|
||||
assert(false);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
EC_KEY* pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
|
||||
if (!pkey)
|
||||
{
|
||||
BN_free(pubGenerator);
|
||||
return NULL;
|
||||
}
|
||||
EC_KEY_set_conv_form(pkey, POINT_CONVERSION_COMPRESSED);
|
||||
|
||||
EC_POINT* pubPoint = EC_POINT_bn2point(EC_KEY_get0_group(pkey), pubGenerator, NULL, NULL);
|
||||
BN_free(pubGenerator);
|
||||
if(!pubPoint)
|
||||
{
|
||||
assert(false);
|
||||
EC_KEY_free(pkey);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(!EC_KEY_set_public_key(pkey, pubPoint))
|
||||
{
|
||||
assert(false);
|
||||
EC_POINT_free(pubPoint);
|
||||
EC_KEY_free(pkey);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return pkey;
|
||||
}
|
||||
|
||||
// --> public generator
|
||||
static BIGNUM* makeHash(const RippleAddress& pubGen, int seq, BIGNUM* order)
|
||||
{
|
||||
int subSeq=0;
|
||||
BIGNUM* ret=NULL;
|
||||
do
|
||||
{
|
||||
Serializer s((33*8+32+32)/8);
|
||||
s.addRaw(pubGen.getGenerator());
|
||||
s.add32(seq);
|
||||
s.add32(subSeq++);
|
||||
uint256 root=s.getSHA512Half();
|
||||
s.secureErase();
|
||||
ret=BN_bin2bn((const unsigned char *) &root, sizeof(root), ret);
|
||||
if(!ret) return NULL;
|
||||
} while (BN_is_zero(ret) || (BN_cmp(ret, order)>=0));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// --> public generator
|
||||
EC_KEY* CKey::GeneratePublicDeterministicKey(const RippleAddress& pubGen, int seq)
|
||||
{ // publicKey(n) = rootPublicKey EC_POINT_+ Hash(pubHash|seq)*point
|
||||
EC_KEY* rootKey = CKey::GenerateRootPubKey(pubGen.getGeneratorBN());
|
||||
const EC_POINT* rootPubKey = EC_KEY_get0_public_key(rootKey);
|
||||
BN_CTX* ctx = BN_CTX_new();
|
||||
EC_KEY* pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
|
||||
EC_POINT* newPoint = 0;
|
||||
BIGNUM* order = 0;
|
||||
BIGNUM* hash = 0;
|
||||
bool success = true;
|
||||
|
||||
if (!ctx || !pkey) success = false;
|
||||
|
||||
if (success)
|
||||
EC_KEY_set_conv_form(pkey, POINT_CONVERSION_COMPRESSED);
|
||||
|
||||
if (success) {
|
||||
newPoint = EC_POINT_new(EC_KEY_get0_group(pkey));
|
||||
if(!newPoint) success = false;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
order = BN_new();
|
||||
|
||||
if(!order || !EC_GROUP_get_order(EC_KEY_get0_group(pkey), order, ctx))
|
||||
success = false;
|
||||
}
|
||||
|
||||
// Calculate the private additional key.
|
||||
if (success) {
|
||||
hash = makeHash(pubGen, seq, order);
|
||||
if(!hash) success = false;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
// Calculate the corresponding public key.
|
||||
EC_POINT_mul(EC_KEY_get0_group(pkey), newPoint, hash, NULL, NULL, ctx);
|
||||
|
||||
// Add the master public key and set.
|
||||
EC_POINT_add(EC_KEY_get0_group(pkey), newPoint, newPoint, rootPubKey, ctx);
|
||||
EC_KEY_set_public_key(pkey, newPoint);
|
||||
}
|
||||
|
||||
if (order) BN_free(order);
|
||||
if (hash) BN_free(hash);
|
||||
if (newPoint) EC_POINT_free(newPoint);
|
||||
if (ctx) BN_CTX_free(ctx);
|
||||
if (rootKey) EC_KEY_free(rootKey);
|
||||
if (pkey && !success) EC_KEY_free(pkey);
|
||||
|
||||
return success ? pkey : NULL;
|
||||
}
|
||||
|
||||
EC_KEY* CKey::GeneratePrivateDeterministicKey(const RippleAddress& pubGen, const uint256& u, int seq)
|
||||
{
|
||||
CBigNum bn(u);
|
||||
return GeneratePrivateDeterministicKey(pubGen, static_cast<BIGNUM*>(&bn), seq);
|
||||
}
|
||||
|
||||
// --> root private key
|
||||
EC_KEY* CKey::GeneratePrivateDeterministicKey(const RippleAddress& pubGen, const BIGNUM* rootPrivKey, int seq)
|
||||
{ // privateKey(n) = (rootPrivateKey + Hash(pubHash|seq)) % order
|
||||
BN_CTX* ctx=BN_CTX_new();
|
||||
if(ctx==NULL) return NULL;
|
||||
|
||||
EC_KEY* pkey=EC_KEY_new_by_curve_name(NID_secp256k1);
|
||||
if(pkey==NULL)
|
||||
{
|
||||
BN_CTX_free(ctx);
|
||||
return NULL;
|
||||
}
|
||||
EC_KEY_set_conv_form(pkey, POINT_CONVERSION_COMPRESSED);
|
||||
|
||||
BIGNUM* order=BN_new();
|
||||
if(order==NULL)
|
||||
{
|
||||
BN_CTX_free(ctx);
|
||||
EC_KEY_free(pkey);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(!EC_GROUP_get_order(EC_KEY_get0_group(pkey), order, ctx))
|
||||
{
|
||||
BN_free(order);
|
||||
BN_CTX_free(ctx);
|
||||
EC_KEY_free(pkey);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// calculate the private additional key
|
||||
BIGNUM* privKey=makeHash(pubGen, seq, order);
|
||||
if(privKey==NULL)
|
||||
{
|
||||
BN_free(order);
|
||||
BN_CTX_free(ctx);
|
||||
EC_KEY_free(pkey);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// calculate the final private key
|
||||
BN_mod_add(privKey, privKey, rootPrivKey, order, ctx);
|
||||
BN_free(order);
|
||||
EC_KEY_set_private_key(pkey, privKey);
|
||||
|
||||
// compute the corresponding public key
|
||||
EC_POINT* pubKey=EC_POINT_new(EC_KEY_get0_group(pkey));
|
||||
if(!pubKey)
|
||||
{
|
||||
BN_clear_free(privKey);
|
||||
BN_CTX_free(ctx);
|
||||
EC_KEY_free(pkey);
|
||||
return NULL;
|
||||
}
|
||||
if(EC_POINT_mul(EC_KEY_get0_group(pkey), pubKey, privKey, NULL, NULL, ctx)==0)
|
||||
{
|
||||
BN_clear_free(privKey);
|
||||
BN_CTX_free(ctx);
|
||||
EC_KEY_free(pkey);
|
||||
return NULL;
|
||||
}
|
||||
BN_clear_free(privKey);
|
||||
EC_KEY_set_public_key(pkey, pubKey);
|
||||
|
||||
EC_POINT_free(pubKey);
|
||||
BN_CTX_free(ctx);
|
||||
|
||||
return pkey;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(DeterministicKeys_test)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(DeterminsticKeys_test1)
|
||||
{
|
||||
Log(lsDEBUG) << "Beginning deterministic key test";
|
||||
|
||||
uint128 seed1, seed2;
|
||||
seed1.SetHex("71ED064155FFADFA38782C5E0158CB26");
|
||||
seed2.SetHex("CF0C3BE4485961858C4198515AE5B965");
|
||||
CKey root1(seed1), root2(seed2);
|
||||
|
||||
uint256 priv1, priv2;
|
||||
root1.GetPrivateKeyU(priv1);
|
||||
root2.GetPrivateKeyU(priv2);
|
||||
|
||||
if (priv1.GetHex() != "7CFBA64F771E93E817E15039215430B53F7401C34931D111EAB3510B22DBB0D8")
|
||||
BOOST_FAIL("Incorrect private key for generator");
|
||||
if (priv2.GetHex() != "98BC2EACB26EB021D1A6293C044D88BA2F0B6729A2772DEEBF2E21A263C1740B")
|
||||
BOOST_FAIL("Incorrect private key for generator");
|
||||
|
||||
RippleAddress nSeed;
|
||||
nSeed.setSeed(seed1);
|
||||
if (nSeed.humanSeed() != "shHM53KPZ87Gwdqarm1bAmPeXg8Tn")
|
||||
BOOST_FAIL("Incorrect human seed");
|
||||
if (nSeed.humanSeed1751() != "MAD BODY ACE MINT OKAY HUB WHAT DATA SACK FLAT DANA MATH")
|
||||
BOOST_FAIL("Incorrect 1751 seed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END();
|
||||
|
||||
// vim:ts=4
|
||||
303
src/cpp/ripple/ECIES.cpp
Normal file
303
src/cpp/ripple/ECIES.cpp
Normal file
@@ -0,0 +1,303 @@
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/ecdsa.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
#include "key.h"
|
||||
|
||||
// ECIES uses elliptic curve keys to send an encrypted message.
|
||||
|
||||
// A shared secret is generated from one public key and one private key.
|
||||
// The same key results regardless of which key is public and which private.
|
||||
|
||||
// Anonymous messages can be sent by generating an ephemeral public/private
|
||||
// key pair, using that private key with the recipient's public key to
|
||||
// encrypt and publishing the ephemeral public key. Non-anonymous messages
|
||||
// can be sent by using your own private key with the recipient's public key.
|
||||
|
||||
// A random IV is used to encrypt the message and an HMAC is used to ensure
|
||||
// message integrity. If you need timestamps or need to tell the recipient
|
||||
// which key to use (his, yours, or ephemeral) you must add that data.
|
||||
// (Obviously, key information can't go in the encrypted portion anyway.)
|
||||
|
||||
// Our ciphertext is all encrypted except the IV. The encrypted data decodes as follows:
|
||||
// 1) IV (unencrypted)
|
||||
// 2) Encrypted: HMAC of original plaintext
|
||||
// 3) Encrypted: Original plaintext
|
||||
// 4) Encrypted: Rest of block/padding
|
||||
|
||||
// ECIES operations throw on any error such as a corrupt message or incorrect
|
||||
// key. They *must* be called in try/catch blocks.
|
||||
|
||||
// Algorithmic choices:
|
||||
#define ECIES_KEY_HASH SHA512 // Hash used to expand shared secret
|
||||
#define ECIES_KEY_LENGTH (512/8) // Size of expanded shared secret
|
||||
#define ECIES_MIN_SEC (128/8) // The minimum equivalent security
|
||||
#define ECIES_ENC_ALGO EVP_aes_256_cbc() // Encryption algorithm
|
||||
#define ECIES_ENC_KEY_TYPE uint256 // Type used to hold shared secret
|
||||
#define ECIES_ENC_KEY_SIZE (256/8) // Encryption key size
|
||||
#define ECIES_ENC_BLK_SIZE (128/8) // Encryption block size
|
||||
#define ECIES_ENC_IV_TYPE uint128 // Type used to hold IV
|
||||
#define ECIES_HMAC_ALGO EVP_sha256() // HMAC algorithm
|
||||
#define ECIES_HMAC_KEY_TYPE uint256 // Type used to hold HMAC key
|
||||
#define ECIES_HMAC_KEY_SIZE (256/8) // Size of HMAC key
|
||||
#define ECIES_HMAC_TYPE uint256 // Type used to hold HMAC value
|
||||
#define ECIES_HMAC_SIZE (256/8) // Size of HMAC value
|
||||
|
||||
void CKey::getECIESSecret(CKey& otherKey, ECIES_ENC_KEY_TYPE& enc_key, ECIES_HMAC_KEY_TYPE& hmac_key)
|
||||
{ // Retrieve a secret generated from an EC key pair. At least one private key must be known.
|
||||
if (!pkey || !otherKey.pkey)
|
||||
throw std::runtime_error("missing key");
|
||||
|
||||
EC_KEY *pubkey, *privkey;
|
||||
if (EC_KEY_get0_private_key(pkey))
|
||||
{
|
||||
privkey = pkey;
|
||||
pubkey = otherKey.pkey;
|
||||
}
|
||||
else if (EC_KEY_get0_private_key(otherKey.pkey))
|
||||
{
|
||||
privkey = otherKey.pkey;
|
||||
pubkey = pkey;
|
||||
}
|
||||
else throw std::runtime_error("no private key");
|
||||
|
||||
unsigned char rawbuf[512];
|
||||
int buflen = ECDH_compute_key(rawbuf, 512, EC_KEY_get0_public_key(pubkey), privkey, NULL);
|
||||
if (buflen < ECIES_MIN_SEC)
|
||||
throw std::runtime_error("ecdh key failed");
|
||||
|
||||
unsigned char hbuf[ECIES_KEY_LENGTH];
|
||||
ECIES_KEY_HASH(rawbuf, buflen, hbuf);
|
||||
memset(rawbuf, 0, ECIES_HMAC_KEY_SIZE);
|
||||
|
||||
assert((ECIES_ENC_KEY_SIZE + ECIES_HMAC_KEY_SIZE) >= ECIES_KEY_LENGTH);
|
||||
memcpy(enc_key.begin(), hbuf, ECIES_ENC_KEY_SIZE);
|
||||
memcpy(hmac_key.begin(), hbuf + ECIES_ENC_KEY_SIZE, ECIES_HMAC_KEY_SIZE);
|
||||
memset(hbuf, 0, ECIES_KEY_LENGTH);
|
||||
}
|
||||
|
||||
static ECIES_HMAC_TYPE makeHMAC(const ECIES_HMAC_KEY_TYPE& secret, const std::vector<unsigned char>& data)
|
||||
{
|
||||
HMAC_CTX ctx;
|
||||
HMAC_CTX_init(&ctx);
|
||||
|
||||
if (HMAC_Init_ex(&ctx, secret.begin(), ECIES_HMAC_KEY_SIZE, ECIES_HMAC_ALGO, NULL) != 1)
|
||||
{
|
||||
HMAC_CTX_cleanup(&ctx);
|
||||
throw std::runtime_error("init hmac");
|
||||
}
|
||||
|
||||
if (HMAC_Update(&ctx, &(data.front()), data.size()) != 1)
|
||||
{
|
||||
HMAC_CTX_cleanup(&ctx);
|
||||
throw std::runtime_error("update hmac");
|
||||
}
|
||||
|
||||
ECIES_HMAC_TYPE ret;
|
||||
unsigned int ml = ECIES_HMAC_SIZE;
|
||||
if (HMAC_Final(&ctx, ret.begin(), &ml) != 1)
|
||||
{
|
||||
HMAC_CTX_cleanup(&ctx);
|
||||
throw std::runtime_error("finalize hmac");
|
||||
}
|
||||
assert(ml == ECIES_HMAC_SIZE);
|
||||
HMAC_CTX_cleanup(&ctx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> CKey::encryptECIES(CKey& otherKey, const std::vector<unsigned char>& plaintext)
|
||||
{
|
||||
|
||||
ECIES_ENC_IV_TYPE iv;
|
||||
if (RAND_bytes(static_cast<unsigned char *>(iv.begin()), ECIES_ENC_BLK_SIZE) != 1)
|
||||
throw std::runtime_error("insufficient entropy");
|
||||
|
||||
ECIES_ENC_KEY_TYPE secret;
|
||||
ECIES_HMAC_KEY_TYPE hmacKey;
|
||||
|
||||
getECIESSecret(otherKey, secret, hmacKey);
|
||||
ECIES_HMAC_TYPE hmac = makeHMAC(hmacKey, plaintext);
|
||||
hmacKey.zero();
|
||||
|
||||
EVP_CIPHER_CTX ctx;
|
||||
EVP_CIPHER_CTX_init(&ctx);
|
||||
|
||||
if (EVP_EncryptInit_ex(&ctx, ECIES_ENC_ALGO, NULL, secret.begin(), iv.begin()) != 1)
|
||||
{
|
||||
EVP_CIPHER_CTX_cleanup(&ctx);
|
||||
secret.zero();
|
||||
throw std::runtime_error("init cipher ctx");
|
||||
}
|
||||
secret.zero();
|
||||
|
||||
std::vector<unsigned char> out(plaintext.size() + ECIES_HMAC_SIZE + ECIES_ENC_KEY_SIZE + ECIES_ENC_BLK_SIZE, 0);
|
||||
int len = 0, bytesWritten;
|
||||
|
||||
// output IV
|
||||
memcpy(&(out.front()), iv.begin(), ECIES_ENC_BLK_SIZE);
|
||||
len = ECIES_ENC_BLK_SIZE;
|
||||
|
||||
// Encrypt/output HMAC
|
||||
bytesWritten = out.capacity() - len;
|
||||
assert(bytesWritten>0);
|
||||
if (EVP_EncryptUpdate(&ctx, &(out.front()) + len, &bytesWritten, hmac.begin(), ECIES_HMAC_SIZE) < 0)
|
||||
{
|
||||
EVP_CIPHER_CTX_cleanup(&ctx);
|
||||
throw std::runtime_error("");
|
||||
}
|
||||
len += bytesWritten;
|
||||
|
||||
// encrypt/output plaintext
|
||||
bytesWritten = out.capacity() - len;
|
||||
assert(bytesWritten>0);
|
||||
if (EVP_EncryptUpdate(&ctx, &(out.front()) + len, &bytesWritten, &(plaintext.front()), plaintext.size()) < 0)
|
||||
{
|
||||
EVP_CIPHER_CTX_cleanup(&ctx);
|
||||
throw std::runtime_error("");
|
||||
}
|
||||
len += bytesWritten;
|
||||
|
||||
// finalize
|
||||
bytesWritten = out.capacity() - len;
|
||||
if (EVP_EncryptFinal_ex(&ctx, &(out.front()) + len, &bytesWritten) < 0)
|
||||
{
|
||||
EVP_CIPHER_CTX_cleanup(&ctx);
|
||||
throw std::runtime_error("encryption error");
|
||||
}
|
||||
len += bytesWritten;
|
||||
|
||||
// Output contains: IV, encrypted HMAC, encrypted data, encrypted padding
|
||||
assert(len <= (plaintext.size() + ECIES_HMAC_SIZE + (2 * ECIES_ENC_BLK_SIZE)));
|
||||
assert(len >= (plaintext.size() + ECIES_HMAC_SIZE + ECIES_ENC_BLK_SIZE)); // IV, HMAC, data
|
||||
out.resize(len);
|
||||
EVP_CIPHER_CTX_cleanup(&ctx);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> CKey::decryptECIES(CKey& otherKey, const std::vector<unsigned char>& ciphertext)
|
||||
{
|
||||
// minimum ciphertext = IV + HMAC + 1 block
|
||||
if (ciphertext.size() < ((2 * ECIES_ENC_BLK_SIZE) + ECIES_HMAC_SIZE) )
|
||||
throw std::runtime_error("ciphertext too short");
|
||||
|
||||
// extract IV
|
||||
ECIES_ENC_IV_TYPE iv;
|
||||
memcpy(iv.begin(), &(ciphertext.front()), ECIES_ENC_BLK_SIZE);
|
||||
|
||||
// begin decrypting
|
||||
EVP_CIPHER_CTX ctx;
|
||||
EVP_CIPHER_CTX_init(&ctx);
|
||||
|
||||
ECIES_ENC_KEY_TYPE secret;
|
||||
ECIES_HMAC_KEY_TYPE hmacKey;
|
||||
getECIESSecret(otherKey, secret, hmacKey);
|
||||
|
||||
if (EVP_DecryptInit_ex(&ctx, ECIES_ENC_ALGO, NULL, secret.begin(), iv.begin()) != 1)
|
||||
{
|
||||
secret.zero();
|
||||
hmacKey.zero();
|
||||
EVP_CIPHER_CTX_cleanup(&ctx);
|
||||
throw std::runtime_error("unable to init cipher");
|
||||
}
|
||||
|
||||
// decrypt mac
|
||||
ECIES_HMAC_TYPE hmac;
|
||||
int outlen = ECIES_HMAC_SIZE;
|
||||
if ( (EVP_DecryptUpdate(&ctx, hmac.begin(), &outlen,
|
||||
&(ciphertext.front()) + ECIES_ENC_BLK_SIZE, ECIES_HMAC_SIZE + 1) != 1) || (outlen != ECIES_HMAC_SIZE) )
|
||||
{
|
||||
secret.zero();
|
||||
hmacKey.zero();
|
||||
EVP_CIPHER_CTX_cleanup(&ctx);
|
||||
throw std::runtime_error("unable to extract hmac");
|
||||
}
|
||||
|
||||
// decrypt plaintext (after IV and encrypted mac)
|
||||
std::vector<unsigned char> plaintext(ciphertext.size() - ECIES_HMAC_SIZE - ECIES_ENC_BLK_SIZE);
|
||||
outlen = plaintext.size();
|
||||
if (EVP_DecryptUpdate(&ctx, &(plaintext.front()), &outlen,
|
||||
&(ciphertext.front()) + ECIES_ENC_BLK_SIZE + ECIES_HMAC_SIZE + 1,
|
||||
ciphertext.size() - ECIES_ENC_BLK_SIZE - ECIES_HMAC_SIZE - 1) != 1)
|
||||
{
|
||||
secret.zero();
|
||||
hmacKey.zero();
|
||||
EVP_CIPHER_CTX_cleanup(&ctx);
|
||||
throw std::runtime_error("unable to extract plaintext");
|
||||
}
|
||||
|
||||
// decrypt padding
|
||||
int flen = 0;
|
||||
if (EVP_DecryptFinal(&ctx, &(plaintext.front()) + outlen, &flen) != 1)
|
||||
{
|
||||
secret.zero();
|
||||
hmacKey.zero();
|
||||
EVP_CIPHER_CTX_cleanup(&ctx);
|
||||
throw std::runtime_error("plaintext had bad padding");
|
||||
}
|
||||
plaintext.resize(flen + outlen);
|
||||
|
||||
// verify integrity
|
||||
if (hmac != makeHMAC(hmacKey, plaintext))
|
||||
{
|
||||
secret.zero();
|
||||
hmacKey.zero();
|
||||
EVP_CIPHER_CTX_cleanup(&ctx);
|
||||
throw std::runtime_error("plaintext had bad hmac");
|
||||
}
|
||||
secret.zero();
|
||||
hmacKey.zero();
|
||||
|
||||
EVP_CIPHER_CTX_cleanup(&ctx);
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
bool checkECIES(void)
|
||||
{
|
||||
CKey senderPriv, recipientPriv, senderPub, recipientPub;
|
||||
|
||||
for (int i = 0; i < 30000; ++i)
|
||||
{
|
||||
if ((i % 100) == 0)
|
||||
{ // generate new keys every 100 times
|
||||
// std::cerr << "new keys" << std::endl;
|
||||
senderPriv.MakeNewKey();
|
||||
recipientPriv.MakeNewKey();
|
||||
|
||||
if (!senderPub.SetPubKey(senderPriv.GetPubKey()))
|
||||
throw std::runtime_error("key error");
|
||||
if (!recipientPub.SetPubKey(recipientPriv.GetPubKey()))
|
||||
throw std::runtime_error("key error");
|
||||
}
|
||||
|
||||
// generate message
|
||||
std::vector<unsigned char> message(4096);
|
||||
int msglen = i%3000;
|
||||
|
||||
if (RAND_bytes(static_cast<unsigned char *>(&message.front()), msglen) != 1)
|
||||
throw std::runtime_error("insufficient entropy");
|
||||
message.resize(msglen);
|
||||
|
||||
// encrypt message with sender's private key and recipient's public key
|
||||
std::vector<unsigned char> ciphertext = senderPriv.encryptECIES(recipientPub, message);
|
||||
|
||||
// decrypt message with recipient's private key and sender's public key
|
||||
std::vector<unsigned char> decrypt = recipientPriv.decryptECIES(senderPub, ciphertext);
|
||||
|
||||
if (decrypt != message)
|
||||
{
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
||||
// std::cerr << "Msg(" << msglen << ") ok " << ciphertext.size() << std::endl;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
143
src/cpp/ripple/FieldNames.cpp
Normal file
143
src/cpp/ripple/FieldNames.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
|
||||
#include "FieldNames.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <boost/thread/mutex.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
// These must stay at the top of this file
|
||||
std::map<int, SField::ptr> SField::codeToField;
|
||||
boost::mutex SField::mapMutex;
|
||||
|
||||
SField sfInvalid(-1), sfGeneric(0);
|
||||
SField sfLedgerEntry(STI_LEDGERENTRY, 1, "LedgerEntry");
|
||||
SField sfTransaction(STI_TRANSACTION, 1, "Transaction");
|
||||
SField sfValidation(STI_VALIDATION, 1, "Validation");
|
||||
SField sfHash(STI_HASH256, 257, "hash");
|
||||
SField sfIndex(STI_HASH256, 258, "index");
|
||||
|
||||
#define FIELD(name, type, index) SField sf##name(FIELD_CODE(STI_##type, index), STI_##type, index, #name);
|
||||
#define TYPE(name, type, index)
|
||||
#include "SerializeProto.h"
|
||||
#undef FIELD
|
||||
#undef TYPE
|
||||
|
||||
static int initFields()
|
||||
{
|
||||
sfTxnSignature.notSigningField(); sfTxnSignatures.notSigningField();
|
||||
sfSignature.notSigningField();
|
||||
|
||||
sfHighQualityIn.setMeta(SFM_CHANGE); sfHighQualityOut.setMeta(SFM_CHANGE);
|
||||
sfLowQualityIn.setMeta(SFM_CHANGE); sfLowQualityOut.setMeta(SFM_CHANGE);
|
||||
|
||||
sfLowLimit.setMeta(SFM_ALWAYS); sfHighLimit.setMeta(SFM_ALWAYS);
|
||||
sfTakerPays.setMeta(SFM_ALWAYS); sfTakerGets.setMeta(SFM_ALWAYS);
|
||||
sfQualityIn.setMeta(SFM_ALWAYS); sfQualityOut.setMeta(SFM_ALWAYS);
|
||||
|
||||
sfBalance.setMeta(SFM_ALWAYS);
|
||||
|
||||
sfPublicKey.setMeta(SFM_CHANGE); sfMessageKey.setMeta(SFM_CHANGE);
|
||||
sfSigningPubKey.setMeta(SFM_CHANGE); sfAuthorizedKey.setMeta(SFM_CHANGE);
|
||||
sfSigningAccounts.setMeta(SFM_CHANGE);
|
||||
|
||||
sfWalletLocator.setMeta(SFM_ALWAYS);
|
||||
sfWalletSize.setMeta(SFM_ALWAYS);
|
||||
sfNickname.setMeta(SFM_CHANGE);
|
||||
sfAmount.setMeta(SFM_ALWAYS);
|
||||
sfDomain.setMeta(SFM_CHANGE);
|
||||
sfOwner.setMeta(SFM_ALWAYS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
static const int f = initFields();
|
||||
|
||||
|
||||
SField::SField(SerializedTypeID tid, int fv) : fieldCode(FIELD_CODE(tid, fv)), fieldType(tid), fieldValue(fv)
|
||||
{ // call with the map mutex
|
||||
fieldName = lexical_cast_i(tid) + "/" + lexical_cast_i(fv);
|
||||
codeToField[fieldCode] = this;
|
||||
}
|
||||
|
||||
SField::ref SField::getField(int code)
|
||||
{
|
||||
int type = code >> 16;
|
||||
int field = code % 0xffff;
|
||||
|
||||
if ((type <= 0) || (field <= 0))
|
||||
return sfInvalid;
|
||||
|
||||
boost::mutex::scoped_lock sl(mapMutex);
|
||||
|
||||
std::map<int, SField::ptr>::iterator it = codeToField.find(code);
|
||||
if (it != codeToField.end())
|
||||
return *(it->second);
|
||||
|
||||
if (field > 255) // don't dynamically extend types that have no binary encoding
|
||||
return sfInvalid;
|
||||
|
||||
switch (type)
|
||||
{ // types we are willing to dynamically extend
|
||||
|
||||
#define FIELD(name, type, index)
|
||||
#define TYPE(name, type, index) case STI_##type:
|
||||
#include "SerializeProto.h"
|
||||
#undef FIELD
|
||||
#undef TYPE
|
||||
|
||||
break;
|
||||
default:
|
||||
return sfInvalid;
|
||||
}
|
||||
|
||||
return *(new SField(static_cast<SerializedTypeID>(type), field));
|
||||
}
|
||||
|
||||
int SField::compare(SField::ref f1, SField::ref f2)
|
||||
{ // -1 = f1 comes before f2, 0 = illegal combination, 1 = f1 comes after f2
|
||||
if ((f1.fieldCode <= 0) || (f2.fieldCode <= 0))
|
||||
return 0;
|
||||
|
||||
if (f1.fieldCode < f2.fieldCode)
|
||||
return -1;
|
||||
|
||||
if (f2.fieldCode < f1.fieldCode)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string SField::getName() const
|
||||
{
|
||||
if (!fieldName.empty())
|
||||
return fieldName;
|
||||
if (fieldValue == 0)
|
||||
return "";
|
||||
return boost::lexical_cast<std::string>(static_cast<int>(fieldType)) + "/" +
|
||||
boost::lexical_cast<std::string>(fieldValue);
|
||||
}
|
||||
|
||||
SField::ref SField::getField(const std::string& fieldName)
|
||||
{ // OPTIMIZEME me with a map. CHECKME this is case sensitive
|
||||
boost::mutex::scoped_lock sl(mapMutex);
|
||||
typedef std::pair<const int, SField::ptr> int_sfref_pair;
|
||||
BOOST_FOREACH(const int_sfref_pair& fieldPair, codeToField)
|
||||
{
|
||||
if (fieldPair.second->fieldName == fieldName)
|
||||
return *(fieldPair.second);
|
||||
}
|
||||
return sfInvalid;
|
||||
}
|
||||
|
||||
SField::~SField()
|
||||
{
|
||||
boost::mutex::scoped_lock sl(mapMutex);
|
||||
std::map<int, ptr>::iterator it = codeToField.find(fieldCode);
|
||||
if ((it != codeToField.end()) && (it->second == this))
|
||||
codeToField.erase(it);
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
122
src/cpp/ripple/FieldNames.h
Normal file
122
src/cpp/ripple/FieldNames.h
Normal file
@@ -0,0 +1,122 @@
|
||||
#ifndef __FIELDNAMES__
|
||||
#define __FIELDNAMES__
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <boost/thread/mutex.hpp>
|
||||
|
||||
#define FIELD_CODE(type, index) ((static_cast<int>(type) << 16) | index)
|
||||
|
||||
enum SerializedTypeID
|
||||
{
|
||||
// special types
|
||||
STI_UNKNOWN = -2,
|
||||
STI_DONE = -1,
|
||||
STI_NOTPRESENT = 0,
|
||||
|
||||
#define TYPE(name, field, value) STI_##field = value,
|
||||
#define FIELD(name, field, value)
|
||||
#include "SerializeProto.h"
|
||||
#undef TYPE
|
||||
#undef FIELD
|
||||
|
||||
// high level types
|
||||
STI_TRANSACTION = 10001,
|
||||
STI_LEDGERENTRY = 10002,
|
||||
STI_VALIDATION = 10003,
|
||||
};
|
||||
|
||||
enum SOE_Flags
|
||||
{
|
||||
SOE_INVALID = -1,
|
||||
SOE_REQUIRED = 0, // required
|
||||
SOE_OPTIONAL = 1, // optional
|
||||
};
|
||||
|
||||
enum SF_Meta
|
||||
{
|
||||
SFM_NEVER = 0,
|
||||
SFM_CHANGE = 1,
|
||||
SFM_DELETE = 2,
|
||||
SFM_ALWAYS = 3
|
||||
};
|
||||
|
||||
class SField
|
||||
{
|
||||
public:
|
||||
typedef const SField& ref;
|
||||
typedef SField const * ptr;
|
||||
|
||||
protected:
|
||||
static std::map<int, ptr> codeToField;
|
||||
static boost::mutex mapMutex;
|
||||
|
||||
SField(SerializedTypeID id, int val);
|
||||
|
||||
public:
|
||||
|
||||
const int fieldCode; // (type<<16)|index
|
||||
const SerializedTypeID fieldType; // STI_*
|
||||
const int fieldValue; // Code number for protocol
|
||||
std::string fieldName;
|
||||
SF_Meta fieldMeta;
|
||||
bool signingField;
|
||||
|
||||
SField(int fc, SerializedTypeID tid, int fv, const char* fn) :
|
||||
fieldCode(fc), fieldType(tid), fieldValue(fv), fieldName(fn), fieldMeta(SFM_NEVER), signingField(true)
|
||||
{
|
||||
boost::mutex::scoped_lock sl(mapMutex);
|
||||
codeToField[fieldCode] = this;
|
||||
}
|
||||
|
||||
SField(SerializedTypeID tid, int fv, const char *fn) :
|
||||
fieldCode(FIELD_CODE(tid, fv)), fieldType(tid), fieldValue(fv), fieldName(fn),
|
||||
fieldMeta(SFM_NEVER), signingField(true)
|
||||
{
|
||||
boost::mutex::scoped_lock sl(mapMutex);
|
||||
codeToField[fieldCode] = this;
|
||||
}
|
||||
|
||||
SField(int fc) : fieldCode(fc), fieldType(STI_UNKNOWN), fieldValue(0) { ; }
|
||||
|
||||
~SField();
|
||||
|
||||
static SField::ref getField(int fieldCode);
|
||||
static SField::ref getField(const std::string& fieldName);
|
||||
static SField::ref getField(int type, int value) { return getField(FIELD_CODE(type, value)); }
|
||||
static SField::ref getField(SerializedTypeID type, int value) { return getField(FIELD_CODE(type, value)); }
|
||||
|
||||
std::string getName() const;
|
||||
bool hasName() const { return !fieldName.empty(); }
|
||||
|
||||
bool isGeneric() const { return fieldCode == 0; }
|
||||
bool isInvalid() const { return fieldCode == -1; }
|
||||
bool isKnown() const { return fieldType != STI_UNKNOWN; }
|
||||
bool isBinary() const { return fieldValue < 256; }
|
||||
bool isDiscardable() const { return fieldValue > 256; }
|
||||
|
||||
SF_Meta getMeta() const { return fieldMeta; }
|
||||
bool shouldMetaDel() const { return (fieldMeta == SFM_DELETE) || (fieldMeta == SFM_ALWAYS); }
|
||||
bool shouldMetaMod() const { return (fieldMeta == SFM_CHANGE) || (fieldMeta == SFM_ALWAYS); }
|
||||
void setMeta(SF_Meta m) { fieldMeta = m; }
|
||||
bool isSigningField() const { return signingField; }
|
||||
void notSigningField() { signingField = false; }
|
||||
|
||||
bool shouldInclude(bool withSigningField) const
|
||||
{ return (fieldValue < 256) && (withSigningField || signingField); }
|
||||
|
||||
bool operator==(const SField& f) const { return fieldCode == f.fieldCode; }
|
||||
bool operator!=(const SField& f) const { return fieldCode != f.fieldCode; }
|
||||
|
||||
static int compare(SField::ref f1, SField::ref f2);
|
||||
};
|
||||
|
||||
extern SField sfInvalid, sfGeneric, sfLedgerEntry, sfTransaction, sfValidation;
|
||||
|
||||
#define FIELD(name, type, index) extern SField sf##name;
|
||||
#define TYPE(name, type, index)
|
||||
#include "SerializeProto.h"
|
||||
#undef FIELD
|
||||
#undef TYPE
|
||||
|
||||
#endif
|
||||
102
src/cpp/ripple/HTTPRequest.cpp
Normal file
102
src/cpp/ripple/HTTPRequest.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
#include "HTTPRequest.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include "Log.h"
|
||||
SETUP_LOG();
|
||||
|
||||
void HTTPRequest::reset()
|
||||
{
|
||||
vHeaders.clear();
|
||||
sRequestBody.clear();
|
||||
sAuthorization.clear();
|
||||
iDataSize = 0;
|
||||
bShouldClose = true;
|
||||
eState = await_request;
|
||||
}
|
||||
|
||||
HTTPRequestAction HTTPRequest::requestDone(bool forceClose)
|
||||
{
|
||||
if (forceClose || bShouldClose)
|
||||
return haCLOSE_CONN;
|
||||
reset();
|
||||
return haREAD_LINE;
|
||||
}
|
||||
|
||||
std::string HTTPRequest::getReplyHeaders(bool forceClose)
|
||||
{
|
||||
if (forceClose || bShouldClose)
|
||||
return "Connection: close\r\n";
|
||||
else
|
||||
return "Connection: Keep-Alive\r\n";
|
||||
}
|
||||
|
||||
HTTPRequestAction HTTPRequest::consume(boost::asio::streambuf& buf)
|
||||
{
|
||||
std::string line;
|
||||
std::istream is(&buf);
|
||||
std::getline(is, line);
|
||||
boost::trim(line);
|
||||
|
||||
// cLog(lsTRACE) << "HTTPRequest line: " << line;
|
||||
|
||||
if (eState == await_request)
|
||||
{ // VERB URL PROTO
|
||||
if (line.empty())
|
||||
return haREAD_LINE;
|
||||
sRequest = line;
|
||||
bShouldClose = sRequest.find("HTTP/1.1") == std::string::npos;
|
||||
|
||||
eState = await_header;
|
||||
return haREAD_LINE;
|
||||
}
|
||||
|
||||
if (eState == await_header)
|
||||
{ // HEADER_NAME: HEADER_BODY
|
||||
if (line.empty()) // empty line or bare \r
|
||||
{
|
||||
if (iDataSize == 0)
|
||||
{ // no body
|
||||
eState = do_request;
|
||||
return haDO_REQUEST;
|
||||
}
|
||||
eState = getting_body;
|
||||
return haREAD_RAW;
|
||||
}
|
||||
vHeaders.push_back(line);
|
||||
|
||||
size_t colon = line.find(':');
|
||||
if (colon != std::string::npos)
|
||||
{
|
||||
std::string headerName = line.substr(0, colon);
|
||||
boost::trim(headerName);
|
||||
boost::to_lower(headerName);
|
||||
|
||||
std::string headerValue = line.substr(colon+1);
|
||||
boost::trim(headerValue);
|
||||
|
||||
if (headerName == "connection")
|
||||
{
|
||||
boost::to_lower(headerValue);
|
||||
if ((headerValue == "keep-alive") || (headerValue == "keepalive"))
|
||||
bShouldClose = false;
|
||||
if (headerValue == "close")
|
||||
bShouldClose = true;
|
||||
}
|
||||
|
||||
if (headerName == "content-length")
|
||||
iDataSize = boost::lexical_cast<int>(headerValue);
|
||||
|
||||
if (headerName == "authorization")
|
||||
sAuthorization = headerValue;
|
||||
|
||||
}
|
||||
|
||||
return haREAD_LINE;
|
||||
}
|
||||
|
||||
assert(false);
|
||||
return haERROR;
|
||||
}
|
||||
61
src/cpp/ripple/HTTPRequest.h
Normal file
61
src/cpp/ripple/HTTPRequest.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#ifndef HTTPREQUEST__HPP
|
||||
#define HTTPREQUEST__HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/asio/streambuf.hpp>
|
||||
|
||||
enum HTTPRequestAction
|
||||
{ // What the application code needs to do
|
||||
haERROR = 0,
|
||||
haREAD_LINE = 1,
|
||||
haREAD_RAW = 2,
|
||||
haDO_REQUEST = 3,
|
||||
haCLOSE_CONN = 4
|
||||
};
|
||||
|
||||
class HTTPRequest
|
||||
{ // an HTTP request in progress
|
||||
protected:
|
||||
|
||||
enum state
|
||||
{
|
||||
await_request, // We are waiting for the request line
|
||||
await_header, // We are waiting for request headers
|
||||
getting_body, // We are waiting for the body
|
||||
do_request, // We are waiting for the request to complete
|
||||
};
|
||||
|
||||
state eState;
|
||||
std::string sRequest; // VERB URL PROTO
|
||||
std::string sRequestBody;
|
||||
std::string sAuthorization;
|
||||
|
||||
std::vector<std::string> vHeaders;
|
||||
|
||||
int iDataSize;
|
||||
bool bShouldClose;
|
||||
|
||||
public:
|
||||
|
||||
HTTPRequest() : eState(await_request), iDataSize(0), bShouldClose(true) { ; }
|
||||
void reset();
|
||||
|
||||
std::string& peekBody() { return sRequestBody; }
|
||||
std::string getBody() { return sRequestBody; }
|
||||
std::string& peekRequest() { return sRequest; }
|
||||
std::string getRequest() { return sRequest; }
|
||||
std::string& peekAuth() { return sAuthorization; }
|
||||
std::string getAuth() { return sAuthorization; }
|
||||
|
||||
std::vector<std::string>& peekHeaders() { return vHeaders; }
|
||||
std::string getReplyHeaders(bool forceClose);
|
||||
|
||||
HTTPRequestAction consume(boost::asio::streambuf&);
|
||||
HTTPRequestAction requestDone(bool forceClose); // call after reply is sent
|
||||
|
||||
int getDataSize() { return iDataSize; }
|
||||
};
|
||||
|
||||
#endif
|
||||
32
src/cpp/ripple/HashPrefixes.h
Normal file
32
src/cpp/ripple/HashPrefixes.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef __HASHPREFIXES__
|
||||
#define __HASHPREFIXES__
|
||||
|
||||
#include "types.h"
|
||||
|
||||
// TXN - Hash of transaction plus signature to give transaction ID
|
||||
const uint32 sHP_TransactionID = 0x54584E00;
|
||||
|
||||
// STX - Hash of inner transaction to sign
|
||||
const uint32 sHP_TransactionSign = 0x53545800;
|
||||
|
||||
// TND - Hash of transaction plus metadata
|
||||
const uint32 sHP_TransactionNode = 0x534E4400;
|
||||
|
||||
// MLN - Hash of account state
|
||||
const uint32 sHP_LeafNode = 0x4D4C4E00;
|
||||
|
||||
// MIN - Hash of inner node in tree
|
||||
const uint32 sHP_InnerNode = 0x4D494E00;
|
||||
|
||||
// LGR - Hash of ledger master data for signing
|
||||
const uint32 sHP_Ledger = 0x4C575200;
|
||||
|
||||
// VAL - Hash of validation for signing
|
||||
const uint32 sHP_Validation = 0x56414C00;
|
||||
|
||||
// PRP - Hash of proposal for signing
|
||||
const uint32 sHP_Proposal = 0x50525000;
|
||||
|
||||
#endif
|
||||
|
||||
// vim:ts=4
|
||||
175
src/cpp/ripple/HashedObject.cpp
Normal file
175
src/cpp/ripple/HashedObject.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
|
||||
#include "HashedObject.h"
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "Serializer.h"
|
||||
#include "Application.h"
|
||||
#include "Log.h"
|
||||
|
||||
SETUP_LOG();
|
||||
DECLARE_INSTANCE(HashedObject);
|
||||
|
||||
HashedObjectStore::HashedObjectStore(int cacheSize, int cacheAge) :
|
||||
mCache("HashedObjectStore", cacheSize, cacheAge), mWritePending(false)
|
||||
{
|
||||
mWriteSet.reserve(128);
|
||||
}
|
||||
|
||||
|
||||
bool HashedObjectStore::store(HashedObjectType type, uint32 index,
|
||||
const std::vector<unsigned char>& data, const uint256& hash)
|
||||
{ // return: false = already in cache, true = added to cache
|
||||
if (!theApp->getHashNodeDB())
|
||||
{
|
||||
cLog(lsTRACE) << "HOS: no db";
|
||||
return true;
|
||||
}
|
||||
if (mCache.touch(hash))
|
||||
{
|
||||
cLog(lsTRACE) << "HOS: " << hash << " store: incache";
|
||||
return false;
|
||||
}
|
||||
assert(hash == Serializer::getSHA512Half(data));
|
||||
|
||||
HashedObject::pointer object = boost::make_shared<HashedObject>(type, index, data, hash);
|
||||
if (!mCache.canonicalize(hash, object))
|
||||
{
|
||||
// cLog(lsTRACE) << "Queuing write for " << hash;
|
||||
boost::mutex::scoped_lock sl(mWriteMutex);
|
||||
mWriteSet.push_back(object);
|
||||
if (!mWritePending)
|
||||
{
|
||||
mWritePending = true;
|
||||
boost::thread t(boost::bind(&HashedObjectStore::bulkWrite, this));
|
||||
t.detach();
|
||||
}
|
||||
}
|
||||
// else
|
||||
// cLog(lsTRACE) << "HOS: already had " << hash;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HashedObjectStore::waitWrite()
|
||||
{
|
||||
boost::unique_lock<boost::mutex> sl(mWriteMutex);
|
||||
while (mWritePending)
|
||||
mWriteCondition.wait(sl);
|
||||
}
|
||||
|
||||
void HashedObjectStore::bulkWrite()
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
std::vector< boost::shared_ptr<HashedObject> > set;
|
||||
set.reserve(128);
|
||||
|
||||
{
|
||||
boost::unique_lock<boost::mutex> sl(mWriteMutex);
|
||||
mWriteSet.swap(set);
|
||||
assert(mWriteSet.empty());
|
||||
if (set.empty())
|
||||
{
|
||||
mWritePending = false;
|
||||
mWriteCondition.notify_all();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// cLog(lsTRACE) << "HOS: writing " << set.size();
|
||||
|
||||
static boost::format fExists("SELECT ObjType FROM CommittedObjects WHERE Hash = '%s';");
|
||||
static boost::format
|
||||
fAdd("INSERT INTO CommittedObjects (Hash,ObjType,LedgerIndex,Object) VALUES ('%s','%c','%u',%s);");
|
||||
|
||||
Database* db = theApp->getHashNodeDB()->getDB();
|
||||
ScopedLock sl = theApp->getHashNodeDB()->getDBLock();
|
||||
|
||||
db->executeSQL("BEGIN TRANSACTION;");
|
||||
|
||||
BOOST_FOREACH(const boost::shared_ptr<HashedObject>& it, set)
|
||||
{
|
||||
if (!SQL_EXISTS(db, boost::str(fExists % it->getHash().GetHex())))
|
||||
{
|
||||
char type;
|
||||
switch(it->getType())
|
||||
{
|
||||
case hotLEDGER: type = 'L'; break;
|
||||
case hotTRANSACTION: type = 'T'; break;
|
||||
case hotACCOUNT_NODE: type = 'A'; break;
|
||||
case hotTRANSACTION_NODE: type = 'N'; break;
|
||||
default: type = 'U';
|
||||
}
|
||||
std::string rawData;
|
||||
db->escape(&(it->getData().front()), it->getData().size(), rawData);
|
||||
db->executeSQL(boost::str(fAdd % it->getHash().GetHex() % type % it->getIndex() % rawData ));
|
||||
}
|
||||
}
|
||||
|
||||
db->executeSQL("END TRANSACTION;");
|
||||
}
|
||||
}
|
||||
|
||||
HashedObject::pointer HashedObjectStore::retrieve(const uint256& hash)
|
||||
{
|
||||
HashedObject::pointer obj;
|
||||
{
|
||||
ScopedLock sl(theApp->getHashNodeDB()->getDBLock());
|
||||
obj = mCache.fetch(hash);
|
||||
if (obj)
|
||||
{
|
||||
cLog(lsTRACE) << "HOS: " << hash << " fetch: incache";
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
if (!theApp || !theApp->getHashNodeDB())
|
||||
return HashedObject::pointer();
|
||||
std::string sql = "SELECT * FROM CommittedObjects WHERE Hash='";
|
||||
sql.append(hash.GetHex());
|
||||
sql.append("';");
|
||||
|
||||
std::vector<unsigned char> data;
|
||||
{
|
||||
ScopedLock sl(theApp->getHashNodeDB()->getDBLock());
|
||||
Database* db = theApp->getHashNodeDB()->getDB();
|
||||
|
||||
if (!db->executeSQL(sql) || !db->startIterRows())
|
||||
{
|
||||
// cLog(lsTRACE) << "HOS: " << hash << " fetch: not in db";
|
||||
return HashedObject::pointer();
|
||||
}
|
||||
|
||||
std::string type;
|
||||
db->getStr("ObjType", type);
|
||||
if (type.size() == 0) return HashedObject::pointer();
|
||||
|
||||
uint32 index = db->getBigInt("LedgerIndex");
|
||||
|
||||
int size = db->getBinary("Object", NULL, 0);
|
||||
data.resize(size);
|
||||
db->getBinary("Object", &(data.front()), size);
|
||||
db->endIterRows();
|
||||
|
||||
assert(Serializer::getSHA512Half(data) == hash);
|
||||
|
||||
HashedObjectType htype = hotUNKNOWN;
|
||||
switch (type[0])
|
||||
{
|
||||
case 'L': htype = hotLEDGER; break;
|
||||
case 'T': htype = hotTRANSACTION; break;
|
||||
case 'A': htype = hotACCOUNT_NODE; break;
|
||||
case 'N': htype = hotTRANSACTION_NODE; break;
|
||||
default:
|
||||
cLog(lsERROR) << "Invalid hashed object";
|
||||
return HashedObject::pointer();
|
||||
}
|
||||
|
||||
obj = boost::make_shared<HashedObject>(htype, index, data, hash);
|
||||
mCache.canonicalize(hash, obj);
|
||||
}
|
||||
cLog(lsTRACE) << "HOS: " << hash << " fetch: in db";
|
||||
return obj;
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
70
src/cpp/ripple/HashedObject.h
Normal file
70
src/cpp/ripple/HashedObject.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#ifndef __HASHEDOBJECT__
|
||||
#define __HASHEDOBJECT__
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <boost/thread/mutex.hpp>
|
||||
#include <boost/thread/condition_variable.hpp>
|
||||
|
||||
#include "types.h"
|
||||
#include "uint256.h"
|
||||
#include "ScopedLock.h"
|
||||
#include "TaggedCache.h"
|
||||
#include "InstanceCounter.h"
|
||||
|
||||
DEFINE_INSTANCE(HashedObject);
|
||||
|
||||
enum HashedObjectType
|
||||
{
|
||||
hotUNKNOWN = 0,
|
||||
hotLEDGER = 1,
|
||||
hotTRANSACTION = 2,
|
||||
hotACCOUNT_NODE = 3,
|
||||
hotTRANSACTION_NODE = 4
|
||||
};
|
||||
|
||||
class HashedObject : private IS_INSTANCE(HashedObject)
|
||||
{
|
||||
public:
|
||||
typedef boost::shared_ptr<HashedObject> pointer;
|
||||
|
||||
HashedObjectType mType;
|
||||
uint256 mHash;
|
||||
uint32 mLedgerIndex;
|
||||
std::vector<unsigned char> mData;
|
||||
|
||||
HashedObject(HashedObjectType type, uint32 index, const std::vector<unsigned char>& data, const uint256& hash) :
|
||||
mType(type), mHash(hash), mLedgerIndex(index), mData(data) { ; }
|
||||
|
||||
const std::vector<unsigned char>& getData() const { return mData; }
|
||||
const uint256& getHash() const { return mHash; }
|
||||
HashedObjectType getType() const { return mType; }
|
||||
uint32 getIndex() const { return mLedgerIndex; }
|
||||
};
|
||||
|
||||
class HashedObjectStore
|
||||
{
|
||||
protected:
|
||||
TaggedCache<uint256, HashedObject> mCache;
|
||||
|
||||
boost::mutex mWriteMutex;
|
||||
boost::condition_variable mWriteCondition;
|
||||
|
||||
std::vector< boost::shared_ptr<HashedObject> > mWriteSet;
|
||||
bool mWritePending;
|
||||
|
||||
public:
|
||||
|
||||
HashedObjectStore(int cacheSize, int cacheAge);
|
||||
|
||||
bool store(HashedObjectType type, uint32 index, const std::vector<unsigned char>& data,
|
||||
const uint256& hash);
|
||||
|
||||
HashedObject::pointer retrieve(const uint256& hash);
|
||||
|
||||
void bulkWrite();
|
||||
void waitWrite();
|
||||
void sweep() { mCache.sweep(); }
|
||||
};
|
||||
|
||||
#endif
|
||||
383
src/cpp/ripple/HttpsClient.cpp
Normal file
383
src/cpp/ripple/HttpsClient.cpp
Normal file
@@ -0,0 +1,383 @@
|
||||
//
|
||||
// Fetch a web page via https.
|
||||
//
|
||||
|
||||
#include "HttpsClient.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <boost/regex.hpp>
|
||||
#include <boost/smart_ptr/shared_ptr.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
using namespace boost::system;
|
||||
using namespace boost::asio;
|
||||
|
||||
HttpsClient::HttpsClient(
|
||||
boost::asio::io_service& io_service,
|
||||
const unsigned short port,
|
||||
const std::string& strPath,
|
||||
std::size_t responseMax
|
||||
) :
|
||||
mCtx(boost::asio::ssl::context::sslv23),
|
||||
mResolver(io_service),
|
||||
mSocketSsl(io_service, mCtx),
|
||||
mResponse(responseMax),
|
||||
mStrPath(strPath),
|
||||
mPort(port),
|
||||
mDeadline(io_service)
|
||||
{
|
||||
}
|
||||
|
||||
void HttpsClient::httpsGet(
|
||||
std::deque<std::string> deqSites,
|
||||
boost::posix_time::time_duration timeout,
|
||||
boost::function<void(const boost::system::error_code& ecResult, std::string& strData)> complete) {
|
||||
|
||||
mDeqSites = deqSites;
|
||||
mComplete = complete;
|
||||
mTimeout = timeout;
|
||||
|
||||
httpsNext();
|
||||
}
|
||||
|
||||
void HttpsClient::httpsNext()
|
||||
{
|
||||
// std::cerr << "Fetch: " << mDeqSites[0] << std::endl;
|
||||
boost::shared_ptr<boost::asio::ip::tcp::resolver::query> query(new boost::asio::ip::tcp::resolver::query(mDeqSites[0], boost::lexical_cast<std::string>(mPort),
|
||||
ip::resolver_query_base::numeric_service|ip::resolver_query_base::numeric_service));
|
||||
mQuery = query;
|
||||
|
||||
mCtx.set_default_verify_paths(mShutdown);
|
||||
if (mShutdown)
|
||||
{
|
||||
std::cerr << "set_default_verify_paths: " << mShutdown.message() << std::endl;
|
||||
}
|
||||
|
||||
if (!mShutdown)
|
||||
{
|
||||
mDeadline.expires_from_now(mTimeout, mShutdown);
|
||||
|
||||
// std::cerr << "expires_from_now: " << mShutdown.message() << std::endl;
|
||||
}
|
||||
|
||||
if (!mShutdown)
|
||||
{
|
||||
mDeadline.async_wait(
|
||||
boost::bind(
|
||||
&HttpsClient::ShandleDeadline,
|
||||
shared_from_this(),
|
||||
boost::asio::placeholders::error));
|
||||
}
|
||||
|
||||
if (!mShutdown)
|
||||
{
|
||||
// std::cerr << "Resolving: " << mDeqSites[0] << std::endl;
|
||||
|
||||
mResolver.async_resolve(*mQuery,
|
||||
boost::bind(
|
||||
&HttpsClient::ShandleResolve,
|
||||
shared_from_this(),
|
||||
boost::asio::placeholders::error,
|
||||
boost::asio::placeholders::iterator));
|
||||
}
|
||||
|
||||
if (mShutdown)
|
||||
invokeComplete(mShutdown);
|
||||
}
|
||||
|
||||
void HttpsClient::handleDeadline(const boost::system::error_code& ecResult)
|
||||
{
|
||||
if (ecResult == boost::asio::error::operation_aborted)
|
||||
{
|
||||
// Timer canceled because deadline no longer needed.
|
||||
// std::cerr << "Deadline cancelled." << std::endl;
|
||||
|
||||
nothing(); // Aborter is done.
|
||||
}
|
||||
else if (ecResult)
|
||||
{
|
||||
std::cerr << "Deadline error: " << mDeqSites[0] << ": " << ecResult.message() << std::endl;
|
||||
|
||||
// Can't do anything sound.
|
||||
abort();
|
||||
}
|
||||
else
|
||||
{
|
||||
boost::system::error_code ec_shutdown;
|
||||
|
||||
std::cerr << "Deadline arrived." << std::endl;
|
||||
|
||||
// Mark us as shutting down.
|
||||
// XXX Use our own error code.
|
||||
mShutdown = boost::system::error_code(errc::bad_address, system_category());
|
||||
|
||||
// Cancel any resolving.
|
||||
mResolver.cancel();
|
||||
|
||||
// Stop the transaction.
|
||||
mSocketSsl.shutdown(ec_shutdown);
|
||||
|
||||
if (ec_shutdown)
|
||||
{
|
||||
std::cerr << "Shutdown error: " << mDeqSites[0] << ": " << ec_shutdown.message() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HttpsClient::handleResolve(
|
||||
const boost::system::error_code& ecResult,
|
||||
boost::asio::ip::tcp::resolver::iterator itrEndpoint
|
||||
)
|
||||
{
|
||||
if (!mShutdown)
|
||||
mShutdown = ecResult;
|
||||
|
||||
if (mShutdown)
|
||||
{
|
||||
// std::cerr << "Resolve error: " << mDeqSites[0] << ": " << mShutdown.message() << std::endl;
|
||||
|
||||
invokeComplete(mShutdown);
|
||||
}
|
||||
else
|
||||
{
|
||||
// std::cerr << "Resolve complete." << std::endl;
|
||||
|
||||
boost::asio::async_connect(
|
||||
mSocketSsl.lowest_layer(),
|
||||
itrEndpoint,
|
||||
boost::bind(
|
||||
&HttpsClient::ShandleConnect,
|
||||
shared_from_this(),
|
||||
boost::asio::placeholders::error));
|
||||
}
|
||||
}
|
||||
|
||||
void HttpsClient::handleConnect(const boost::system::error_code& ecResult)
|
||||
{
|
||||
if (!mShutdown)
|
||||
mShutdown = ecResult;
|
||||
|
||||
if (mShutdown)
|
||||
{
|
||||
std::cerr << "Connect error: " << mShutdown.message() << std::endl;
|
||||
}
|
||||
|
||||
if (!mShutdown)
|
||||
{
|
||||
// std::cerr << "Connected." << std::endl;
|
||||
|
||||
mSocketSsl.set_verify_mode(boost::asio::ssl::verify_peer);
|
||||
|
||||
// XXX Verify semantics of RFC 2818 are what we want.
|
||||
mSocketSsl.set_verify_callback(boost::asio::ssl::rfc2818_verification(mDeqSites[0]), mShutdown);
|
||||
|
||||
if (mShutdown)
|
||||
{
|
||||
std::cerr << "set_verify_callback: " << mDeqSites[0] << ": " << mShutdown.message() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mShutdown)
|
||||
{
|
||||
mSocketSsl.async_handshake(boost::asio::ssl::stream<boost::asio::ip::tcp::socket>::client,
|
||||
boost::bind(&HttpsClient::ShandleRequest,
|
||||
shared_from_this(),
|
||||
boost::asio::placeholders::error));
|
||||
}
|
||||
else
|
||||
{
|
||||
invokeComplete(mShutdown);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpsClient::handleRequest(const boost::system::error_code& ecResult)
|
||||
{
|
||||
if (!mShutdown)
|
||||
mShutdown = ecResult;
|
||||
|
||||
if (mShutdown)
|
||||
{
|
||||
std::cerr << "Handshake error:" << mShutdown.message() << std::endl;
|
||||
|
||||
invokeComplete(mShutdown);
|
||||
}
|
||||
else
|
||||
{
|
||||
// std::cerr << "SSL session started." << std::endl;
|
||||
|
||||
std::ostream osRequest(&mRequest);
|
||||
|
||||
osRequest <<
|
||||
"GET " << mStrPath << " HTTP/1.0\r\n"
|
||||
"Host: " << mDeqSites[0] << "\r\n"
|
||||
"Accept: */*\r\n" // YYY Do we need this line?
|
||||
"Connection: close\r\n\r\n";
|
||||
|
||||
boost::asio::async_write(
|
||||
mSocketSsl,
|
||||
mRequest,
|
||||
boost::bind(&HttpsClient::ShandleWrite,
|
||||
shared_from_this(),
|
||||
boost::asio::placeholders::error));
|
||||
}
|
||||
}
|
||||
|
||||
void HttpsClient::handleWrite(const boost::system::error_code& ecResult)
|
||||
{
|
||||
if (!mShutdown)
|
||||
mShutdown = ecResult;
|
||||
|
||||
if (mShutdown)
|
||||
{
|
||||
std::cerr << "Write error: " << mShutdown.message() << std::endl;
|
||||
|
||||
invokeComplete(mShutdown);
|
||||
}
|
||||
else
|
||||
{
|
||||
// std::cerr << "Wrote." << std::endl;
|
||||
|
||||
boost::asio::async_read(
|
||||
mSocketSsl,
|
||||
mResponse,
|
||||
boost::asio::transfer_all(),
|
||||
boost::bind(&HttpsClient::ShandleData,
|
||||
shared_from_this(),
|
||||
boost::asio::placeholders::error));
|
||||
}
|
||||
}
|
||||
|
||||
void HttpsClient::handleData(const boost::system::error_code& ecResult)
|
||||
{
|
||||
if (!mShutdown)
|
||||
mShutdown = ecResult;
|
||||
|
||||
if (mShutdown && mShutdown != boost::asio::error::eof)
|
||||
{
|
||||
std::cerr << "Read error: " << mShutdown.message() << std::endl;
|
||||
|
||||
invokeComplete(mShutdown);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mShutdown)
|
||||
{
|
||||
// std::cerr << "Complete." << std::endl;
|
||||
nothing();
|
||||
}
|
||||
else
|
||||
{
|
||||
// XXX According to boost example code, this is what we should expect for success.
|
||||
std::cerr << "Complete, no eof." << std::endl;
|
||||
}
|
||||
|
||||
parseData();
|
||||
}
|
||||
}
|
||||
|
||||
// Call cancel the deadline timer and invoke the completion routine.
|
||||
void HttpsClient::invokeComplete(const boost::system::error_code& ecResult, std::string strData)
|
||||
{
|
||||
boost::system::error_code ecCancel;
|
||||
|
||||
(void) mDeadline.cancel(ecCancel);
|
||||
|
||||
if (ecCancel)
|
||||
{
|
||||
std::cerr << "Deadline cancel error: " << ecCancel.message() << std::endl;
|
||||
}
|
||||
|
||||
mDeqSites.pop_front();
|
||||
|
||||
if (mDeqSites.empty())
|
||||
{
|
||||
mComplete(ecResult ? ecResult : ecCancel, strData);
|
||||
}
|
||||
else
|
||||
{
|
||||
httpsNext();
|
||||
}
|
||||
}
|
||||
|
||||
void HttpsClient::parseData()
|
||||
{
|
||||
std::string strData((std::istreambuf_iterator<char>(&mResponse)), std::istreambuf_iterator<char>());
|
||||
|
||||
static boost::regex reStatus("\\`HTTP/1\\S+ (\\d{3}) .*\\'"); // HTTP/1.1 200 OK
|
||||
static boost::regex reBody("\\`(?:.*?\\r\\n\\r\\n){1}(.*)\\'");
|
||||
|
||||
boost::smatch smMatch;
|
||||
|
||||
bool bMatch = boost::regex_match(strData, smMatch, reStatus) // Match status code.
|
||||
&& !smMatch[1].compare("200")
|
||||
&& boost::regex_match(strData, smMatch, reBody); // Match body.
|
||||
|
||||
// std::cerr << "Data:" << strData << std::endl;
|
||||
// std::cerr << "Match: " << bMatch << std::endl;
|
||||
// std::cerr << "Body:" << smMatch[1] << std::endl;
|
||||
|
||||
if (bMatch)
|
||||
{
|
||||
boost::system::error_code noErr;
|
||||
|
||||
invokeComplete(noErr, smMatch[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// XXX Use our own error code.
|
||||
invokeComplete(boost::system::error_code(errc::bad_address, system_category()));
|
||||
}
|
||||
}
|
||||
|
||||
void HttpsClient::httpsGet(
|
||||
boost::asio::io_service& io_service,
|
||||
std::deque<std::string> deqSites,
|
||||
const unsigned short port,
|
||||
const std::string& strPath,
|
||||
std::size_t responseMax,
|
||||
boost::posix_time::time_duration timeout,
|
||||
boost::function<void(const boost::system::error_code& ecResult, std::string& strData)> complete) {
|
||||
|
||||
boost::shared_ptr<HttpsClient> client(new HttpsClient(io_service, port, strPath, responseMax));
|
||||
|
||||
client->httpsGet(deqSites, timeout, complete);
|
||||
}
|
||||
|
||||
void HttpsClient::httpsGet(
|
||||
boost::asio::io_service& io_service,
|
||||
std::string strSite,
|
||||
const unsigned short port,
|
||||
const std::string& strPath,
|
||||
std::size_t responseMax,
|
||||
boost::posix_time::time_duration timeout,
|
||||
boost::function<void(const boost::system::error_code& ecResult, std::string& strData)> complete) {
|
||||
|
||||
std::deque<std::string> deqSites(1, strSite);
|
||||
|
||||
boost::shared_ptr<HttpsClient> client(new HttpsClient(io_service, port, strPath, responseMax));
|
||||
|
||||
client->httpsGet(deqSites, timeout, complete);
|
||||
}
|
||||
|
||||
bool HttpsClient::httpsParseUrl(const std::string& strUrl, std::string& strDomain, std::string& strPath)
|
||||
{
|
||||
static boost::regex reUrl("(?i)\\`\\s*https://([^/]+)(/.*)\\s*\\'"); // https://DOMAINPATH
|
||||
|
||||
boost::smatch smMatch;
|
||||
|
||||
bool bMatch = boost::regex_match(strUrl, smMatch, reUrl); // Match status code.
|
||||
|
||||
if (bMatch)
|
||||
{
|
||||
strDomain = smMatch[1];
|
||||
strPath = smMatch[2];
|
||||
}
|
||||
// std::cerr << strUrl << " : " << bMatch << " : '" << strDomain << "' : '" << strPath << "'" << std::endl;
|
||||
|
||||
return bMatch;
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
106
src/cpp/ripple/HttpsClient.h
Normal file
106
src/cpp/ripple/HttpsClient.h
Normal file
@@ -0,0 +1,106 @@
|
||||
#ifndef _HTTPS_CLIENT_
|
||||
#define _HTTPS_CLIENT_
|
||||
|
||||
#include <deque>
|
||||
#include <string>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time_types.hpp>
|
||||
#include <boost/enable_shared_from_this.hpp>
|
||||
#include <boost/function.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
|
||||
//
|
||||
// Async https client.
|
||||
//
|
||||
|
||||
class HttpsClient : public boost::enable_shared_from_this<HttpsClient>
|
||||
{
|
||||
private:
|
||||
typedef boost::shared_ptr<HttpsClient> pointer;
|
||||
|
||||
boost::asio::ssl::context mCtx;
|
||||
boost::asio::ip::tcp::resolver mResolver;
|
||||
boost::shared_ptr<boost::asio::ip::tcp::resolver::query> mQuery;
|
||||
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> mSocketSsl;
|
||||
boost::asio::streambuf mRequest;
|
||||
boost::asio::streambuf mResponse;
|
||||
const std::string mStrPath;
|
||||
const unsigned short mPort;
|
||||
boost::function<void(const boost::system::error_code& ecResult, std::string& strData)> mComplete;
|
||||
|
||||
boost::asio::deadline_timer mDeadline;
|
||||
|
||||
// If not success, we are shutting down.
|
||||
boost::system::error_code mShutdown;
|
||||
|
||||
std::deque<std::string> mDeqSites;
|
||||
boost::posix_time::time_duration mTimeout;
|
||||
|
||||
void handleDeadline(const boost::system::error_code& ecResult);
|
||||
static void ShandleDeadline(pointer This, const boost::system::error_code& ecResult)
|
||||
{ This->handleDeadline(ecResult); }
|
||||
|
||||
void handleResolve(const boost::system::error_code& ecResult, boost::asio::ip::tcp::resolver::iterator endpoint_iterator);
|
||||
static void ShandleResolve(pointer This, const boost::system::error_code& ecResult, boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
|
||||
{ This->handleResolve(ecResult, endpoint_iterator); }
|
||||
|
||||
void handleConnect(const boost::system::error_code& ecResult);
|
||||
static void ShandleConnect(pointer This, const boost::system::error_code& ecResult)
|
||||
{ This->handleConnect(ecResult); }
|
||||
|
||||
void handleRequest(const boost::system::error_code& ecResult);
|
||||
static void ShandleRequest(pointer This, const boost::system::error_code& ecResult)
|
||||
{ This->handleRequest(ecResult); }
|
||||
|
||||
void handleWrite(const boost::system::error_code& ecResult);
|
||||
static void ShandleWrite(pointer This, const boost::system::error_code& ecResult)
|
||||
{ This->handleWrite(ecResult); }
|
||||
|
||||
void handleData(const boost::system::error_code& ecResult);
|
||||
static void ShandleData(pointer This, const boost::system::error_code& ecResult)
|
||||
{ This->handleData(ecResult); }
|
||||
|
||||
void parseData();
|
||||
void httpsNext();
|
||||
|
||||
void invokeComplete(const boost::system::error_code& ecResult, std::string strData = "");
|
||||
|
||||
public:
|
||||
|
||||
HttpsClient(
|
||||
boost::asio::io_service& io_service,
|
||||
const unsigned short port,
|
||||
const std::string& strPath,
|
||||
std::size_t responseMax
|
||||
);
|
||||
|
||||
void httpsGet(
|
||||
std::deque<std::string> deqSites,
|
||||
boost::posix_time::time_duration timeout,
|
||||
boost::function<void(const boost::system::error_code& ecResult, std::string& strData)> complete);
|
||||
|
||||
static void httpsGet(
|
||||
boost::asio::io_service& io_service,
|
||||
std::deque<std::string> deqSites,
|
||||
const unsigned short port,
|
||||
const std::string& strPath,
|
||||
std::size_t responseMax,
|
||||
boost::posix_time::time_duration timeout,
|
||||
boost::function<void(const boost::system::error_code& ecResult, std::string& strData)> complete);
|
||||
|
||||
static void httpsGet(
|
||||
boost::asio::io_service& io_service,
|
||||
std::string strSite,
|
||||
const unsigned short port,
|
||||
const std::string& strPath,
|
||||
std::size_t responseMax,
|
||||
boost::posix_time::time_duration timeout,
|
||||
boost::function<void(const boost::system::error_code& ecResult, std::string& strData)> complete);
|
||||
|
||||
static bool httpsParseUrl(const std::string& strUrl, std::string& strDomain, std::string& strPath);
|
||||
};
|
||||
#endif
|
||||
// vim:ts=4
|
||||
15
src/cpp/ripple/InstanceCounter.cpp
Normal file
15
src/cpp/ripple/InstanceCounter.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#include "InstanceCounter.h"
|
||||
|
||||
InstanceType* InstanceType::sHeadInstance = NULL;
|
||||
|
||||
std::vector<InstanceType::InstanceCount> InstanceType::getInstanceCounts(int min)
|
||||
{
|
||||
std::vector<InstanceCount> ret;
|
||||
for (InstanceType* i = sHeadInstance; i != NULL; i = i->mNextInstance)
|
||||
{
|
||||
int c = i->getCount();
|
||||
if (c >= min)
|
||||
ret.push_back(InstanceCount(i->getName(), c));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
80
src/cpp/ripple/InstanceCounter.h
Normal file
80
src/cpp/ripple/InstanceCounter.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#ifndef INSTANCE_COUNTER__H
|
||||
#define INSTANCE_COUNTER__H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/thread/mutex.hpp>
|
||||
|
||||
#define DEFINE_INSTANCE(x) \
|
||||
extern InstanceType IT_##x; \
|
||||
class Instance_##x : private Instance \
|
||||
{ \
|
||||
protected: \
|
||||
Instance_##x() : Instance(IT_##x) { ; } \
|
||||
Instance_##x(const Instance_##x &) : \
|
||||
Instance(IT_##x) { ; } \
|
||||
Instance_##x& operator=(const Instance_##x&) \
|
||||
{ return *this; } \
|
||||
}
|
||||
|
||||
#define DECLARE_INSTANCE(x) \
|
||||
InstanceType IT_##x(#x);
|
||||
|
||||
#define IS_INSTANCE(x) Instance_##x
|
||||
|
||||
class InstanceType
|
||||
{
|
||||
protected:
|
||||
int mInstances;
|
||||
std::string mName;
|
||||
boost::mutex mLock;
|
||||
|
||||
InstanceType* mNextInstance;
|
||||
static InstanceType* sHeadInstance;
|
||||
|
||||
public:
|
||||
typedef std::pair<std::string, int> InstanceCount;
|
||||
|
||||
InstanceType(const char *n) : mInstances(0), mName(n)
|
||||
{
|
||||
mNextInstance = sHeadInstance;
|
||||
sHeadInstance = this;
|
||||
}
|
||||
|
||||
void addInstance()
|
||||
{
|
||||
mLock.lock();
|
||||
++mInstances;
|
||||
mLock.unlock();
|
||||
}
|
||||
void decInstance()
|
||||
{
|
||||
mLock.lock();
|
||||
--mInstances;
|
||||
mLock.unlock();
|
||||
}
|
||||
int getCount()
|
||||
{
|
||||
boost::mutex::scoped_lock(mLock);
|
||||
return mInstances;
|
||||
}
|
||||
const std::string& getName()
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
|
||||
static std::vector<InstanceCount> getInstanceCounts(int min = 1);
|
||||
};
|
||||
|
||||
class Instance
|
||||
{
|
||||
protected:
|
||||
InstanceType& mType;
|
||||
|
||||
public:
|
||||
Instance(InstanceType& t) : mType(t) { mType.addInstance(); }
|
||||
~Instance() { mType.decInstance(); }
|
||||
};
|
||||
|
||||
#endif
|
||||
202
src/cpp/ripple/Interpreter.cpp
Normal file
202
src/cpp/ripple/Interpreter.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
#include "Interpreter.h"
|
||||
#include "Operation.h"
|
||||
#include "Config.h"
|
||||
|
||||
/*
|
||||
We also need to charge for each op
|
||||
|
||||
*/
|
||||
|
||||
namespace Script {
|
||||
|
||||
Interpreter::Interpreter()
|
||||
{
|
||||
mContract=NULL;
|
||||
mCode=NULL;
|
||||
mInstructionPointer=0;
|
||||
mTotalFee=0;
|
||||
|
||||
mInBlock=false;
|
||||
mBlockSuccess=true;
|
||||
mBlockJump=0;
|
||||
|
||||
mFunctionTable.resize(NUM_OF_OPS);
|
||||
|
||||
mFunctionTable[INT_OP]=new IntOp();
|
||||
mFunctionTable[FLOAT_OP]=new FloatOp();
|
||||
mFunctionTable[UINT160_OP]=new Uint160Op();
|
||||
mFunctionTable[BOOL_OP]=new Uint160Op();
|
||||
mFunctionTable[PATH_OP]=new Uint160Op();
|
||||
|
||||
mFunctionTable[ADD_OP]=new AddOp();
|
||||
mFunctionTable[SUB_OP]=new SubOp();
|
||||
mFunctionTable[MUL_OP]=new MulOp();
|
||||
mFunctionTable[DIV_OP]=new DivOp();
|
||||
mFunctionTable[MOD_OP]=new ModOp();
|
||||
mFunctionTable[GTR_OP]=new GtrOp();
|
||||
mFunctionTable[LESS_OP]=new LessOp();
|
||||
mFunctionTable[EQUAL_OP]=new SubOp();
|
||||
mFunctionTable[NOT_EQUAL_OP]=new SubOp();
|
||||
mFunctionTable[AND_OP]=new SubOp();
|
||||
mFunctionTable[OR_OP]=new SubOp();
|
||||
mFunctionTable[NOT_OP]=new SubOp();
|
||||
mFunctionTable[JUMP_OP]=new SubOp();
|
||||
mFunctionTable[JUMPIF_OP]=new SubOp();
|
||||
mFunctionTable[STOP_OP]=new SubOp();
|
||||
mFunctionTable[CANCEL_OP]=new SubOp();
|
||||
mFunctionTable[BLOCK_OP]=new SubOp();
|
||||
mFunctionTable[BLOCK_END_OP]=new SubOp();
|
||||
mFunctionTable[SEND_XRP_OP]=new SendXRPOp();
|
||||
/*
|
||||
mFunctionTable[SEND_OP]=new SendOp();
|
||||
mFunctionTable[REMOVE_CONTRACT_OP]=new SubOp();
|
||||
mFunctionTable[FEE_OP]=new SubOp();
|
||||
mFunctionTable[CHANGE_CONTRACT_OWNER_OP]=new SubOp();
|
||||
mFunctionTable[STOP_REMOVE_OP]=new SubOp();
|
||||
mFunctionTable[SET_DATA_OP]=new SubOp();
|
||||
mFunctionTable[GET_DATA_OP]=new SubOp();
|
||||
mFunctionTable[GET_NUM_DATA_OP]=new SubOp();
|
||||
mFunctionTable[SET_REGISTER_OP]=new SubOp();
|
||||
mFunctionTable[GET_REGISTER_OP]=new SubOp();
|
||||
mFunctionTable[GET_ISSUER_ID_OP]=new SubOp();
|
||||
mFunctionTable[GET_OWNER_ID_OP]=new SubOp();
|
||||
mFunctionTable[GET_LEDGER_TIME_OP]=new SubOp();
|
||||
mFunctionTable[GET_LEDGER_NUM_OP]=new SubOp();
|
||||
mFunctionTable[GET_RAND_FLOAT_OP]=new SubOp();
|
||||
mFunctionTable[GET_XRP_ESCROWED_OP]=new SubOp();
|
||||
mFunctionTable[GET_RIPPLE_ESCROWED_OP]=new SubOp();
|
||||
mFunctionTable[GET_RIPPLE_ESCROWED_CURRENCY_OP]=new SubOp();
|
||||
mFunctionTable[GET_RIPPLE_ESCROWED_ISSUER]=new GetRippleEscrowedIssuerOp();
|
||||
mFunctionTable[GET_ACCEPT_DATA_OP]=new AcceptDataOp();
|
||||
mFunctionTable[GET_ACCEPTOR_ID_OP]=new GetAcceptorIDOp();
|
||||
mFunctionTable[GET_CONTRACT_ID_OP]=new GetContractIDOp();
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
Data::pointer Interpreter::popStack()
|
||||
{
|
||||
if(mStack.size())
|
||||
{
|
||||
Data::pointer item=mStack[mStack.size()-1];
|
||||
mStack.pop_back();
|
||||
return(item);
|
||||
}else
|
||||
{
|
||||
return(Data::pointer(new ErrorData()));
|
||||
}
|
||||
}
|
||||
|
||||
void Interpreter::pushStack(Data::pointer data)
|
||||
{
|
||||
mStack.push_back(data);
|
||||
}
|
||||
|
||||
|
||||
// offset is where to jump to if the block fails
|
||||
bool Interpreter::startBlock(int offset)
|
||||
{
|
||||
if(mInBlock) return(false); // can't nest blocks
|
||||
mBlockSuccess=true;
|
||||
mInBlock=true;
|
||||
mBlockJump=offset+mInstructionPointer;
|
||||
return(true);
|
||||
}
|
||||
|
||||
bool Interpreter::endBlock()
|
||||
{
|
||||
if(!mInBlock) return(false);
|
||||
mInBlock=false;
|
||||
mBlockJump=0;
|
||||
pushStack(Data::pointer(new BoolData(mBlockSuccess)));
|
||||
return(true);
|
||||
}
|
||||
|
||||
TER Interpreter::interpret(Contract* contract,const SerializedTransaction& txn,std::vector<unsigned char>& code)
|
||||
{
|
||||
mContract=contract;
|
||||
mCode=&code;
|
||||
mTotalFee=0;
|
||||
mInstructionPointer=0;
|
||||
while(mInstructionPointer<code.size())
|
||||
{
|
||||
unsigned int fun=(*mCode)[mInstructionPointer];
|
||||
mInstructionPointer++;
|
||||
|
||||
if(fun>=mFunctionTable.size())
|
||||
{
|
||||
// TODO: log
|
||||
return(temMALFORMED); // TODO: is this actually what we want to do?
|
||||
}
|
||||
|
||||
mTotalFee += mFunctionTable[ fun ]->getFee();
|
||||
if(mTotalFee>txn.getTransactionFee().getNValue())
|
||||
{
|
||||
// TODO: log
|
||||
return(telINSUF_FEE_P);
|
||||
}else
|
||||
{
|
||||
if(!mFunctionTable[ fun ]->work(this))
|
||||
{
|
||||
// TODO: log
|
||||
return(temMALFORMED); // TODO: is this actually what we want to do?
|
||||
}
|
||||
}
|
||||
}
|
||||
return(tesSUCCESS);
|
||||
}
|
||||
|
||||
|
||||
Data::pointer Interpreter::getIntData()
|
||||
{
|
||||
int value=0; // TODO
|
||||
mInstructionPointer += 4;
|
||||
return(Data::pointer(new IntData(value)));
|
||||
}
|
||||
|
||||
Data::pointer Interpreter::getFloatData()
|
||||
{
|
||||
float value=0; // TODO
|
||||
mInstructionPointer += 4;
|
||||
return(Data::pointer(new FloatData(value)));
|
||||
}
|
||||
|
||||
Data::pointer Interpreter::getUint160Data()
|
||||
{
|
||||
uint160 value; // TODO
|
||||
mInstructionPointer += 20;
|
||||
return(Data::pointer(new Uint160Data(value)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Interpreter::jumpTo(int offset)
|
||||
{
|
||||
mInstructionPointer += offset;
|
||||
if( (mInstructionPointer<0) || (mInstructionPointer>mCode->size()) )
|
||||
{
|
||||
mInstructionPointer -= offset;
|
||||
return(false);
|
||||
}
|
||||
return(true);
|
||||
}
|
||||
|
||||
void Interpreter::stop()
|
||||
{
|
||||
mInstructionPointer=mCode->size();
|
||||
}
|
||||
|
||||
Data::pointer Interpreter::getContractData(int index)
|
||||
{
|
||||
return(Data::pointer(new ErrorData()));
|
||||
}
|
||||
|
||||
bool Interpreter::canSign(const uint160& signer)
|
||||
{
|
||||
return(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
} // end namespace
|
||||
82
src/cpp/ripple/Interpreter.h
Normal file
82
src/cpp/ripple/Interpreter.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#ifndef __INTERPRETER__
|
||||
#define __INTERPRETER__
|
||||
|
||||
#include "uint256.h"
|
||||
#include "Contract.h"
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <vector>
|
||||
#include "ScriptData.h"
|
||||
#include "TransactionEngine.h"
|
||||
|
||||
namespace Script {
|
||||
|
||||
class Operation;
|
||||
// Contracts are non typed have variable data types
|
||||
|
||||
class Interpreter
|
||||
{
|
||||
std::vector<Operation*> mFunctionTable;
|
||||
|
||||
std::vector<Data::pointer> mStack;
|
||||
|
||||
Contract* mContract;
|
||||
std::vector<unsigned char>* mCode;
|
||||
unsigned int mInstructionPointer;
|
||||
int mTotalFee;
|
||||
|
||||
bool mInBlock;
|
||||
int mBlockJump;
|
||||
bool mBlockSuccess;
|
||||
|
||||
public:
|
||||
|
||||
|
||||
enum { INT_OP=1,FLOAT_OP,UINT160_OP,BOOL_OP,PATH_OP,
|
||||
ADD_OP,SUB_OP,MUL_OP,DIV_OP,MOD_OP,
|
||||
GTR_OP,LESS_OP,EQUAL_OP,NOT_EQUAL_OP,
|
||||
AND_OP,OR_OP,NOT_OP,
|
||||
JUMP_OP, JUMPIF_OP,
|
||||
STOP_OP, CANCEL_OP,
|
||||
|
||||
BLOCK_OP, BLOCK_END_OP,
|
||||
SEND_XRP_OP,SEND_OP,REMOVE_CONTRACT_OP,FEE_OP,CHANGE_CONTRACT_OWNER_OP,
|
||||
STOP_REMOVE_OP,
|
||||
SET_DATA_OP,GET_DATA_OP, GET_NUM_DATA_OP,
|
||||
SET_REGISTER_OP,GET_REGISTER_OP,
|
||||
GET_ISSUER_ID_OP, GET_OWNER_ID_OP, GET_LEDGER_TIME_OP, GET_LEDGER_NUM_OP, GET_RAND_FLOAT_OP,
|
||||
GET_XRP_ESCROWED_OP, GET_RIPPLE_ESCROWED_OP, GET_RIPPLE_ESCROWED_CURRENCY_OP, GET_RIPPLE_ESCROWED_ISSUER,
|
||||
GET_ACCEPT_DATA_OP, GET_ACCEPTOR_ID_OP, GET_CONTRACT_ID_OP,
|
||||
NUM_OF_OPS };
|
||||
|
||||
Interpreter();
|
||||
|
||||
// returns a TransactionEngineResult
|
||||
TER interpret(Contract* contract,const SerializedTransaction& txn,std::vector<unsigned char>& code);
|
||||
|
||||
void stop();
|
||||
|
||||
bool canSign(const uint160& signer);
|
||||
|
||||
int getInstructionPointer(){ return(mInstructionPointer); }
|
||||
void setInstructionPointer(int n){ mInstructionPointer=n;}
|
||||
|
||||
Data::pointer popStack();
|
||||
void pushStack(Data::pointer data);
|
||||
bool jumpTo(int offset);
|
||||
|
||||
bool startBlock(int offset);
|
||||
bool endBlock();
|
||||
|
||||
|
||||
Data::pointer getIntData();
|
||||
Data::pointer getFloatData();
|
||||
Data::pointer getUint160Data();
|
||||
Data::pointer getAcceptData(int index);
|
||||
Data::pointer getContractData(int index);
|
||||
|
||||
|
||||
};
|
||||
|
||||
} // end namespace
|
||||
|
||||
#endif
|
||||
181
src/cpp/ripple/JobQueue.cpp
Normal file
181
src/cpp/ripple/JobQueue.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
#include "JobQueue.h"
|
||||
|
||||
#include <boost/make_shared.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
#include "Log.h"
|
||||
|
||||
SETUP_LOG();
|
||||
|
||||
const char* Job::toString(JobType t)
|
||||
{
|
||||
switch(t)
|
||||
{
|
||||
case jtINVALID: return "invalid";
|
||||
case jtVALIDATION_ut: return "untrustedValidation";
|
||||
case jtTRANSACTION: return "transaction";
|
||||
case jtPROPOSAL_ut: return "untrustedProposal";
|
||||
case jtVALIDATION_t: return "trustedValidation";
|
||||
case jtPROPOSAL_t: return "trustedProposal";
|
||||
case jtADMIN: return "administration";
|
||||
case jtDEATH: return "jobOfDeath";
|
||||
default: assert(false); return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
bool Job::operator<(const Job& j) const
|
||||
{ // These comparison operators make the jobs sort in priority order in the job set
|
||||
if (mType < j.mType)
|
||||
return true;
|
||||
if (mType > j.mType)
|
||||
return false;
|
||||
return mJobIndex < j.mJobIndex;
|
||||
}
|
||||
|
||||
bool Job::operator<=(const Job& j) const
|
||||
{
|
||||
if (mType < j.mType)
|
||||
return true;
|
||||
if (mType > j.mType)
|
||||
return false;
|
||||
return mJobIndex <= j.mJobIndex;
|
||||
}
|
||||
|
||||
bool Job::operator>(const Job& j) const
|
||||
{
|
||||
if (mType < j.mType)
|
||||
return false;
|
||||
if (mType > j.mType)
|
||||
return true;
|
||||
return mJobIndex > j.mJobIndex;
|
||||
}
|
||||
|
||||
bool Job::operator>=(const Job& j) const
|
||||
{
|
||||
if (mType < j.mType)
|
||||
return false;
|
||||
if (mType > j.mType)
|
||||
return true;
|
||||
return mJobIndex >= j.mJobIndex;
|
||||
}
|
||||
|
||||
void JobQueue::addJob(JobType type, const boost::function<void(Job&)>& jobFunc)
|
||||
{
|
||||
assert(type != jtINVALID);
|
||||
|
||||
boost::mutex::scoped_lock sl(mJobLock);
|
||||
assert(mThreadCount != 0); // do not add jobs to a queue with no threads
|
||||
|
||||
mJobSet.insert(Job(type, ++mLastJob, jobFunc));
|
||||
++mJobCounts[type];
|
||||
mJobCond.notify_one();
|
||||
}
|
||||
|
||||
int JobQueue::getJobCount(JobType t)
|
||||
{
|
||||
boost::mutex::scoped_lock sl(mJobLock);
|
||||
|
||||
std::map<JobType, int>::iterator c = mJobCounts.find(t);
|
||||
return (c == mJobCounts.end()) ? 0 : c->second;
|
||||
}
|
||||
|
||||
int JobQueue::getJobCountGE(JobType t)
|
||||
{ // return the number of jobs at this priority level or greater
|
||||
int ret = 0;
|
||||
|
||||
boost::mutex::scoped_lock sl(mJobLock);
|
||||
|
||||
typedef std::pair<JobType, int> jt_int_pair;
|
||||
BOOST_FOREACH(const jt_int_pair& it, mJobCounts)
|
||||
if (it.first >= t)
|
||||
ret += it.second;
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector< std::pair<JobType, int> > JobQueue::getJobCounts()
|
||||
{ // return all jobs at all priority levels
|
||||
std::vector< std::pair<JobType, int> > ret;
|
||||
|
||||
boost::mutex::scoped_lock sl(mJobLock);
|
||||
ret.reserve(mJobCounts.size());
|
||||
|
||||
typedef std::pair<JobType, int> jt_int_pair;
|
||||
BOOST_FOREACH(const jt_int_pair& it, mJobCounts)
|
||||
ret.push_back(it);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void JobQueue::shutdown()
|
||||
{ // shut down the job queue without completing pending jobs
|
||||
cLog(lsINFO) << "Job queue shutting down";
|
||||
boost::mutex::scoped_lock sl(mJobLock);
|
||||
mShuttingDown = true;
|
||||
mJobCond.notify_all();
|
||||
while (mThreadCount != 0)
|
||||
mJobCond.wait(sl);
|
||||
}
|
||||
|
||||
void JobQueue::setThreadCount(int c)
|
||||
{ // set the number of thread serving the job queue to precisely this number
|
||||
if (c == 0)
|
||||
{
|
||||
c = boost::thread::hardware_concurrency();
|
||||
if (c < 2)
|
||||
c = 2;
|
||||
cLog(lsINFO) << "Auto-tuning to " << c << " validation/transaction/proposal threads";
|
||||
}
|
||||
|
||||
boost::mutex::scoped_lock sl(mJobLock);
|
||||
|
||||
while (mJobCounts[jtDEATH] != 0)
|
||||
mJobCond.wait(sl);
|
||||
|
||||
while (mThreadCount < c)
|
||||
{
|
||||
++mThreadCount;
|
||||
boost::thread t(boost::bind(&JobQueue::threadEntry, this));
|
||||
t.detach();
|
||||
}
|
||||
while (mThreadCount > c)
|
||||
{
|
||||
if (mJobCounts[jtDEATH] != 0)
|
||||
mJobCond.wait(sl);
|
||||
else
|
||||
{
|
||||
mJobSet.insert(Job(jtDEATH, 0));
|
||||
++mJobCounts[jtDEATH];
|
||||
}
|
||||
}
|
||||
mJobCond.notify_one(); // in case we sucked up someone else's signal
|
||||
}
|
||||
|
||||
void JobQueue::threadEntry()
|
||||
{ // do jobs until asked to stop
|
||||
boost::mutex::scoped_lock sl(mJobLock);
|
||||
while (1)
|
||||
{
|
||||
while (mJobSet.empty() && !mShuttingDown)
|
||||
mJobCond.wait(sl);
|
||||
|
||||
if (mShuttingDown)
|
||||
break;
|
||||
|
||||
std::set<Job>::iterator it = mJobSet.begin();
|
||||
Job job(*it);
|
||||
mJobSet.erase(it);
|
||||
--mJobCounts[job.getType()];
|
||||
|
||||
if (job.getType() == jtDEATH)
|
||||
break;
|
||||
|
||||
sl.unlock();
|
||||
cLog(lsDEBUG) << "Doing " << Job::toString(job.getType()) << " job";
|
||||
job.doJob();
|
||||
sl.lock();
|
||||
}
|
||||
--mThreadCount;
|
||||
mJobCond.notify_all();
|
||||
}
|
||||
83
src/cpp/ripple/JobQueue.h
Normal file
83
src/cpp/ripple/JobQueue.h
Normal file
@@ -0,0 +1,83 @@
|
||||
#ifndef JOB_QUEUE__H
|
||||
#define JOB_QUEUE__H
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/thread/mutex.hpp>
|
||||
#include <boost/thread/condition_variable.hpp>
|
||||
#include <boost/function.hpp>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
// Note that this queue should only be used for CPU-bound jobs
|
||||
// It is primarily intended for signature checking
|
||||
|
||||
enum JobType
|
||||
{ // must be in priority order, low to high
|
||||
jtINVALID,
|
||||
jtVALIDATION_ut,
|
||||
jtTRANSACTION,
|
||||
jtPROPOSAL_ut,
|
||||
jtVALIDATION_t,
|
||||
jtPROPOSAL_t,
|
||||
jtADMIN,
|
||||
jtDEATH, // job of death, used internally
|
||||
};
|
||||
|
||||
class Job
|
||||
{
|
||||
protected:
|
||||
JobType mType;
|
||||
uint64 mJobIndex;
|
||||
boost::function<void(Job&)> mJob;
|
||||
|
||||
public:
|
||||
Job() : mType(jtINVALID), mJobIndex(0) { ; }
|
||||
Job(JobType type, uint64 index) : mType(type), mJobIndex(index) { ; }
|
||||
|
||||
Job(JobType type, uint64 index, const boost::function<void(Job&)>& job)
|
||||
: mType(type), mJobIndex(index), mJob(job) { ; }
|
||||
|
||||
JobType getType() const { return mType; }
|
||||
void doJob(void) { mJob(*this); }
|
||||
|
||||
bool operator<(const Job& j) const;
|
||||
bool operator>(const Job& j) const;
|
||||
bool operator<=(const Job& j) const;
|
||||
bool operator>=(const Job& j) const;
|
||||
|
||||
static const char* toString(JobType);
|
||||
};
|
||||
|
||||
class JobQueue
|
||||
{
|
||||
protected:
|
||||
boost::mutex mJobLock;
|
||||
boost::condition_variable mJobCond;
|
||||
|
||||
uint64 mLastJob;
|
||||
std::set<Job> mJobSet;
|
||||
std::map<JobType, int> mJobCounts;
|
||||
int mThreadCount;
|
||||
bool mShuttingDown;
|
||||
|
||||
|
||||
void threadEntry(void);
|
||||
|
||||
public:
|
||||
|
||||
JobQueue() : mLastJob(0), mThreadCount(0), mShuttingDown(false) { ; }
|
||||
|
||||
void addJob(JobType type, const boost::function<void(Job&)>& job);
|
||||
|
||||
int getJobCount(JobType t); // Jobs at this priority
|
||||
int getJobCountGE(JobType t); // All jobs at or greater than this priority
|
||||
std::vector< std::pair<JobType, int> > getJobCounts();
|
||||
|
||||
void shutdown();
|
||||
void setThreadCount(int c = 0);
|
||||
};
|
||||
|
||||
#endif
|
||||
1093
src/cpp/ripple/Ledger.cpp
Normal file
1093
src/cpp/ripple/Ledger.cpp
Normal file
File diff suppressed because it is too large
Load Diff
315
src/cpp/ripple/Ledger.h
Normal file
315
src/cpp/ripple/Ledger.h
Normal file
@@ -0,0 +1,315 @@
|
||||
#ifndef __LEDGER__
|
||||
#define __LEDGER__
|
||||
|
||||
#include <map>
|
||||
#include <list>
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/enable_shared_from_this.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
|
||||
#include "../json/value.h"
|
||||
|
||||
#include "Transaction.h"
|
||||
#include "TransactionMeta.h"
|
||||
#include "AccountState.h"
|
||||
#include "RippleState.h"
|
||||
#include "NicknameState.h"
|
||||
#include "types.h"
|
||||
#include "BitcoinUtil.h"
|
||||
#include "SHAMap.h"
|
||||
#include "InstanceCounter.h"
|
||||
|
||||
enum LedgerStateParms
|
||||
{
|
||||
lepNONE = 0, // no special flags
|
||||
|
||||
// input flags
|
||||
lepCREATE = 1, // Create if not present
|
||||
|
||||
// output flags
|
||||
lepOKAY = 2, // success
|
||||
lepMISSING = 4, // No node in that slot
|
||||
lepWRONGTYPE = 8, // Node of different type there
|
||||
lepCREATED = 16, // Node was created
|
||||
lepERROR = 32, // error
|
||||
};
|
||||
|
||||
#define LEDGER_JSON_DUMP_TXRP 0x10000000
|
||||
#define LEDGER_JSON_DUMP_STATE 0x20000000
|
||||
#define LEDGER_JSON_FULL 0x40000000
|
||||
|
||||
DEFINE_INSTANCE(Ledger);
|
||||
|
||||
class Ledger : public boost::enable_shared_from_this<Ledger>, public IS_INSTANCE(Ledger)
|
||||
{ // The basic Ledger structure, can be opened, closed, or synching
|
||||
friend class TransactionEngine;
|
||||
public:
|
||||
typedef boost::shared_ptr<Ledger> pointer;
|
||||
typedef const boost::shared_ptr<Ledger>& ref;
|
||||
|
||||
enum TransResult
|
||||
{
|
||||
TR_ERROR = -1,
|
||||
TR_SUCCESS = 0,
|
||||
TR_NOTFOUND = 1,
|
||||
TR_ALREADY = 2,
|
||||
TR_BADTRANS = 3, // the transaction itself is corrupt
|
||||
TR_BADACCT = 4, // one of the accounts is invalid
|
||||
TR_INSUFF = 5, // the sending(apply)/receiving(remove) account is broke
|
||||
TR_PASTASEQ = 6, // account is past this transaction
|
||||
TR_PREASEQ = 7, // account is missing transactions before this
|
||||
TR_BADLSEQ = 8, // ledger too early
|
||||
TR_TOOSMALL = 9, // amount is less than Tx fee
|
||||
};
|
||||
|
||||
// ledger close flags
|
||||
static const uint32 sLCF_NoConsensusTime = 1;
|
||||
|
||||
private:
|
||||
|
||||
uint256 mHash, mParentHash, mTransHash, mAccountHash;
|
||||
uint64 mTotCoins;
|
||||
uint32 mLedgerSeq;
|
||||
uint32 mCloseTime; // when this ledger closed
|
||||
uint32 mParentCloseTime; // when the previous ledger closed
|
||||
int mCloseResolution; // the resolution for this ledger close time (2-120 seconds)
|
||||
uint32 mCloseFlags; // flags indicating how this ledger close took place
|
||||
bool mClosed, mValidHash, mAccepted, mImmutable;
|
||||
|
||||
SHAMap::pointer mTransactionMap, mAccountStateMap;
|
||||
|
||||
mutable boost::recursive_mutex mLock;
|
||||
|
||||
static int sPendingSaves;
|
||||
static boost::recursive_mutex sPendingSaveLock;
|
||||
|
||||
Ledger(const Ledger&); // no implementation
|
||||
Ledger& operator=(const Ledger&); // no implementation
|
||||
|
||||
protected:
|
||||
SLE::pointer getASNode(LedgerStateParms& parms, const uint256& nodeID, LedgerEntryType let);
|
||||
|
||||
static void incPendingSaves();
|
||||
static void decPendingSaves();
|
||||
void saveAcceptedLedger(bool fromConsensus);
|
||||
|
||||
public:
|
||||
Ledger(const RippleAddress& masterID, uint64 startAmount); // used for the starting bootstrap ledger
|
||||
|
||||
Ledger(const uint256 &parentHash, const uint256 &transHash, const uint256 &accountHash,
|
||||
uint64 totCoins, uint32 closeTime, uint32 parentCloseTime, int closeFlags, int closeResolution,
|
||||
uint32 ledgerSeq); // used for database ledgers
|
||||
|
||||
Ledger(const std::vector<unsigned char>& rawLedger);
|
||||
|
||||
Ledger(const std::string& rawLedger);
|
||||
|
||||
Ledger(bool dummy, Ledger& previous); // ledger after this one
|
||||
|
||||
Ledger(Ledger& target, bool isMutable); // snapshot
|
||||
|
||||
static Ledger::pointer getSQL(const std::string& sqlStatement);
|
||||
static Ledger::pointer getLastFullLedger();
|
||||
static int getPendingSaves();
|
||||
|
||||
void updateHash();
|
||||
void setClosed() { mClosed = true; }
|
||||
void setAccepted(uint32 closeTime, int closeResolution, bool correctCloseTime);
|
||||
void setAccepted();
|
||||
void setImmutable() { updateHash(); mImmutable = true; }
|
||||
bool isClosed() { return mClosed; }
|
||||
bool isAccepted() { return mAccepted; }
|
||||
bool isImmutable() { return mImmutable; }
|
||||
|
||||
// ledger signature operations
|
||||
void addRaw(Serializer &s) const;
|
||||
void setRaw(Serializer& s);
|
||||
|
||||
uint256 getHash();
|
||||
const uint256& getParentHash() const { return mParentHash; }
|
||||
const uint256& getTransHash() const { return mTransHash; }
|
||||
const uint256& getAccountHash() const { return mAccountHash; }
|
||||
uint64 getTotalCoins() const { return mTotCoins; }
|
||||
void destroyCoins(uint64 fee) { mTotCoins -= fee; }
|
||||
uint32 getCloseTimeNC() const { return mCloseTime; }
|
||||
uint32 getParentCloseTimeNC() const { return mParentCloseTime; }
|
||||
uint32 getLedgerSeq() const { return mLedgerSeq; }
|
||||
int getCloseResolution() const { return mCloseResolution; }
|
||||
bool getCloseAgree() const { return (mCloseFlags & sLCF_NoConsensusTime) == 0; }
|
||||
|
||||
// close time functions
|
||||
void setCloseTime(uint32 ct) { assert(!mImmutable); mCloseTime = ct; }
|
||||
void setCloseTime(boost::posix_time::ptime);
|
||||
boost::posix_time::ptime getCloseTime() const;
|
||||
|
||||
// low level functions
|
||||
SHAMap::ref peekTransactionMap() { return mTransactionMap; }
|
||||
SHAMap::ref peekAccountStateMap() { return mAccountStateMap; }
|
||||
|
||||
// ledger sync functions
|
||||
void setAcquiring(void);
|
||||
bool isAcquiring(void);
|
||||
bool isAcquiringTx(void);
|
||||
bool isAcquiringAS(void);
|
||||
|
||||
// Transaction Functions
|
||||
bool addTransaction(const uint256& id, const Serializer& txn);
|
||||
bool addTransaction(const uint256& id, const Serializer& txn, const Serializer& metaData);
|
||||
bool hasTransaction(const uint256& TransID) const { return mTransactionMap->hasItem(TransID); }
|
||||
Transaction::pointer getTransaction(const uint256& transID) const;
|
||||
bool getTransaction(const uint256& transID, Transaction::pointer& txn, TransactionMetaSet::pointer& txMeta);
|
||||
static SerializedTransaction::pointer getSTransaction(SHAMapItem::ref, SHAMapTreeNode::TNType);
|
||||
|
||||
// high-level functions
|
||||
AccountState::pointer getAccountState(const RippleAddress& acctID);
|
||||
LedgerStateParms writeBack(LedgerStateParms parms, SLE::ref);
|
||||
SLE::pointer getAccountRoot(const uint160& accountID);
|
||||
SLE::pointer getAccountRoot(const RippleAddress& naAccountID);
|
||||
|
||||
// database functions
|
||||
static Ledger::pointer loadByIndex(uint32 ledgerIndex);
|
||||
static Ledger::pointer loadByHash(const uint256& ledgerHash);
|
||||
void pendSave(bool fromConsensus);
|
||||
|
||||
// next/prev function
|
||||
SLE::pointer getSLE(const uint256& uHash);
|
||||
uint256 getFirstLedgerIndex();
|
||||
uint256 getLastLedgerIndex();
|
||||
uint256 getNextLedgerIndex(const uint256& uHash); // first node >hash
|
||||
uint256 getNextLedgerIndex(const uint256& uHash, const uint256& uEnd); // first node >hash, <end
|
||||
uint256 getPrevLedgerIndex(const uint256& uHash); // last node <hash
|
||||
uint256 getPrevLedgerIndex(const uint256& uHash, const uint256& uBegin); // last node <hash, >begin
|
||||
|
||||
// Ledger hash table function
|
||||
static uint256 getLedgerHashIndex();
|
||||
static uint256 getLedgerHashIndex(uint32 desiredLedgerIndex);
|
||||
static int getLedgerHashOffset(uint32 desiredLedgerIndex);
|
||||
static int getLedgerHashOffset(uint32 desiredLedgerIndex, uint32 currentLedgerIndex);
|
||||
|
||||
// index calculation functions
|
||||
static uint256 getAccountRootIndex(const uint160& uAccountID);
|
||||
|
||||
static uint256 getAccountRootIndex(const RippleAddress& account)
|
||||
{ return getAccountRootIndex(account.getAccountID()); }
|
||||
|
||||
//
|
||||
// Generator Map functions
|
||||
//
|
||||
|
||||
SLE::pointer getGenerator(LedgerStateParms& parms, const uint160& uGeneratorID);
|
||||
|
||||
static uint256 getGeneratorIndex(const uint160& uGeneratorID);
|
||||
|
||||
//
|
||||
// Nickname functions
|
||||
//
|
||||
|
||||
static uint256 getNicknameHash(const std::string& strNickname)
|
||||
{ Serializer s(strNickname); return s.getSHA256(); }
|
||||
|
||||
NicknameState::pointer getNicknameState(const uint256& uNickname);
|
||||
NicknameState::pointer getNicknameState(const std::string& strNickname)
|
||||
{ return getNicknameState(getNicknameHash(strNickname)); }
|
||||
|
||||
SLE::pointer getNickname(LedgerStateParms& parms, const uint256& uNickname);
|
||||
SLE::pointer getNickname(LedgerStateParms& parms, const std::string& strNickname)
|
||||
{ return getNickname(parms, getNicknameHash(strNickname)); }
|
||||
|
||||
static uint256 getNicknameIndex(const uint256& uNickname);
|
||||
|
||||
//
|
||||
// Order book functions
|
||||
//
|
||||
|
||||
// Order book dirs have a base so we can use next to step through them in quality order.
|
||||
static uint256 getBookBase(const uint160& uTakerPaysCurrency, const uint160& uTakerPaysIssuerID,
|
||||
const uint160& uTakerGetsCurrency, const uint160& uTakerGetsIssuerID);
|
||||
|
||||
//
|
||||
// Offer functions
|
||||
//
|
||||
|
||||
SLE::pointer getOffer(LedgerStateParms& parms, const uint256& uIndex);
|
||||
|
||||
SLE::pointer getOffer(const uint256& uIndex)
|
||||
{
|
||||
LedgerStateParms qry = lepNONE;
|
||||
return getOffer(qry, uIndex);
|
||||
}
|
||||
|
||||
SLE::pointer getOffer(LedgerStateParms& parms, const uint160& uAccountID, uint32 uSequence)
|
||||
{ return getOffer(parms, getOfferIndex(uAccountID, uSequence)); }
|
||||
|
||||
// The index of an offer.
|
||||
static uint256 getOfferIndex(const uint160& uAccountID, uint32 uSequence);
|
||||
|
||||
//
|
||||
// Owner functions
|
||||
//
|
||||
|
||||
// All items controlled by an account are here: offers
|
||||
static uint256 getOwnerDirIndex(const uint160& uAccountID);
|
||||
|
||||
//
|
||||
// Directory functions
|
||||
// Directories are doubly linked lists of nodes.
|
||||
|
||||
// Given a directory root and and index compute the index of a node.
|
||||
static uint256 getDirNodeIndex(const uint256& uDirRoot, const uint64 uNodeIndex=0);
|
||||
|
||||
// Return a node: root or normal
|
||||
SLE::pointer getDirNode(LedgerStateParms& parms, const uint256& uNodeIndex);
|
||||
|
||||
//
|
||||
// Quality
|
||||
//
|
||||
|
||||
static uint256 getQualityIndex(const uint256& uBase, const uint64 uNodeDir=0);
|
||||
static uint256 getQualityNext(const uint256& uBase);
|
||||
static uint64 getQuality(const uint256& uBase);
|
||||
|
||||
//
|
||||
// Ripple functions : credit lines
|
||||
//
|
||||
|
||||
// Index of node which is the ripple state between two accounts for a currency.
|
||||
static uint256 getRippleStateIndex(const RippleAddress& naA, const RippleAddress& naB, const uint160& uCurrency);
|
||||
static uint256 getRippleStateIndex(const uint160& uiA, const uint160& uiB, const uint160& uCurrency)
|
||||
{ return getRippleStateIndex(RippleAddress::createAccountID(uiA), RippleAddress::createAccountID(uiB), uCurrency); }
|
||||
|
||||
RippleState::pointer accessRippleState(const uint256& uNode);
|
||||
|
||||
SLE::pointer getRippleState(LedgerStateParms& parms, const uint256& uNode);
|
||||
|
||||
SLE::pointer getRippleState(const uint256& uNode)
|
||||
{
|
||||
LedgerStateParms qry = lepNONE;
|
||||
return getRippleState(qry, uNode);
|
||||
}
|
||||
|
||||
SLE::pointer getRippleState(const RippleAddress& naA, const RippleAddress& naB, const uint160& uCurrency)
|
||||
{ return getRippleState(getRippleStateIndex(naA, naB, uCurrency)); }
|
||||
|
||||
SLE::pointer getRippleState(const uint160& uiA, const uint160& uiB, const uint160& uCurrency)
|
||||
{ return getRippleState(getRippleStateIndex(RippleAddress::createAccountID(uiA), RippleAddress::createAccountID(uiB), uCurrency)); }
|
||||
|
||||
Json::Value getJson(int options);
|
||||
void addJson(Json::Value&, int options);
|
||||
|
||||
bool walkLedger();
|
||||
bool assertSane();
|
||||
};
|
||||
|
||||
inline LedgerStateParms operator|(const LedgerStateParms& l1, const LedgerStateParms& l2)
|
||||
{
|
||||
return static_cast<LedgerStateParms>(static_cast<int>(l1) | static_cast<int>(l2));
|
||||
}
|
||||
|
||||
inline LedgerStateParms operator&(const LedgerStateParms& l1, const LedgerStateParms& l2)
|
||||
{
|
||||
return static_cast<LedgerStateParms>(static_cast<int>(l1) & static_cast<int>(l2));
|
||||
}
|
||||
|
||||
#endif
|
||||
// vim:ts=4
|
||||
615
src/cpp/ripple/LedgerAcquire.cpp
Normal file
615
src/cpp/ripple/LedgerAcquire.cpp
Normal file
@@ -0,0 +1,615 @@
|
||||
|
||||
#include "LedgerAcquire.h"
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/make_shared.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
#include "Application.h"
|
||||
#include "Log.h"
|
||||
#include "SHAMapSync.h"
|
||||
#include "HashPrefixes.h"
|
||||
|
||||
SETUP_LOG();
|
||||
DECLARE_INSTANCE(PeerSet);
|
||||
|
||||
#define LA_DEBUG
|
||||
#define LEDGER_ACQUIRE_TIMEOUT 750
|
||||
#define TRUST_NETWORK
|
||||
|
||||
PeerSet::PeerSet(const uint256& hash, int interval) : mHash(hash), mTimerInterval(interval), mTimeouts(0),
|
||||
mComplete(false), mFailed(false), mProgress(true), mTimer(theApp->getIOService())
|
||||
{
|
||||
assert((mTimerInterval > 10) && (mTimerInterval < 30000));
|
||||
}
|
||||
|
||||
void PeerSet::peerHas(Peer::ref ptr)
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock sl(mLock);
|
||||
if (!mPeers.insert(std::make_pair(ptr->getPeerId(), 0)).second)
|
||||
return;
|
||||
newPeer(ptr);
|
||||
}
|
||||
|
||||
void PeerSet::badPeer(Peer::ref ptr)
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock sl(mLock);
|
||||
mPeers.erase(ptr->getPeerId());
|
||||
}
|
||||
|
||||
void PeerSet::resetTimer()
|
||||
{
|
||||
mTimer.expires_from_now(boost::posix_time::milliseconds(mTimerInterval));
|
||||
mTimer.async_wait(boost::bind(&PeerSet::TimerEntry, pmDowncast(), boost::asio::placeholders::error));
|
||||
}
|
||||
|
||||
void PeerSet::invokeOnTimer()
|
||||
{
|
||||
if (!mProgress)
|
||||
{
|
||||
++mTimeouts;
|
||||
cLog(lsWARNING) << "Timeout(" << mTimeouts << ") pc=" << mPeers.size() << " acquiring " << mHash;
|
||||
onTimer(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
mProgress = false;
|
||||
onTimer(true);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerSet::TimerEntry(boost::weak_ptr<PeerSet> wptr, const boost::system::error_code& result)
|
||||
{
|
||||
if (result == boost::asio::error::operation_aborted)
|
||||
return;
|
||||
boost::shared_ptr<PeerSet> ptr = wptr.lock();
|
||||
if (ptr)
|
||||
ptr->invokeOnTimer();
|
||||
}
|
||||
|
||||
LedgerAcquire::LedgerAcquire(const uint256& hash) : PeerSet(hash, LEDGER_ACQUIRE_TIMEOUT),
|
||||
mHaveBase(false), mHaveState(false), mHaveTransactions(false), mAborted(false), mSignaled(false), mAccept(false)
|
||||
{
|
||||
#ifdef LA_DEBUG
|
||||
cLog(lsTRACE) << "Acquiring ledger " << mHash;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool LedgerAcquire::tryLocal()
|
||||
{ // return value: true = no more work to do
|
||||
HashedObject::pointer node = theApp->getHashedObjectStore().retrieve(mHash);
|
||||
if (!node)
|
||||
return false;
|
||||
|
||||
mLedger = boost::make_shared<Ledger>(strCopy(node->getData()));
|
||||
assert(mLedger->getHash() == mHash);
|
||||
mHaveBase = true;
|
||||
|
||||
if (!mLedger->getTransHash())
|
||||
mHaveTransactions = true;
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
mLedger->peekTransactionMap()->fetchRoot(mLedger->getTransHash());
|
||||
}
|
||||
catch (SHAMapMissingNode&)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (!mLedger->getAccountHash())
|
||||
mHaveState = true;
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
mLedger->peekAccountStateMap()->fetchRoot(mLedger->getAccountHash());
|
||||
}
|
||||
catch (SHAMapMissingNode&)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return mHaveTransactions && mHaveState;
|
||||
}
|
||||
|
||||
void LedgerAcquire::onTimer(bool progress)
|
||||
{
|
||||
if (getTimeouts() > 6)
|
||||
{
|
||||
setFailed();
|
||||
done();
|
||||
}
|
||||
else if (!progress)
|
||||
{
|
||||
if (!getPeerCount())
|
||||
addPeers();
|
||||
else
|
||||
trigger(Peer::pointer(), true);
|
||||
}
|
||||
}
|
||||
|
||||
void LedgerAcquire::addPeers()
|
||||
{
|
||||
std::vector<Peer::pointer> peerList = theApp->getConnectionPool().getPeerVector();
|
||||
|
||||
bool found = false;
|
||||
BOOST_FOREACH(Peer::ref peer, peerList)
|
||||
{
|
||||
if (peer->hasLedger(getHash()))
|
||||
{
|
||||
found = true;
|
||||
peerHas(peer);
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
BOOST_FOREACH(Peer::ref peer, peerList)
|
||||
peerHas(peer);
|
||||
}
|
||||
}
|
||||
|
||||
boost::weak_ptr<PeerSet> LedgerAcquire::pmDowncast()
|
||||
{
|
||||
return boost::shared_polymorphic_downcast<PeerSet>(shared_from_this());
|
||||
}
|
||||
|
||||
void LedgerAcquire::done()
|
||||
{
|
||||
if (mSignaled)
|
||||
return;
|
||||
mSignaled = true;
|
||||
#ifdef LA_DEBUG
|
||||
cLog(lsTRACE) << "Done acquiring ledger " << mHash;
|
||||
#endif
|
||||
std::vector< boost::function<void (LedgerAcquire::pointer)> > triggers;
|
||||
|
||||
setComplete();
|
||||
mLock.lock();
|
||||
triggers = mOnComplete;
|
||||
mOnComplete.clear();
|
||||
mLock.unlock();
|
||||
|
||||
if (mLedger)
|
||||
{
|
||||
if (mAccept)
|
||||
mLedger->setAccepted();
|
||||
theApp->getMasterLedger().storeLedger(mLedger);
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < triggers.size(); ++i)
|
||||
triggers[i](shared_from_this());
|
||||
}
|
||||
|
||||
void LedgerAcquire::addOnComplete(boost::function<void (LedgerAcquire::pointer)> trigger)
|
||||
{
|
||||
mLock.lock();
|
||||
mOnComplete.push_back(trigger);
|
||||
mLock.unlock();
|
||||
}
|
||||
|
||||
void LedgerAcquire::trigger(Peer::ref peer, bool timer)
|
||||
{
|
||||
if (mAborted || mComplete || mFailed)
|
||||
{
|
||||
cLog(lsTRACE) << "Trigger on ledger:" <<
|
||||
(mAborted ? " aborted": "") << (mComplete ? " completed": "") << (mFailed ? " failed" : "");
|
||||
return;
|
||||
}
|
||||
|
||||
if (sLog(lsTRACE))
|
||||
{
|
||||
if (peer)
|
||||
cLog(lsTRACE) << "Trigger acquiring ledger " << mHash << " from " << peer->getIP();
|
||||
else
|
||||
cLog(lsTRACE) << "Trigger acquiring ledger " << mHash;
|
||||
if (mComplete || mFailed)
|
||||
cLog(lsTRACE) << "complete=" << mComplete << " failed=" << mFailed;
|
||||
else
|
||||
cLog(lsTRACE) << "base=" << mHaveBase << " tx=" << mHaveTransactions << " as=" << mHaveState;
|
||||
}
|
||||
|
||||
if (!mHaveBase)
|
||||
{
|
||||
ripple::TMGetLedger tmGL;
|
||||
tmGL.set_ledgerhash(mHash.begin(), mHash.size());
|
||||
tmGL.set_itype(ripple::liBASE);
|
||||
cLog(lsTRACE) << "Sending base request to " << (peer ? "selected peer" : "all peers");
|
||||
sendRequest(tmGL, peer);
|
||||
}
|
||||
|
||||
if (mHaveBase && !mHaveTransactions)
|
||||
{
|
||||
assert(mLedger);
|
||||
if (mLedger->peekTransactionMap()->getHash().isZero())
|
||||
{ // we need the root node
|
||||
ripple::TMGetLedger tmGL;
|
||||
tmGL.set_ledgerhash(mHash.begin(), mHash.size());
|
||||
tmGL.set_ledgerseq(mLedger->getLedgerSeq());
|
||||
tmGL.set_itype(ripple::liTX_NODE);
|
||||
*(tmGL.add_nodeids()) = SHAMapNode().getRawString();
|
||||
cLog(lsTRACE) << "Sending TX root request to " << (peer ? "selected peer" : "all peers");
|
||||
sendRequest(tmGL, peer);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<SHAMapNode> nodeIDs;
|
||||
std::vector<uint256> nodeHashes;
|
||||
TransactionStateSF tFilter(mLedger->getHash(), mLedger->getLedgerSeq());
|
||||
mLedger->peekTransactionMap()->getMissingNodes(nodeIDs, nodeHashes, 128, &tFilter);
|
||||
if (nodeIDs.empty())
|
||||
{
|
||||
if (!mLedger->peekTransactionMap()->isValid()) mFailed = true;
|
||||
else
|
||||
{
|
||||
mHaveTransactions = true;
|
||||
if (mHaveState)
|
||||
mComplete = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ripple::TMGetLedger tmGL;
|
||||
tmGL.set_ledgerhash(mHash.begin(), mHash.size());
|
||||
tmGL.set_ledgerseq(mLedger->getLedgerSeq());
|
||||
tmGL.set_itype(ripple::liTX_NODE);
|
||||
BOOST_FOREACH(SHAMapNode& it, nodeIDs)
|
||||
*(tmGL.add_nodeids()) = it.getRawString();
|
||||
cLog(lsTRACE) << "Sending TX node " << nodeIDs.size()
|
||||
<< " request to " << (peer ? "selected peer" : "all peers");
|
||||
sendRequest(tmGL, peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mHaveBase && !mHaveState)
|
||||
{
|
||||
assert(mLedger);
|
||||
if (mLedger->peekAccountStateMap()->getHash().isZero())
|
||||
{ // we need the root node
|
||||
ripple::TMGetLedger tmGL;
|
||||
tmGL.set_ledgerhash(mHash.begin(), mHash.size());
|
||||
tmGL.set_ledgerseq(mLedger->getLedgerSeq());
|
||||
tmGL.set_itype(ripple::liAS_NODE);
|
||||
*(tmGL.add_nodeids()) = SHAMapNode().getRawString();
|
||||
cLog(lsTRACE) << "Sending AS root request to " << (peer ? "selected peer" : "all peers");
|
||||
sendRequest(tmGL, peer);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<SHAMapNode> nodeIDs;
|
||||
std::vector<uint256> nodeHashes;
|
||||
AccountStateSF aFilter(mLedger->getHash(), mLedger->getLedgerSeq());
|
||||
mLedger->peekAccountStateMap()->getMissingNodes(nodeIDs, nodeHashes, 128, &aFilter);
|
||||
if (nodeIDs.empty())
|
||||
{
|
||||
if (!mLedger->peekAccountStateMap()->isValid()) mFailed = true;
|
||||
else
|
||||
{
|
||||
mHaveState = true;
|
||||
if (mHaveTransactions)
|
||||
mComplete = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ripple::TMGetLedger tmGL;
|
||||
tmGL.set_ledgerhash(mHash.begin(), mHash.size());
|
||||
tmGL.set_ledgerseq(mLedger->getLedgerSeq());
|
||||
tmGL.set_itype(ripple::liAS_NODE);
|
||||
BOOST_FOREACH(SHAMapNode& it, nodeIDs)
|
||||
*(tmGL.add_nodeids()) = it.getRawString();
|
||||
cLog(lsTRACE) << "Sending AS node " << nodeIDs.size()
|
||||
<< " request to " << (peer ? "selected peer" : "all peers");
|
||||
tLog(nodeIDs.size() == 1, lsTRACE) << "AS node: " << nodeIDs[0];
|
||||
sendRequest(tmGL, peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mComplete || mFailed)
|
||||
{
|
||||
cLog(lsDEBUG) << "Done:" << (mComplete ? " complete" : "") << (mFailed ? " failed" : "");
|
||||
done();
|
||||
}
|
||||
else if (timer)
|
||||
resetTimer();
|
||||
}
|
||||
|
||||
void PeerSet::sendRequest(const ripple::TMGetLedger& tmGL, Peer::ref peer)
|
||||
{
|
||||
if (!peer)
|
||||
sendRequest(tmGL);
|
||||
else
|
||||
peer->sendPacket(boost::make_shared<PackedMessage>(tmGL, ripple::mtGET_LEDGER));
|
||||
}
|
||||
|
||||
void PeerSet::sendRequest(const ripple::TMGetLedger& tmGL)
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock sl(mLock);
|
||||
if (mPeers.empty())
|
||||
return;
|
||||
|
||||
PackedMessage::pointer packet = boost::make_shared<PackedMessage>(tmGL, ripple::mtGET_LEDGER);
|
||||
for (boost::unordered_map<uint64, int>::iterator it = mPeers.begin(), end = mPeers.end(); it != end; ++it)
|
||||
{
|
||||
Peer::pointer peer = theApp->getConnectionPool().getPeerById(it->first);
|
||||
if (peer)
|
||||
peer->sendPacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
int PeerSet::takePeerSetFrom(const PeerSet& s)
|
||||
{
|
||||
int ret = 0;
|
||||
mPeers.clear();
|
||||
for (boost::unordered_map<uint64, int>::const_iterator it = s.mPeers.begin(), end = s.mPeers.end();
|
||||
it != end; ++it)
|
||||
{
|
||||
mPeers.insert(std::make_pair(it->first, 0));
|
||||
++ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int PeerSet::getPeerCount() const
|
||||
{
|
||||
int ret = 0;
|
||||
for (boost::unordered_map<uint64, int>::const_iterator it = mPeers.begin(), end = mPeers.end(); it != end; ++it)
|
||||
if (theApp->getConnectionPool().hasPeer(it->first))
|
||||
++ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool LedgerAcquire::takeBase(const std::string& data)
|
||||
{ // Return value: true=normal, false=bad data
|
||||
#ifdef LA_DEBUG
|
||||
cLog(lsTRACE) << "got base acquiring ledger " << mHash;
|
||||
#endif
|
||||
boost::recursive_mutex::scoped_lock sl(mLock);
|
||||
if (mHaveBase) return true;
|
||||
mLedger = boost::make_shared<Ledger>(data);
|
||||
if (mLedger->getHash() != mHash)
|
||||
{
|
||||
cLog(lsWARNING) << "Acquire hash mismatch";
|
||||
cLog(lsWARNING) << mLedger->getHash() << "!=" << mHash;
|
||||
mLedger.reset();
|
||||
#ifdef TRUST_NETWORK
|
||||
assert(false);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
mHaveBase = true;
|
||||
|
||||
Serializer s(data.size() + 4);
|
||||
s.add32(sHP_Ledger);
|
||||
s.addRaw(data);
|
||||
theApp->getHashedObjectStore().store(hotLEDGER, mLedger->getLedgerSeq(), s.peekData(), mHash);
|
||||
|
||||
progress();
|
||||
if (!mLedger->getTransHash())
|
||||
mHaveTransactions = true;
|
||||
if (!mLedger->getAccountHash())
|
||||
mHaveState = true;
|
||||
mLedger->setAcquiring();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LedgerAcquire::takeTxNode(const std::list<SHAMapNode>& nodeIDs,
|
||||
const std::list< std::vector<unsigned char> >& data)
|
||||
{
|
||||
if (!mHaveBase) return false;
|
||||
std::list<SHAMapNode>::const_iterator nodeIDit = nodeIDs.begin();
|
||||
std::list< std::vector<unsigned char> >::const_iterator nodeDatait = data.begin();
|
||||
TransactionStateSF tFilter(mLedger->getHash(), mLedger->getLedgerSeq());
|
||||
while (nodeIDit != nodeIDs.end())
|
||||
{
|
||||
if (nodeIDit->isRoot())
|
||||
{
|
||||
if (!mLedger->peekTransactionMap()->addRootNode(mLedger->getTransHash(), *nodeDatait, snfWIRE, &tFilter))
|
||||
return false;
|
||||
}
|
||||
else if (!mLedger->peekTransactionMap()->addKnownNode(*nodeIDit, *nodeDatait, &tFilter))
|
||||
return false;
|
||||
++nodeIDit;
|
||||
++nodeDatait;
|
||||
}
|
||||
if (!mLedger->peekTransactionMap()->isSynching())
|
||||
{
|
||||
mHaveTransactions = true;
|
||||
if (mHaveState)
|
||||
{
|
||||
mComplete = true;
|
||||
done();
|
||||
}
|
||||
}
|
||||
progress();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LedgerAcquire::takeAsNode(const std::list<SHAMapNode>& nodeIDs,
|
||||
const std::list< std::vector<unsigned char> >& data)
|
||||
{
|
||||
cLog(lsTRACE) << "got ASdata (" << nodeIDs.size() <<") acquiring ledger " << mHash;
|
||||
tLog(nodeIDs.size() == 1, lsTRACE) << "got AS node: " << nodeIDs.front();
|
||||
|
||||
if (!mHaveBase)
|
||||
{
|
||||
cLog(lsWARNING) << "Don't have ledger base";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::list<SHAMapNode>::const_iterator nodeIDit = nodeIDs.begin();
|
||||
std::list< std::vector<unsigned char> >::const_iterator nodeDatait = data.begin();
|
||||
AccountStateSF tFilter(mLedger->getHash(), mLedger->getLedgerSeq());
|
||||
while (nodeIDit != nodeIDs.end())
|
||||
{
|
||||
if (nodeIDit->isRoot())
|
||||
{
|
||||
if (!mLedger->peekAccountStateMap()->addRootNode(mLedger->getAccountHash(),
|
||||
*nodeDatait, snfWIRE, &tFilter))
|
||||
{
|
||||
cLog(lsWARNING) << "Bad ledger base";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!mLedger->peekAccountStateMap()->addKnownNode(*nodeIDit, *nodeDatait, &tFilter))
|
||||
{
|
||||
cLog(lsWARNING) << "Unable to add AS node";
|
||||
return false;
|
||||
}
|
||||
++nodeIDit;
|
||||
++nodeDatait;
|
||||
}
|
||||
if (!mLedger->peekAccountStateMap()->isSynching())
|
||||
{
|
||||
mHaveState = true;
|
||||
if (mHaveTransactions)
|
||||
{
|
||||
mComplete = true;
|
||||
done();
|
||||
}
|
||||
}
|
||||
progress();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LedgerAcquire::takeAsRootNode(const std::vector<unsigned char>& data)
|
||||
{
|
||||
if (!mHaveBase)
|
||||
return false;
|
||||
AccountStateSF tFilter(mLedger->getHash(), mLedger->getLedgerSeq());
|
||||
if (!mLedger->peekAccountStateMap()->addRootNode(mLedger->getAccountHash(), data, snfWIRE, &tFilter))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LedgerAcquire::takeTxRootNode(const std::vector<unsigned char>& data)
|
||||
{
|
||||
if (!mHaveBase)
|
||||
return false;
|
||||
TransactionStateSF tFilter(mLedger->getHash(), mLedger->getLedgerSeq());
|
||||
if (!mLedger->peekTransactionMap()->addRootNode(mLedger->getTransHash(), data, snfWIRE, &tFilter))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
LedgerAcquire::pointer LedgerAcquireMaster::findCreate(const uint256& hash)
|
||||
{
|
||||
assert(hash.isNonZero());
|
||||
boost::mutex::scoped_lock sl(mLock);
|
||||
LedgerAcquire::pointer& ptr = mLedgers[hash];
|
||||
if (ptr)
|
||||
return ptr;
|
||||
ptr = boost::make_shared<LedgerAcquire>(hash);
|
||||
assert(mLedgers[hash] == ptr);
|
||||
ptr->addPeers();
|
||||
ptr->resetTimer(); // Cannot call in constructor
|
||||
return ptr;
|
||||
}
|
||||
|
||||
LedgerAcquire::pointer LedgerAcquireMaster::find(const uint256& hash)
|
||||
{
|
||||
assert(hash.isNonZero());
|
||||
boost::mutex::scoped_lock sl(mLock);
|
||||
std::map<uint256, LedgerAcquire::pointer>::iterator it = mLedgers.find(hash);
|
||||
if (it != mLedgers.end())
|
||||
return it->second;
|
||||
return LedgerAcquire::pointer();
|
||||
}
|
||||
|
||||
bool LedgerAcquireMaster::hasLedger(const uint256& hash)
|
||||
{
|
||||
assert(hash.isNonZero());
|
||||
boost::mutex::scoped_lock sl(mLock);
|
||||
return mLedgers.find(hash) != mLedgers.end();
|
||||
}
|
||||
|
||||
void LedgerAcquireMaster::dropLedger(const uint256& hash)
|
||||
{
|
||||
assert(hash.isNonZero());
|
||||
boost::mutex::scoped_lock sl(mLock);
|
||||
mLedgers.erase(hash);
|
||||
}
|
||||
|
||||
bool LedgerAcquireMaster::gotLedgerData(ripple::TMLedgerData& packet, Peer::ref peer)
|
||||
{
|
||||
uint256 hash;
|
||||
if (packet.ledgerhash().size() != 32)
|
||||
{
|
||||
std::cerr << "Acquire error" << std::endl;
|
||||
return false;
|
||||
}
|
||||
memcpy(hash.begin(), packet.ledgerhash().data(), 32);
|
||||
cLog(lsTRACE) << "Got data ( " << packet.nodes().size() << ") for acquiring ledger: " << hash;
|
||||
|
||||
LedgerAcquire::pointer ledger = find(hash);
|
||||
if (!ledger)
|
||||
{
|
||||
cLog(lsINFO) << "Got data for ledger we're not acquiring";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (packet.type() == ripple::liBASE)
|
||||
{
|
||||
if (packet.nodes_size() < 1)
|
||||
{
|
||||
cLog(lsWARNING) << "Got empty base data";
|
||||
return false;
|
||||
}
|
||||
if (!ledger->takeBase(packet.nodes(0).nodedata()))
|
||||
{
|
||||
cLog(lsWARNING) << "Got invalid base data";
|
||||
return false;
|
||||
}
|
||||
if ((packet.nodes().size() > 1) && !ledger->takeAsRootNode(strCopy(packet.nodes(1).nodedata())))
|
||||
{
|
||||
cLog(lsWARNING) << "Included ASbase invalid";
|
||||
}
|
||||
if ((packet.nodes().size() > 2) && !ledger->takeTxRootNode(strCopy(packet.nodes(2).nodedata())))
|
||||
{
|
||||
cLog(lsWARNING) << "Included TXbase invalid";
|
||||
}
|
||||
ledger->trigger(peer, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((packet.type() == ripple::liTX_NODE) || (packet.type() == ripple::liAS_NODE))
|
||||
{
|
||||
std::list<SHAMapNode> nodeIDs;
|
||||
std::list< std::vector<unsigned char> > nodeData;
|
||||
|
||||
if (packet.nodes().size() <= 0)
|
||||
{
|
||||
cLog(lsINFO) << "Got request for no nodes";
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < packet.nodes().size(); ++i)
|
||||
{
|
||||
const ripple::TMLedgerNode& node = packet.nodes(i);
|
||||
if (!node.has_nodeid() || !node.has_nodedata())
|
||||
{
|
||||
cLog(lsWARNING) << "Got bad node";
|
||||
return false;
|
||||
}
|
||||
|
||||
nodeIDs.push_back(SHAMapNode(node.nodeid().data(), node.nodeid().size()));
|
||||
nodeData.push_back(std::vector<unsigned char>(node.nodedata().begin(), node.nodedata().end()));
|
||||
}
|
||||
bool ret;
|
||||
if (packet.type() == ripple::liTX_NODE)
|
||||
ret = ledger->takeTxNode(nodeIDs, nodeData);
|
||||
else
|
||||
ret = ledger->takeAsNode(nodeIDs, nodeData);
|
||||
if (ret)
|
||||
ledger->trigger(peer, false);
|
||||
return ret;
|
||||
}
|
||||
|
||||
cLog(lsWARNING) << "Not sure what ledger data we got";
|
||||
return false;
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
127
src/cpp/ripple/LedgerAcquire.h
Normal file
127
src/cpp/ripple/LedgerAcquire.h
Normal file
@@ -0,0 +1,127 @@
|
||||
#ifndef __LEDGERACQUIRE__
|
||||
#define __LEDGERACQUIRE__
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <list>
|
||||
|
||||
#include <boost/enable_shared_from_this.hpp>
|
||||
#include <boost/function.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/thread/mutex.hpp>
|
||||
#include <boost/unordered_map.hpp>
|
||||
#include <boost/weak_ptr.hpp>
|
||||
|
||||
#include "Ledger.h"
|
||||
#include "Peer.h"
|
||||
#include "TaggedCache.h"
|
||||
#include "InstanceCounter.h"
|
||||
#include "ripple.pb.h"
|
||||
|
||||
DEFINE_INSTANCE(PeerSet);
|
||||
|
||||
class PeerSet : private IS_INSTANCE(PeerSet)
|
||||
{
|
||||
protected:
|
||||
uint256 mHash;
|
||||
int mTimerInterval, mTimeouts;
|
||||
bool mComplete, mFailed, mProgress;
|
||||
|
||||
boost::recursive_mutex mLock;
|
||||
boost::asio::deadline_timer mTimer;
|
||||
boost::unordered_map<uint64, int> mPeers;
|
||||
|
||||
PeerSet(const uint256& hash, int interval);
|
||||
virtual ~PeerSet() { ; }
|
||||
|
||||
void sendRequest(const ripple::TMGetLedger& message);
|
||||
void sendRequest(const ripple::TMGetLedger& message, Peer::ref peer);
|
||||
|
||||
public:
|
||||
const uint256& getHash() const { return mHash; }
|
||||
bool isComplete() const { return mComplete; }
|
||||
bool isFailed() const { return mFailed; }
|
||||
int getTimeouts() const { return mTimeouts; }
|
||||
|
||||
void progress() { mProgress = true; }
|
||||
|
||||
void peerHas(Peer::ref);
|
||||
void badPeer(Peer::ref);
|
||||
void resetTimer();
|
||||
|
||||
int takePeerSetFrom(const PeerSet& s);
|
||||
int getPeerCount() const;
|
||||
|
||||
protected:
|
||||
virtual void newPeer(Peer::ref) = 0;
|
||||
virtual void onTimer(bool progress) = 0;
|
||||
virtual boost::weak_ptr<PeerSet> pmDowncast() = 0;
|
||||
|
||||
void setComplete() { mComplete = true; }
|
||||
void setFailed() { mFailed = true; }
|
||||
void invokeOnTimer();
|
||||
|
||||
private:
|
||||
static void TimerEntry(boost::weak_ptr<PeerSet>, const boost::system::error_code& result);
|
||||
};
|
||||
|
||||
class LedgerAcquire : public PeerSet, public boost::enable_shared_from_this<LedgerAcquire>
|
||||
{ // A ledger we are trying to acquire
|
||||
public:
|
||||
typedef boost::shared_ptr<LedgerAcquire> pointer;
|
||||
|
||||
protected:
|
||||
Ledger::pointer mLedger;
|
||||
bool mHaveBase, mHaveState, mHaveTransactions, mAborted, mSignaled, mAccept;
|
||||
|
||||
std::vector< boost::function<void (LedgerAcquire::pointer)> > mOnComplete;
|
||||
|
||||
void done();
|
||||
void onTimer(bool progress);
|
||||
|
||||
void newPeer(Peer::ref peer) { trigger(peer, false); }
|
||||
|
||||
boost::weak_ptr<PeerSet> pmDowncast();
|
||||
|
||||
public:
|
||||
LedgerAcquire(const uint256& hash);
|
||||
virtual ~LedgerAcquire() { ; }
|
||||
|
||||
bool isBase() const { return mHaveBase; }
|
||||
bool isAcctStComplete() const { return mHaveState; }
|
||||
bool isTransComplete() const { return mHaveTransactions; }
|
||||
Ledger::pointer getLedger() { return mLedger; }
|
||||
void abort() { mAborted = true; }
|
||||
bool setAccept() { if (mAccept) return false; mAccept = true; return true; }
|
||||
|
||||
void addOnComplete(boost::function<void (LedgerAcquire::pointer)>);
|
||||
|
||||
bool takeBase(const std::string& data);
|
||||
bool takeTxNode(const std::list<SHAMapNode>& IDs, const std::list<std::vector<unsigned char> >& data);
|
||||
bool takeTxRootNode(const std::vector<unsigned char>& data);
|
||||
bool takeAsNode(const std::list<SHAMapNode>& IDs, const std::list<std::vector<unsigned char> >& data);
|
||||
bool takeAsRootNode(const std::vector<unsigned char>& data);
|
||||
void trigger(Peer::ref, bool timer);
|
||||
bool tryLocal();
|
||||
void addPeers();
|
||||
};
|
||||
|
||||
class LedgerAcquireMaster
|
||||
{
|
||||
protected:
|
||||
boost::mutex mLock;
|
||||
std::map<uint256, LedgerAcquire::pointer> mLedgers;
|
||||
|
||||
public:
|
||||
LedgerAcquireMaster() { ; }
|
||||
|
||||
LedgerAcquire::pointer findCreate(const uint256& hash);
|
||||
LedgerAcquire::pointer find(const uint256& hash);
|
||||
bool hasLedger(const uint256& ledgerHash);
|
||||
void dropLedger(const uint256& ledgerHash);
|
||||
bool gotLedgerData(ripple::TMLedgerData& packet, Peer::ref);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
// vim:ts=4
|
||||
1314
src/cpp/ripple/LedgerConsensus.cpp
Normal file
1314
src/cpp/ripple/LedgerConsensus.cpp
Normal file
File diff suppressed because it is too large
Load Diff
195
src/cpp/ripple/LedgerConsensus.h
Normal file
195
src/cpp/ripple/LedgerConsensus.h
Normal file
@@ -0,0 +1,195 @@
|
||||
#ifndef __LEDGER_CONSENSUS__
|
||||
#define __LEDGER_CONSENSUS__
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
#include <boost/weak_ptr.hpp>
|
||||
#include <boost/enable_shared_from_this.hpp>
|
||||
#include <boost/unordered/unordered_map.hpp>
|
||||
|
||||
#include "../json/value.h"
|
||||
|
||||
#include "key.h"
|
||||
#include "Transaction.h"
|
||||
#include "LedgerAcquire.h"
|
||||
#include "LedgerProposal.h"
|
||||
#include "Peer.h"
|
||||
#include "CanonicalTXSet.h"
|
||||
#include "TransactionEngine.h"
|
||||
#include "InstanceCounter.h"
|
||||
|
||||
DEFINE_INSTANCE(LedgerConsensus);
|
||||
|
||||
class TransactionAcquire : public PeerSet, public boost::enable_shared_from_this<TransactionAcquire>
|
||||
{ // A transaction set we are trying to acquire
|
||||
public:
|
||||
typedef boost::shared_ptr<TransactionAcquire> pointer;
|
||||
|
||||
protected:
|
||||
SHAMap::pointer mMap;
|
||||
bool mHaveRoot;
|
||||
|
||||
void onTimer(bool progress);
|
||||
void newPeer(Peer::ref peer) { trigger(peer, false); }
|
||||
|
||||
void done();
|
||||
void trigger(Peer::ref, bool timer);
|
||||
boost::weak_ptr<PeerSet> pmDowncast();
|
||||
|
||||
public:
|
||||
|
||||
TransactionAcquire(const uint256& hash);
|
||||
virtual ~TransactionAcquire() { ; }
|
||||
|
||||
SHAMap::pointer getMap() { return mMap; }
|
||||
|
||||
bool takeNodes(const std::list<SHAMapNode>& IDs, const std::list< std::vector<unsigned char> >& data, Peer::ref);
|
||||
};
|
||||
|
||||
class LCTransaction
|
||||
{ // A transaction that may be disputed
|
||||
protected:
|
||||
uint256 mTransactionID;
|
||||
int mYays, mNays;
|
||||
bool mOurVote;
|
||||
Serializer transaction;
|
||||
boost::unordered_map<uint160, bool> mVotes;
|
||||
|
||||
public:
|
||||
typedef boost::shared_ptr<LCTransaction> pointer;
|
||||
|
||||
LCTransaction(const uint256 &txID, const std::vector<unsigned char>& tx, bool ourVote) :
|
||||
mTransactionID(txID), mYays(0), mNays(0), mOurVote(ourVote), transaction(tx) { ; }
|
||||
|
||||
const uint256& getTransactionID() const { return mTransactionID; }
|
||||
bool getOurVote() const { return mOurVote; }
|
||||
Serializer& peekTransaction() { return transaction; }
|
||||
void setOurVote(bool o) { mOurVote = o; }
|
||||
|
||||
void setVote(const uint160& peer, bool votesYes);
|
||||
void unVote(const uint160& peer);
|
||||
|
||||
bool updateVote(int percentTime, bool proposing);
|
||||
};
|
||||
|
||||
enum LCState
|
||||
{
|
||||
lcsPRE_CLOSE, // We haven't closed our ledger yet, but others might have
|
||||
lcsESTABLISH, // Establishing consensus
|
||||
lcsFINISHED, // We have closed on a transaction set
|
||||
lcsACCEPTED, // We have accepted/validated a new last closed ledger
|
||||
};
|
||||
|
||||
class LedgerConsensus : public boost::enable_shared_from_this<LedgerConsensus>, IS_INSTANCE(LedgerConsensus)
|
||||
{
|
||||
protected:
|
||||
LCState mState;
|
||||
uint32 mCloseTime; // The wall time this ledger closed
|
||||
uint256 mPrevLedgerHash, mNewLedgerHash;
|
||||
Ledger::pointer mPreviousLedger;
|
||||
LedgerAcquire::pointer mAcquiringLedger;
|
||||
LedgerProposal::pointer mOurPosition;
|
||||
RippleAddress mValPublic, mValPrivate;
|
||||
bool mProposing, mValidating, mHaveCorrectLCL;
|
||||
|
||||
int mCurrentMSeconds, mClosePercent, mCloseResolution;
|
||||
bool mHaveCloseTimeConsensus;
|
||||
|
||||
boost::posix_time::ptime mConsensusStartTime;
|
||||
int mPreviousProposers;
|
||||
int mPreviousMSeconds;
|
||||
|
||||
// Convergence tracking, trusted peers indexed by hash of public key
|
||||
boost::unordered_map<uint160, LedgerProposal::pointer> mPeerPositions;
|
||||
|
||||
// Transaction Sets, indexed by hash of transaction tree
|
||||
boost::unordered_map<uint256, SHAMap::pointer> mAcquired;
|
||||
boost::unordered_map<uint256, TransactionAcquire::pointer> mAcquiring;
|
||||
|
||||
// Peer sets
|
||||
boost::unordered_map<uint256, std::vector< boost::weak_ptr<Peer> > > mPeerData;
|
||||
|
||||
// Disputed transactions
|
||||
boost::unordered_map<uint256, LCTransaction::pointer> mDisputes;
|
||||
|
||||
// Close time estimates
|
||||
std::map<uint32, int> mCloseTimes;
|
||||
|
||||
// nodes that have bowed out of this consensus process
|
||||
boost::unordered_set<uint160> mDeadNodes;
|
||||
|
||||
// final accept logic
|
||||
void accept(SHAMap::ref txSet);
|
||||
|
||||
void weHave(const uint256& id, Peer::ref avoidPeer);
|
||||
void startAcquiring(const TransactionAcquire::pointer&);
|
||||
SHAMap::pointer find(const uint256& hash);
|
||||
|
||||
void createDisputes(SHAMap::ref, SHAMap::ref);
|
||||
void addDisputedTransaction(const uint256&, const std::vector<unsigned char>& transaction);
|
||||
void adjustCount(SHAMap::ref map, const std::vector<uint160>& peers);
|
||||
void propose();
|
||||
|
||||
void addPosition(LedgerProposal&, bool ours);
|
||||
void removePosition(LedgerProposal&, bool ours);
|
||||
void sendHaveTxSet(const uint256& set, bool direct);
|
||||
void applyTransactions(SHAMap::ref transactionSet, Ledger::ref targetLedger,
|
||||
Ledger::ref checkLedger, CanonicalTXSet& failedTransactions, bool openLgr);
|
||||
void applyTransaction(TransactionEngine& engine, SerializedTransaction::ref txn,
|
||||
Ledger::ref targetLedger, CanonicalTXSet& failedTransactions, bool openLgr);
|
||||
|
||||
uint32 roundCloseTime(uint32 closeTime);
|
||||
|
||||
// manipulating our own position
|
||||
void statusChange(ripple::NodeEvent, Ledger& ledger);
|
||||
void takeInitialPosition(Ledger& initialLedger);
|
||||
void updateOurPositions();
|
||||
void playbackProposals();
|
||||
int getThreshold();
|
||||
void closeLedger();
|
||||
|
||||
void beginAccept(bool synchronous);
|
||||
void endConsensus();
|
||||
|
||||
public:
|
||||
LedgerConsensus(const uint256& prevLCLHash, Ledger::ref previousLedger, uint32 closeTime);
|
||||
|
||||
int startup();
|
||||
Json::Value getJson();
|
||||
|
||||
Ledger::pointer peekPreviousLedger() { return mPreviousLedger; }
|
||||
uint256 getLCL() { return mPrevLedgerHash; }
|
||||
|
||||
SHAMap::pointer getTransactionTree(const uint256& hash, bool doAcquire);
|
||||
TransactionAcquire::pointer getAcquiring(const uint256& hash);
|
||||
void mapComplete(const uint256& hash, SHAMap::ref map, bool acquired);
|
||||
void checkLCL();
|
||||
void handleLCL(const uint256& lclHash);
|
||||
|
||||
void timerEntry();
|
||||
|
||||
// state handlers
|
||||
void statePreClose();
|
||||
void stateEstablish();
|
||||
void stateCutoff();
|
||||
void stateFinished();
|
||||
void stateAccepted();
|
||||
|
||||
bool haveConsensus(bool forReal);
|
||||
|
||||
bool peerPosition(const LedgerProposal::pointer&);
|
||||
|
||||
bool peerHasSet(Peer::ref peer, const uint256& set, ripple::TxSetStatus status);
|
||||
|
||||
bool peerGaveNodes(Peer::ref peer, const uint256& setHash,
|
||||
const std::list<SHAMapNode>& nodeIDs, const std::list< std::vector<unsigned char> >& nodeData);
|
||||
|
||||
bool isOurPubKey(const RippleAddress &k) { return k == mValPublic; }
|
||||
|
||||
// test/debug
|
||||
void simulate();
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
1133
src/cpp/ripple/LedgerEntrySet.cpp
Normal file
1133
src/cpp/ripple/LedgerEntrySet.cpp
Normal file
File diff suppressed because it is too large
Load Diff
150
src/cpp/ripple/LedgerEntrySet.h
Normal file
150
src/cpp/ripple/LedgerEntrySet.h
Normal file
@@ -0,0 +1,150 @@
|
||||
#ifndef __LEDGERENTRYSET__
|
||||
#define __LEDGERENTRYSET__
|
||||
|
||||
#include <boost/unordered_map.hpp>
|
||||
|
||||
#include "SerializedLedger.h"
|
||||
#include "TransactionMeta.h"
|
||||
#include "Ledger.h"
|
||||
#include "TransactionErr.h"
|
||||
#include "InstanceCounter.h"
|
||||
|
||||
DEFINE_INSTANCE(LedgerEntrySetEntry);
|
||||
DEFINE_INSTANCE(LedgerEntrySet);
|
||||
|
||||
enum LedgerEntryAction
|
||||
{
|
||||
taaNONE,
|
||||
taaCACHED, // Unmodified.
|
||||
taaMODIFY, // Modifed, must have previously been taaCACHED.
|
||||
taaDELETE, // Delete, must have previously been taaDELETE or taaMODIFY.
|
||||
taaCREATE, // Newly created.
|
||||
};
|
||||
|
||||
class LedgerEntrySetEntry : private IS_INSTANCE(LedgerEntrySetEntry)
|
||||
{
|
||||
public:
|
||||
SLE::pointer mEntry;
|
||||
LedgerEntryAction mAction;
|
||||
int mSeq;
|
||||
|
||||
LedgerEntrySetEntry(SLE::ref e, LedgerEntryAction a, int s) : mEntry(e), mAction(a), mSeq(s) { ; }
|
||||
};
|
||||
|
||||
|
||||
class LedgerEntrySet : private IS_INSTANCE(LedgerEntrySet)
|
||||
{
|
||||
protected:
|
||||
Ledger::pointer mLedger;
|
||||
std::map<uint256, LedgerEntrySetEntry> mEntries; // cannot be unordered!
|
||||
TransactionMetaSet mSet;
|
||||
int mSeq;
|
||||
|
||||
LedgerEntrySet(Ledger::ref ledger, const std::map<uint256, LedgerEntrySetEntry> &e,
|
||||
const TransactionMetaSet& s, int m) : mLedger(ledger), mEntries(e), mSet(s), mSeq(m) { ; }
|
||||
|
||||
SLE::pointer getForMod(const uint256& node, Ledger::ref ledger,
|
||||
boost::unordered_map<uint256, SLE::pointer>& newMods);
|
||||
|
||||
bool threadTx(const RippleAddress& threadTo, Ledger::ref ledger,
|
||||
boost::unordered_map<uint256, SLE::pointer>& newMods);
|
||||
|
||||
bool threadTx(SLE::ref threadTo, Ledger::ref ledger, boost::unordered_map<uint256, SLE::pointer>& newMods);
|
||||
|
||||
bool threadOwners(SLE::ref node, Ledger::ref ledger, boost::unordered_map<uint256, SLE::pointer>& newMods);
|
||||
|
||||
public:
|
||||
|
||||
LedgerEntrySet(Ledger::ref ledger) : mLedger(ledger), mSeq(0) { ; }
|
||||
|
||||
LedgerEntrySet() : mSeq(0) { ; }
|
||||
|
||||
// set functions
|
||||
LedgerEntrySet duplicate() const; // Make a duplicate of this set
|
||||
void setTo(const LedgerEntrySet&); // Set this set to have the same contents as another
|
||||
void swapWith(LedgerEntrySet&); // Swap the contents of two sets
|
||||
|
||||
int getSeq() const { return mSeq; }
|
||||
void bumpSeq() { ++mSeq; }
|
||||
void init(Ledger::ref ledger, const uint256& transactionID, uint32 ledgerID);
|
||||
void clear();
|
||||
|
||||
Ledger::pointer& getLedger() { return mLedger; }
|
||||
Ledger::ref getLedgerRef() const { return mLedger; }
|
||||
|
||||
// basic entry functions
|
||||
SLE::pointer getEntry(const uint256& index, LedgerEntryAction&);
|
||||
LedgerEntryAction hasEntry(const uint256& index) const;
|
||||
void entryCache(SLE::ref); // Add this entry to the cache
|
||||
void entryCreate(SLE::ref); // This entry will be created
|
||||
void entryDelete(SLE::ref); // This entry will be deleted
|
||||
void entryModify(SLE::ref); // This entry will be modified
|
||||
|
||||
// higher-level ledger functions
|
||||
SLE::pointer entryCreate(LedgerEntryType letType, const uint256& uIndex);
|
||||
SLE::pointer entryCache(LedgerEntryType letType, const uint256& uIndex);
|
||||
|
||||
// Directory functions.
|
||||
TER dirAdd(
|
||||
uint64& uNodeDir, // Node of entry.
|
||||
const uint256& uRootIndex,
|
||||
const uint256& uLedgerIndex);
|
||||
|
||||
TER dirDelete(
|
||||
const bool bKeepRoot,
|
||||
const uint64& uNodeDir, // Node item is mentioned in.
|
||||
const uint256& uRootIndex,
|
||||
const uint256& uLedgerIndex, // Item being deleted
|
||||
const bool bStable);
|
||||
|
||||
bool dirFirst(const uint256& uRootIndex, SLE::pointer& sleNode, unsigned int& uDirEntry, uint256& uEntryIndex);
|
||||
bool dirNext(const uint256& uRootIndex, SLE::pointer& sleNode, unsigned int& uDirEntry, uint256& uEntryIndex);
|
||||
|
||||
// Offer functions.
|
||||
TER offerDelete(const uint256& uOfferIndex);
|
||||
TER offerDelete(const SLE::pointer& sleOffer, const uint256& uOfferIndex, const uint160& uOwnerID);
|
||||
|
||||
// Balance functions.
|
||||
uint32 rippleTransferRate(const uint160& uIssuerID);
|
||||
uint32 rippleTransferRate(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID);
|
||||
STAmount rippleOwed(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID);
|
||||
STAmount rippleLimit(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID);
|
||||
uint32 rippleQualityIn(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID,
|
||||
SField::ref sfLow = sfLowQualityIn, SField::ref sfHigh = sfHighQualityIn);
|
||||
uint32 rippleQualityOut(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID)
|
||||
{ return rippleQualityIn(uToAccountID, uFromAccountID, uCurrencyID, sfLowQualityOut, sfHighQualityOut); }
|
||||
|
||||
STAmount rippleHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID);
|
||||
STAmount rippleTransferFee(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount);
|
||||
void rippleCredit(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount, bool bCheckIssuer=true);
|
||||
STAmount rippleSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount);
|
||||
|
||||
STAmount accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID);
|
||||
void accountSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount);
|
||||
STAmount accountFunds(const uint160& uAccountID, const STAmount& saDefault);
|
||||
|
||||
Json::Value getJson(int) const;
|
||||
void calcRawMeta(Serializer&, TER result);
|
||||
|
||||
// iterator functions
|
||||
typedef std::map<uint256, LedgerEntrySetEntry>::iterator iterator;
|
||||
typedef std::map<uint256, LedgerEntrySetEntry>::const_iterator const_iterator;
|
||||
bool isEmpty() const { return mEntries.empty(); }
|
||||
std::map<uint256, LedgerEntrySetEntry>::const_iterator begin() const { return mEntries.begin(); }
|
||||
std::map<uint256, LedgerEntrySetEntry>::const_iterator end() const { return mEntries.end(); }
|
||||
std::map<uint256, LedgerEntrySetEntry>::iterator begin() { return mEntries.begin(); }
|
||||
std::map<uint256, LedgerEntrySetEntry>::iterator end() { return mEntries.end(); }
|
||||
|
||||
static bool intersect(const LedgerEntrySet& lesLeft, const LedgerEntrySet& lesRight);
|
||||
};
|
||||
|
||||
inline LedgerEntrySet::iterator range_begin(LedgerEntrySet& x) { return x.begin(); }
|
||||
inline LedgerEntrySet::iterator range_end(LedgerEntrySet &x) { return x.end(); }
|
||||
namespace boost
|
||||
{
|
||||
template<> struct range_mutable_iterator<LedgerEntrySet> { typedef LedgerEntrySet::iterator type; };
|
||||
template<> struct range_const_iterator<LedgerEntrySet> { typedef LedgerEntrySet::const_iterator type; };
|
||||
}
|
||||
|
||||
#endif
|
||||
// vim:ts=4
|
||||
121
src/cpp/ripple/LedgerFormats.cpp
Normal file
121
src/cpp/ripple/LedgerFormats.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
|
||||
#include "LedgerFormats.h"
|
||||
|
||||
std::map<int, LedgerEntryFormat*> LedgerEntryFormat::byType;
|
||||
std::map<std::string, LedgerEntryFormat*> LedgerEntryFormat::byName;
|
||||
|
||||
#define LEF_BASE \
|
||||
<< SOElement(sfLedgerIndex, SOE_OPTIONAL) \
|
||||
<< SOElement(sfLedgerEntryType, SOE_REQUIRED) \
|
||||
<< SOElement(sfFlags, SOE_REQUIRED)
|
||||
|
||||
#define DECLARE_LEF(name, type) lef = new LedgerEntryFormat(#name, type); (*lef) LEF_BASE
|
||||
|
||||
static bool LEFInit()
|
||||
{
|
||||
LedgerEntryFormat* lef;
|
||||
|
||||
DECLARE_LEF(AccountRoot, ltACCOUNT_ROOT)
|
||||
<< SOElement(sfAccount, SOE_REQUIRED)
|
||||
<< SOElement(sfSequence, SOE_REQUIRED)
|
||||
<< SOElement(sfBalance, SOE_REQUIRED)
|
||||
<< SOElement(sfPreviousTxnID, SOE_REQUIRED)
|
||||
<< SOElement(sfPreviousTxnLgrSeq, SOE_REQUIRED)
|
||||
<< SOElement(sfAuthorizedKey, SOE_OPTIONAL)
|
||||
<< SOElement(sfEmailHash, SOE_OPTIONAL)
|
||||
<< SOElement(sfWalletLocator, SOE_OPTIONAL)
|
||||
<< SOElement(sfWalletSize, SOE_OPTIONAL)
|
||||
<< SOElement(sfMessageKey, SOE_OPTIONAL)
|
||||
<< SOElement(sfTransferRate, SOE_OPTIONAL)
|
||||
<< SOElement(sfDomain, SOE_OPTIONAL)
|
||||
;
|
||||
|
||||
DECLARE_LEF(Contract, ltCONTRACT)
|
||||
<< SOElement(sfAccount, SOE_REQUIRED)
|
||||
<< SOElement(sfBalance, SOE_REQUIRED)
|
||||
<< SOElement(sfPreviousTxnID, SOE_REQUIRED)
|
||||
<< SOElement(sfPreviousTxnLgrSeq, SOE_REQUIRED)
|
||||
<< SOElement(sfIssuer, SOE_REQUIRED)
|
||||
<< SOElement(sfOwner, SOE_REQUIRED)
|
||||
<< SOElement(sfExpiration, SOE_REQUIRED)
|
||||
<< SOElement(sfBondAmount, SOE_REQUIRED)
|
||||
<< SOElement(sfCreateCode, SOE_REQUIRED)
|
||||
<< SOElement(sfFundCode, SOE_REQUIRED)
|
||||
<< SOElement(sfRemoveCode, SOE_REQUIRED)
|
||||
<< SOElement(sfExpireCode, SOE_REQUIRED)
|
||||
;
|
||||
|
||||
DECLARE_LEF(DirectoryNode, ltDIR_NODE)
|
||||
<< SOElement(sfIndexes, SOE_REQUIRED)
|
||||
<< SOElement(sfIndexNext, SOE_OPTIONAL)
|
||||
<< SOElement(sfIndexPrevious, SOE_OPTIONAL)
|
||||
;
|
||||
|
||||
DECLARE_LEF(GeneratorMap, ltGENERATOR_MAP)
|
||||
<< SOElement(sfGenerator, SOE_REQUIRED)
|
||||
;
|
||||
|
||||
DECLARE_LEF(Nickname, ltNICKNAME)
|
||||
<< SOElement(sfAccount, SOE_REQUIRED)
|
||||
<< SOElement(sfMinimumOffer, SOE_OPTIONAL)
|
||||
;
|
||||
|
||||
DECLARE_LEF(Offer, ltOFFER)
|
||||
<< SOElement(sfAccount, SOE_REQUIRED)
|
||||
<< SOElement(sfSequence, SOE_REQUIRED)
|
||||
<< SOElement(sfTakerPays, SOE_REQUIRED)
|
||||
<< SOElement(sfTakerGets, SOE_REQUIRED)
|
||||
<< SOElement(sfBookDirectory, SOE_REQUIRED)
|
||||
<< SOElement(sfBookNode, SOE_REQUIRED)
|
||||
<< SOElement(sfOwnerNode, SOE_REQUIRED)
|
||||
<< SOElement(sfPreviousTxnID, SOE_REQUIRED)
|
||||
<< SOElement(sfPreviousTxnLgrSeq, SOE_REQUIRED)
|
||||
<< SOElement(sfExpiration, SOE_OPTIONAL)
|
||||
;
|
||||
|
||||
DECLARE_LEF(RippleState, ltRIPPLE_STATE)
|
||||
<< SOElement(sfBalance, SOE_REQUIRED)
|
||||
<< SOElement(sfLowLimit, SOE_REQUIRED)
|
||||
<< SOElement(sfHighLimit, SOE_REQUIRED)
|
||||
<< SOElement(sfPreviousTxnID, SOE_REQUIRED)
|
||||
<< SOElement(sfPreviousTxnLgrSeq, SOE_REQUIRED)
|
||||
<< SOElement(sfLowQualityIn, SOE_OPTIONAL)
|
||||
<< SOElement(sfLowQualityOut, SOE_OPTIONAL)
|
||||
<< SOElement(sfHighQualityIn, SOE_OPTIONAL)
|
||||
<< SOElement(sfHighQualityOut, SOE_OPTIONAL)
|
||||
;
|
||||
|
||||
DECLARE_LEF(LedgerHashes, ltLEDGER_HASHES)
|
||||
<< SOElement(sfHashes, SOE_REQUIRED)
|
||||
;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LEFInitComplete = LEFInit();
|
||||
|
||||
LedgerEntryFormat* LedgerEntryFormat::getLgrFormat(LedgerEntryType t)
|
||||
{
|
||||
std::map<int, LedgerEntryFormat*>::iterator it = byType.find(static_cast<int>(t));
|
||||
if (it == byType.end())
|
||||
return NULL;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
LedgerEntryFormat* LedgerEntryFormat::getLgrFormat(int t)
|
||||
{
|
||||
std::map<int, LedgerEntryFormat*>::iterator it = byType.find((t));
|
||||
if (it == byType.end())
|
||||
return NULL;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
LedgerEntryFormat* LedgerEntryFormat::getLgrFormat(const std::string& t)
|
||||
{
|
||||
std::map<std::string, LedgerEntryFormat*>::iterator it = byName.find((t));
|
||||
if (it == byName.end())
|
||||
return NULL;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
71
src/cpp/ripple/LedgerFormats.h
Normal file
71
src/cpp/ripple/LedgerFormats.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#ifndef __LEDGERFORMATS__
|
||||
#define __LEDGERFORMATS__
|
||||
|
||||
#include "SerializedObject.h"
|
||||
|
||||
// Used as the type of a transaction or the type of a ledger entry.
|
||||
enum LedgerEntryType
|
||||
{
|
||||
ltINVALID = -1,
|
||||
ltACCOUNT_ROOT = 'a',
|
||||
ltDIR_NODE = 'd',
|
||||
ltGENERATOR_MAP = 'g',
|
||||
ltRIPPLE_STATE = 'r',
|
||||
ltNICKNAME = 'n',
|
||||
ltOFFER = 'o',
|
||||
ltCONTRACT = 'c',
|
||||
ltLEDGER_HASHES = 'h',
|
||||
};
|
||||
|
||||
// Used as a prefix for computing ledger indexes (keys).
|
||||
enum LedgerNameSpace
|
||||
{
|
||||
spaceAccount = 'a',
|
||||
spaceDirNode = 'd',
|
||||
spaceGenerator = 'g',
|
||||
spaceNickname = 'n',
|
||||
spaceRipple = 'r',
|
||||
spaceOffer = 'o', // Entry for an offer.
|
||||
spaceOwnerDir = 'O', // Directory of things owned by an account.
|
||||
spaceBookDir = 'B', // Directory of order books.
|
||||
spaceContract = 'c',
|
||||
spaceHashes = 'h',
|
||||
};
|
||||
|
||||
enum LedgerSpecificFlags
|
||||
{
|
||||
// ltACCOUNT_ROOT
|
||||
lsfPasswordSpent = 0x00010000, // True if password set fee is spent.
|
||||
|
||||
// ltOFFER
|
||||
lsfPassive = 0x00010000,
|
||||
};
|
||||
|
||||
class LedgerEntryFormat
|
||||
{
|
||||
public:
|
||||
std::string t_name;
|
||||
LedgerEntryType t_type;
|
||||
std::vector<SOElement::ptr> elements;
|
||||
|
||||
static std::map<int, LedgerEntryFormat*> byType;
|
||||
static std::map<std::string, LedgerEntryFormat*> byName;
|
||||
|
||||
LedgerEntryFormat(const char *name, LedgerEntryType type) : t_name(name), t_type(type)
|
||||
{
|
||||
byName[name] = this;
|
||||
byType[type] = this;
|
||||
}
|
||||
LedgerEntryFormat& operator<<(const SOElement& el)
|
||||
{
|
||||
elements.push_back(new SOElement(el));
|
||||
return *this;
|
||||
}
|
||||
|
||||
static LedgerEntryFormat* getLgrFormat(LedgerEntryType t);
|
||||
static LedgerEntryFormat* getLgrFormat(const std::string& t);
|
||||
static LedgerEntryFormat* getLgrFormat(int t);
|
||||
};
|
||||
|
||||
#endif
|
||||
// vim:ts=4
|
||||
102
src/cpp/ripple/LedgerHistory.cpp
Normal file
102
src/cpp/ripple/LedgerHistory.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
|
||||
#include "LedgerHistory.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
#include "Config.h"
|
||||
#include "Application.h"
|
||||
|
||||
#ifndef CACHED_LEDGER_NUM
|
||||
#define CACHED_LEDGER_NUM 128
|
||||
#endif
|
||||
|
||||
#ifndef CACHED_LEDGER_AGE
|
||||
#define CACHED_LEDGER_AGE 900
|
||||
#endif
|
||||
|
||||
// FIXME: Need to clean up ledgers by index at some point
|
||||
|
||||
LedgerHistory::LedgerHistory() : mLedgersByHash("LedgerCache", CACHED_LEDGER_NUM, CACHED_LEDGER_AGE)
|
||||
{ ; }
|
||||
|
||||
void LedgerHistory::addLedger(Ledger::pointer ledger)
|
||||
{
|
||||
mLedgersByHash.canonicalize(ledger->getHash(), ledger, true);
|
||||
}
|
||||
|
||||
void LedgerHistory::addAcceptedLedger(Ledger::pointer ledger, bool fromConsensus)
|
||||
{
|
||||
assert(ledger && ledger->isAccepted());
|
||||
uint256 h(ledger->getHash());
|
||||
boost::recursive_mutex::scoped_lock sl(mLedgersByHash.peekMutex());
|
||||
mLedgersByHash.canonicalize(h, ledger, true);
|
||||
assert(ledger);
|
||||
assert(ledger->isAccepted());
|
||||
assert(ledger->isImmutable());
|
||||
mLedgersByIndex[ledger->getLedgerSeq()] = ledger->getHash();
|
||||
|
||||
ledger->pendSave(fromConsensus);
|
||||
}
|
||||
|
||||
Ledger::pointer LedgerHistory::getLedgerBySeq(uint32 index)
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock sl(mLedgersByHash.peekMutex());
|
||||
std::map<uint32, uint256>::iterator it(mLedgersByIndex.find(index));
|
||||
if (it != mLedgersByIndex.end())
|
||||
{
|
||||
uint256 hash = it->second;
|
||||
sl.unlock();
|
||||
return getLedgerByHash(hash);
|
||||
}
|
||||
sl.unlock();
|
||||
|
||||
Ledger::pointer ret(Ledger::loadByIndex(index));
|
||||
if (!ret)
|
||||
return ret;
|
||||
assert(ret->getLedgerSeq() == index);
|
||||
|
||||
sl.lock();
|
||||
mLedgersByHash.canonicalize(ret->getHash(), ret);
|
||||
mLedgersByIndex[ret->getLedgerSeq()] = ret->getHash();
|
||||
return (ret->getLedgerSeq() == index) ? ret : Ledger::pointer();
|
||||
}
|
||||
|
||||
Ledger::pointer LedgerHistory::getLedgerByHash(const uint256& hash)
|
||||
{
|
||||
Ledger::pointer ret = mLedgersByHash.fetch(hash);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = Ledger::loadByHash(hash);
|
||||
if (!ret)
|
||||
return ret;
|
||||
assert(ret->getHash() == hash);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Ledger::pointer LedgerHistory::canonicalizeLedger(Ledger::pointer ledger, bool save)
|
||||
{
|
||||
assert(ledger->isImmutable());
|
||||
uint256 h(ledger->getHash());
|
||||
|
||||
if (!save)
|
||||
{ // return input ledger if not in map, otherwise, return corresponding map ledger
|
||||
Ledger::pointer ret = mLedgersByHash.fetch(h);
|
||||
if (ret)
|
||||
return ret;
|
||||
return ledger;
|
||||
}
|
||||
|
||||
// save input ledger in map if not in map, otherwise return corresponding map ledger
|
||||
boost::recursive_mutex::scoped_lock sl(mLedgersByHash.peekMutex());
|
||||
mLedgersByHash.canonicalize(h, ledger);
|
||||
if (ledger->isAccepted())
|
||||
mLedgersByIndex[ledger->getLedgerSeq()] = ledger->getHash();
|
||||
return ledger;
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
24
src/cpp/ripple/LedgerHistory.h
Normal file
24
src/cpp/ripple/LedgerHistory.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef __LEDGERHISTORY__
|
||||
#define __LEDGERHISTORY__
|
||||
|
||||
#include "TaggedCache.h"
|
||||
#include "Ledger.h"
|
||||
|
||||
class LedgerHistory
|
||||
{
|
||||
TaggedCache<uint256, Ledger> mLedgersByHash;
|
||||
std::map<uint32, uint256> mLedgersByIndex; // accepted ledgers
|
||||
|
||||
public:
|
||||
LedgerHistory();
|
||||
|
||||
void addLedger(Ledger::pointer ledger);
|
||||
void addAcceptedLedger(Ledger::pointer ledger, bool fromConsensus);
|
||||
|
||||
Ledger::pointer getLedgerBySeq(uint32 index);
|
||||
Ledger::pointer getLedgerByHash(const uint256& hash);
|
||||
Ledger::pointer canonicalizeLedger(Ledger::pointer, bool cache);
|
||||
void sweep() { mLedgersByHash.sweep(); }
|
||||
};
|
||||
|
||||
#endif
|
||||
208
src/cpp/ripple/LedgerMaster.cpp
Normal file
208
src/cpp/ripple/LedgerMaster.cpp
Normal file
@@ -0,0 +1,208 @@
|
||||
|
||||
#include "LedgerMaster.h"
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "Application.h"
|
||||
#include "RippleAddress.h"
|
||||
#include "Log.h"
|
||||
|
||||
SETUP_LOG();
|
||||
|
||||
uint32 LedgerMaster::getCurrentLedgerIndex()
|
||||
{
|
||||
return mCurrentLedger->getLedgerSeq();
|
||||
}
|
||||
|
||||
void LedgerMaster::addHeldTransaction(const Transaction::pointer& transaction)
|
||||
{ // returns true if transaction was added
|
||||
boost::recursive_mutex::scoped_lock ml(mLock);
|
||||
mHeldTransactions.push_back(transaction->getSTransaction());
|
||||
}
|
||||
|
||||
void LedgerMaster::pushLedger(Ledger::ref newLedger)
|
||||
{
|
||||
// Caller should already have properly assembled this ledger into "ready-to-close" form --
|
||||
// all candidate transactions must already be applied
|
||||
cLog(lsINFO) << "PushLedger: " << newLedger->getHash();
|
||||
boost::recursive_mutex::scoped_lock ml(mLock);
|
||||
if (!!mFinalizedLedger)
|
||||
{
|
||||
mFinalizedLedger->setClosed();
|
||||
cLog(lsTRACE) << "Finalizes: " << mFinalizedLedger->getHash();
|
||||
}
|
||||
mFinalizedLedger = mCurrentLedger;
|
||||
mCurrentLedger = newLedger;
|
||||
mEngine.setLedger(newLedger);
|
||||
}
|
||||
|
||||
void LedgerMaster::pushLedger(Ledger::ref newLCL, Ledger::ref newOL, bool fromConsensus)
|
||||
{
|
||||
assert(newLCL->isClosed() && newLCL->isAccepted());
|
||||
assert(!newOL->isClosed() && !newOL->isAccepted());
|
||||
|
||||
if (newLCL->isAccepted())
|
||||
{
|
||||
assert(newLCL->isClosed());
|
||||
assert(newLCL->isImmutable());
|
||||
mLedgerHistory.addAcceptedLedger(newLCL, fromConsensus);
|
||||
cLog(lsINFO) << "StashAccepted: " << newLCL->getHash();
|
||||
}
|
||||
|
||||
boost::recursive_mutex::scoped_lock ml(mLock);
|
||||
mFinalizedLedger = newLCL;
|
||||
mCurrentLedger = newOL;
|
||||
mEngine.setLedger(newOL);
|
||||
}
|
||||
|
||||
void LedgerMaster::switchLedgers(Ledger::ref lastClosed, Ledger::ref current)
|
||||
{
|
||||
assert(lastClosed && current);
|
||||
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock ml(mLock);
|
||||
mFinalizedLedger = lastClosed;
|
||||
mFinalizedLedger->setClosed();
|
||||
mFinalizedLedger->setAccepted();
|
||||
mCurrentLedger = current;
|
||||
}
|
||||
|
||||
assert(!mCurrentLedger->isClosed());
|
||||
mEngine.setLedger(mCurrentLedger);
|
||||
}
|
||||
|
||||
void LedgerMaster::storeLedger(Ledger::ref ledger)
|
||||
{
|
||||
mLedgerHistory.addLedger(ledger);
|
||||
if (ledger->isAccepted())
|
||||
mLedgerHistory.addAcceptedLedger(ledger, false);
|
||||
}
|
||||
|
||||
|
||||
Ledger::pointer LedgerMaster::closeLedger(bool recover)
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock sl(mLock);
|
||||
Ledger::pointer closingLedger = mCurrentLedger;
|
||||
|
||||
if (recover)
|
||||
{
|
||||
int recovers = 0;
|
||||
for (CanonicalTXSet::iterator it = mHeldTransactions.begin(), end = mHeldTransactions.end(); it != end; ++it)
|
||||
{
|
||||
try
|
||||
{
|
||||
TER result = mEngine.applyTransaction(*it->second, tapOPEN_LEDGER);
|
||||
if (isTepSuccess(result))
|
||||
++recovers;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
cLog(lsWARNING) << "Held transaction throws";
|
||||
}
|
||||
}
|
||||
tLog(recovers != 0, lsINFO) << "Recovered " << recovers << " held transactions";
|
||||
mHeldTransactions.reset(closingLedger->getHash());
|
||||
}
|
||||
|
||||
mCurrentLedger = boost::make_shared<Ledger>(boost::ref(*closingLedger), true);
|
||||
mEngine.setLedger(mCurrentLedger);
|
||||
|
||||
return closingLedger;
|
||||
}
|
||||
|
||||
TER LedgerMaster::doTransaction(const SerializedTransaction& txn, TransactionEngineParams params)
|
||||
{
|
||||
TER result = mEngine.applyTransaction(txn, params);
|
||||
theApp->getOPs().pubProposedTransaction(mEngine.getLedger(), txn, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void LedgerMaster::acquireMissingLedger(const uint256& ledgerHash, uint32 ledgerSeq)
|
||||
{
|
||||
mMissingLedger = theApp->getMasterLedgerAcquire().findCreate(ledgerHash);
|
||||
if (mMissingLedger->isComplete())
|
||||
{
|
||||
Ledger::pointer lgr = mMissingLedger->getLedger();
|
||||
if (lgr && (lgr->getLedgerSeq() == ledgerSeq))
|
||||
missingAcquireComplete(mMissingLedger);
|
||||
mMissingLedger.reset();
|
||||
return;
|
||||
}
|
||||
mMissingSeq = ledgerSeq;
|
||||
if (mMissingLedger->setAccept())
|
||||
mMissingLedger->addOnComplete(boost::bind(&LedgerMaster::missingAcquireComplete, this, _1));
|
||||
}
|
||||
|
||||
void LedgerMaster::missingAcquireComplete(LedgerAcquire::pointer acq)
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock ml(mLock);
|
||||
|
||||
if (acq->isFailed() && (mMissingSeq != 0))
|
||||
{
|
||||
cLog(lsWARNING) << "Acquire failed for " << mMissingSeq;
|
||||
}
|
||||
|
||||
mMissingLedger.reset();
|
||||
mMissingSeq = 0;
|
||||
|
||||
if (!acq->isFailed())
|
||||
{
|
||||
setFullLedger(acq->getLedger());
|
||||
acq->getLedger()->pendSave(false);
|
||||
}
|
||||
}
|
||||
|
||||
void LedgerMaster::setFullLedger(Ledger::ref ledger)
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock ml(mLock);
|
||||
|
||||
mCompleteLedgers.setValue(ledger->getLedgerSeq());
|
||||
|
||||
if ((ledger->getLedgerSeq() != 0) && mCompleteLedgers.hasValue(ledger->getLedgerSeq() - 1))
|
||||
{ // we think we have the previous ledger, double check
|
||||
Ledger::pointer prevLedger = getLedgerBySeq(ledger->getLedgerSeq() - 1);
|
||||
if (prevLedger && (prevLedger->getHash() != ledger->getParentHash()))
|
||||
{
|
||||
cLog(lsWARNING) << "Ledger " << ledger->getLedgerSeq() << " invalidates prior ledger";
|
||||
mCompleteLedgers.clearValue(prevLedger->getLedgerSeq());
|
||||
}
|
||||
}
|
||||
|
||||
if (mMissingLedger && mMissingLedger->isComplete())
|
||||
mMissingLedger.reset();
|
||||
|
||||
if (mMissingLedger || !theConfig.FULL_HISTORY)
|
||||
return;
|
||||
|
||||
if (Ledger::getPendingSaves() > 3)
|
||||
{
|
||||
cLog(lsINFO) << "Too many pending ledger saves";
|
||||
return;
|
||||
}
|
||||
|
||||
// see if there's a ledger gap we need to fill
|
||||
if (!mCompleteLedgers.hasValue(ledger->getLedgerSeq() - 1))
|
||||
{
|
||||
cLog(lsINFO) << "We need the ledger before the ledger we just accepted";
|
||||
acquireMissingLedger(ledger->getParentHash(), ledger->getLedgerSeq() - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32 prevMissing = mCompleteLedgers.prevMissing(ledger->getLedgerSeq());
|
||||
if (prevMissing != RangeSet::RangeSetAbsent)
|
||||
{
|
||||
cLog(lsINFO) << "Ledger " << prevMissing << " is missing";
|
||||
assert(!mCompleteLedgers.hasValue(prevMissing));
|
||||
Ledger::pointer nextLedger = getLedgerBySeq(prevMissing + 1);
|
||||
if (nextLedger)
|
||||
acquireMissingLedger(nextLedger->getParentHash(), nextLedger->getLedgerSeq() - 1);
|
||||
else
|
||||
{
|
||||
mCompleteLedgers.clearValue(prevMissing);
|
||||
cLog(lsWARNING) << "We have a gap we can't fix: " << prevMissing + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
100
src/cpp/ripple/LedgerMaster.h
Normal file
100
src/cpp/ripple/LedgerMaster.h
Normal file
@@ -0,0 +1,100 @@
|
||||
#ifndef __LEDGERMASTER__
|
||||
#define __LEDGERMASTER__
|
||||
|
||||
#include "Ledger.h"
|
||||
#include "LedgerHistory.h"
|
||||
#include "Peer.h"
|
||||
#include "types.h"
|
||||
#include "LedgerAcquire.h"
|
||||
#include "Transaction.h"
|
||||
#include "TransactionEngine.h"
|
||||
#include "RangeSet.h"
|
||||
#include "CanonicalTXSet.h"
|
||||
|
||||
// Tracks the current ledger and any ledgers in the process of closing
|
||||
// Tracks ledger history
|
||||
// Tracks held transactions
|
||||
|
||||
class LedgerMaster
|
||||
{
|
||||
boost::recursive_mutex mLock;
|
||||
|
||||
TransactionEngine mEngine;
|
||||
|
||||
Ledger::pointer mCurrentLedger; // The ledger we are currently processiong
|
||||
Ledger::pointer mFinalizedLedger; // The ledger that most recently closed
|
||||
|
||||
LedgerHistory mLedgerHistory;
|
||||
|
||||
CanonicalTXSet mHeldTransactions;
|
||||
|
||||
RangeSet mCompleteLedgers;
|
||||
LedgerAcquire::pointer mMissingLedger;
|
||||
uint32 mMissingSeq;
|
||||
|
||||
void applyFutureTransactions(uint32 ledgerIndex);
|
||||
bool isValidTransaction(const Transaction::pointer& trans);
|
||||
bool isTransactionOnFutureList(const Transaction::pointer& trans);
|
||||
|
||||
void acquireMissingLedger(const uint256& ledgerHash, uint32 ledgerSeq);
|
||||
void missingAcquireComplete(LedgerAcquire::pointer);
|
||||
|
||||
public:
|
||||
|
||||
LedgerMaster() : mHeldTransactions(uint256()), mMissingSeq(0) { ; }
|
||||
|
||||
uint32 getCurrentLedgerIndex();
|
||||
|
||||
ScopedLock getLock() { return ScopedLock(mLock); }
|
||||
|
||||
// The current ledger is the ledger we believe new transactions should go in
|
||||
Ledger::pointer getCurrentLedger() { return mCurrentLedger; }
|
||||
|
||||
// The finalized ledger is the last closed/accepted ledger
|
||||
Ledger::pointer getClosedLedger() { return mFinalizedLedger; }
|
||||
|
||||
TER doTransaction(const SerializedTransaction& txn, TransactionEngineParams params);
|
||||
|
||||
void pushLedger(Ledger::ref newLedger);
|
||||
void pushLedger(Ledger::ref newLCL, Ledger::ref newOL, bool fromConsensus);
|
||||
void storeLedger(Ledger::ref);
|
||||
|
||||
void setFullLedger(Ledger::ref ledger);
|
||||
|
||||
void switchLedgers(Ledger::ref lastClosed, Ledger::ref newCurrent);
|
||||
|
||||
std::string getCompleteLedgers() { return mCompleteLedgers.toString(); }
|
||||
|
||||
Ledger::pointer closeLedger(bool recoverHeldTransactions);
|
||||
|
||||
Ledger::pointer getLedgerBySeq(uint32 index)
|
||||
{
|
||||
if (mCurrentLedger && (mCurrentLedger->getLedgerSeq() == index))
|
||||
return mCurrentLedger;
|
||||
if (mFinalizedLedger && (mFinalizedLedger->getLedgerSeq() == index))
|
||||
return mFinalizedLedger;
|
||||
return mLedgerHistory.getLedgerBySeq(index);
|
||||
}
|
||||
|
||||
Ledger::pointer getLedgerByHash(const uint256& hash)
|
||||
{
|
||||
if (hash.isZero())
|
||||
return mCurrentLedger;
|
||||
|
||||
if (mCurrentLedger && (mCurrentLedger->getHash() == hash))
|
||||
return mCurrentLedger;
|
||||
if (mFinalizedLedger && (mFinalizedLedger->getHash() == hash))
|
||||
return mFinalizedLedger;
|
||||
|
||||
return mLedgerHistory.getLedgerByHash(hash);
|
||||
}
|
||||
|
||||
void setLedgerRangePresent(uint32 minV, uint32 maxV) { mCompleteLedgers.setRange(minV, maxV); }
|
||||
|
||||
void addHeldTransaction(const Transaction::pointer& trans);
|
||||
|
||||
void sweep(void) { mLedgerHistory.sweep(); }
|
||||
};
|
||||
|
||||
#endif
|
||||
// vim:ts=4
|
||||
108
src/cpp/ripple/LedgerProposal.cpp
Normal file
108
src/cpp/ripple/LedgerProposal.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
|
||||
#include "LedgerProposal.h"
|
||||
|
||||
#include <boost/make_shared.hpp>
|
||||
|
||||
#include "key.h"
|
||||
#include "Application.h"
|
||||
#include "HashPrefixes.h"
|
||||
|
||||
DECLARE_INSTANCE(LedgerProposal);
|
||||
|
||||
LedgerProposal::LedgerProposal(const uint256& pLgr, uint32 seq, const uint256& tx, uint32 closeTime,
|
||||
const RippleAddress& naPeerPublic, const uint256& suppression) :
|
||||
mPreviousLedger(pLgr), mCurrentHash(tx), mSuppression(suppression), mCloseTime(closeTime),
|
||||
mProposeSeq(seq), mPublicKey(naPeerPublic)
|
||||
{
|
||||
// XXX Validate key.
|
||||
// if (!mKey->SetPubKey(pubKey))
|
||||
// throw std::runtime_error("Invalid public key in proposal");
|
||||
|
||||
mPeerID = mPublicKey.getNodeID();
|
||||
mTime = boost::posix_time::second_clock::universal_time();
|
||||
}
|
||||
|
||||
|
||||
LedgerProposal::LedgerProposal(const RippleAddress& naPub, const RippleAddress& naPriv,
|
||||
const uint256& prevLgr, const uint256& position, uint32 closeTime) :
|
||||
mPreviousLedger(prevLgr), mCurrentHash(position), mCloseTime(closeTime), mProposeSeq(0),
|
||||
mPublicKey(naPub), mPrivateKey(naPriv)
|
||||
{
|
||||
mPeerID = mPublicKey.getNodeID();
|
||||
mTime = boost::posix_time::second_clock::universal_time();
|
||||
}
|
||||
|
||||
LedgerProposal::LedgerProposal(const uint256& prevLgr, const uint256& position, uint32 closeTime) :
|
||||
mPreviousLedger(prevLgr), mCurrentHash(position), mCloseTime(closeTime), mProposeSeq(0)
|
||||
{
|
||||
mTime = boost::posix_time::second_clock::universal_time();
|
||||
}
|
||||
|
||||
uint256 LedgerProposal::getSigningHash() const
|
||||
{
|
||||
Serializer s((32 + 32 + 32 + 256 + 256) / 8);
|
||||
|
||||
s.add32(sHP_Proposal);
|
||||
s.add32(mProposeSeq);
|
||||
s.add32(mCloseTime);
|
||||
s.add256(mPreviousLedger);
|
||||
s.add256(mCurrentHash);
|
||||
|
||||
return s.getSHA512Half();
|
||||
}
|
||||
|
||||
bool LedgerProposal::checkSign(const std::string& signature, const uint256& signingHash)
|
||||
{
|
||||
return mPublicKey.verifyNodePublic(signingHash, signature);
|
||||
}
|
||||
|
||||
bool LedgerProposal::changePosition(const uint256& newPosition, uint32 closeTime)
|
||||
{
|
||||
if (mProposeSeq == seqLeave)
|
||||
return false;
|
||||
|
||||
mCurrentHash = newPosition;
|
||||
mCloseTime = closeTime;
|
||||
mTime = boost::posix_time::second_clock::universal_time();
|
||||
++mProposeSeq;
|
||||
return true;
|
||||
}
|
||||
|
||||
void LedgerProposal::bowOut()
|
||||
{
|
||||
mTime = boost::posix_time::second_clock::universal_time();
|
||||
mProposeSeq = seqLeave;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> LedgerProposal::sign(void)
|
||||
{
|
||||
std::vector<unsigned char> ret;
|
||||
|
||||
mPrivateKey.signNodePrivate(getSigningHash(), ret);
|
||||
// XXX If this can fail, find out sooner.
|
||||
// if (!mPrivateKey.signNodePrivate(getSigningHash(), ret))
|
||||
// throw std::runtime_error("unable to sign proposal");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Json::Value LedgerProposal::getJson() const
|
||||
{
|
||||
Json::Value ret = Json::objectValue;
|
||||
ret["previous_ledger"] = mPreviousLedger.GetHex();
|
||||
|
||||
if (mProposeSeq != seqLeave)
|
||||
{
|
||||
ret["transaction_hash"] = mCurrentHash.GetHex();
|
||||
ret["propose_seq"] = mProposeSeq;
|
||||
}
|
||||
|
||||
ret["close_time"] = mCloseTime;
|
||||
|
||||
if (mPublicKey.isValid())
|
||||
ret["peer_id"] = mPublicKey.humanNodePublic();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
78
src/cpp/ripple/LedgerProposal.h
Normal file
78
src/cpp/ripple/LedgerProposal.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#ifndef __PROPOSELEDGER__
|
||||
#define __PROPOSELEDGER__
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include "../json/value.h"
|
||||
|
||||
#include "RippleAddress.h"
|
||||
#include "Serializer.h"
|
||||
#include "InstanceCounter.h"
|
||||
|
||||
DEFINE_INSTANCE(LedgerProposal);
|
||||
|
||||
class LedgerProposal : private IS_INSTANCE(LedgerProposal)
|
||||
{
|
||||
protected:
|
||||
|
||||
uint256 mPreviousLedger, mCurrentHash, mSuppression;
|
||||
uint32 mCloseTime, mProposeSeq;
|
||||
|
||||
uint160 mPeerID;
|
||||
RippleAddress mPublicKey;
|
||||
RippleAddress mPrivateKey; // If ours
|
||||
|
||||
std::string mSignature; // set only if needed
|
||||
boost::posix_time::ptime mTime;
|
||||
|
||||
public:
|
||||
static const uint32 seqLeave = 0xffffffff; // leaving the consensus process
|
||||
|
||||
typedef boost::shared_ptr<LedgerProposal> pointer;
|
||||
|
||||
// proposal from peer
|
||||
LedgerProposal(const uint256& prevLgr, uint32 proposeSeq, const uint256& propose,
|
||||
uint32 closeTime, const RippleAddress& naPeerPublic, const uint256& suppress);
|
||||
|
||||
// our first proposal
|
||||
LedgerProposal(const RippleAddress& pubKey, const RippleAddress& privKey,
|
||||
const uint256& prevLedger, const uint256& position, uint32 closeTime);
|
||||
|
||||
// an unsigned "dummy" proposal for nodes not validating
|
||||
LedgerProposal(const uint256& prevLedger, const uint256& position, uint32 closeTime);
|
||||
|
||||
uint256 getSigningHash() const;
|
||||
bool checkSign(const std::string& signature, const uint256& signingHash);
|
||||
bool checkSign(const std::string& signature) { return checkSign(signature, getSigningHash()); }
|
||||
bool checkSign() { return checkSign(mSignature, getSigningHash()); }
|
||||
|
||||
const uint160& getPeerID() const { return mPeerID; }
|
||||
const uint256& getCurrentHash() const { return mCurrentHash; }
|
||||
const uint256& getPrevLedger() const { return mPreviousLedger; }
|
||||
const uint256& getSuppression() const { return mSuppression; }
|
||||
uint32 getProposeSeq() const { return mProposeSeq; }
|
||||
uint32 getCloseTime() const { return mCloseTime; }
|
||||
const RippleAddress& peekPublic() const { return mPublicKey; }
|
||||
std::vector<unsigned char> getPubKey() const { return mPublicKey.getNodePublic(); }
|
||||
std::vector<unsigned char> sign();
|
||||
|
||||
void setPrevLedger(const uint256& prevLedger) { mPreviousLedger = prevLedger; }
|
||||
void setSignature(const std::string& signature) { mSignature = signature; }
|
||||
bool hasSignature() { return !mSignature.empty(); }
|
||||
bool isPrevLedger(const uint256& pl) { return mPreviousLedger == pl; }
|
||||
bool isBowOut() { return mProposeSeq == seqLeave; }
|
||||
|
||||
const boost::posix_time::ptime getCreateTime() { return mTime; }
|
||||
bool isStale(boost::posix_time::ptime cutoff) { return mTime <= cutoff; }
|
||||
|
||||
bool changePosition(const uint256& newPosition, uint32 newCloseTime);
|
||||
void bowOut();
|
||||
Json::Value getJson() const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
// vim:ts=4
|
||||
123
src/cpp/ripple/LedgerTiming.cpp
Normal file
123
src/cpp/ripple/LedgerTiming.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
|
||||
#include "LedgerTiming.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#include "Log.h"
|
||||
SETUP_LOG();
|
||||
|
||||
// NOTE: First and last times must be repeated
|
||||
int ContinuousLedgerTiming::LedgerTimeResolution[] = { 10, 10, 20, 30, 60, 90, 120, 120 };
|
||||
|
||||
// Called when a ledger is open and no close is in progress -- when a transaction is received and no close
|
||||
// is in process, or when a close completes. Returns the number of seconds the ledger should be be open.
|
||||
bool ContinuousLedgerTiming::shouldClose(
|
||||
bool anyTransactions,
|
||||
int previousProposers, // proposers in the last closing
|
||||
int proposersClosed, // proposers who have currently closed this ledgers
|
||||
int previousMSeconds, // seconds the previous ledger took to reach consensus
|
||||
int currentMSeconds, // seconds since the previous ledger closed
|
||||
int idleInterval) // network's desired idle interval
|
||||
{
|
||||
if ((previousMSeconds < -1000) || (previousMSeconds > 600000) ||
|
||||
(currentMSeconds < -1000) || (currentMSeconds > 600000))
|
||||
{
|
||||
cLog(lsWARNING) <<
|
||||
boost::str(boost::format("CLC::shouldClose range Trans=%s, Prop: %d/%d, Secs: %d (last:%d)")
|
||||
% (anyTransactions ? "yes" : "no") % previousProposers % proposersClosed
|
||||
% currentMSeconds % previousMSeconds);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!anyTransactions)
|
||||
{ // no transactions so far this interval
|
||||
if (proposersClosed > (previousProposers / 4)) // did we miss a transaction?
|
||||
{
|
||||
cLog(lsTRACE) << "no transactions, many proposers: now (" << proposersClosed << " closed, "
|
||||
<< previousProposers << " before)";
|
||||
return true;
|
||||
}
|
||||
#if 0 // This false triggers on the genesis ledger
|
||||
if (previousMSeconds > (1000 * (LEDGER_IDLE_INTERVAL + 2))) // the last ledger was very slow to close
|
||||
{
|
||||
cLog(lsTRACE) << "was slow to converge (p=" << (previousMSeconds) << ")";
|
||||
if (previousMSeconds < 2000)
|
||||
return previousMSeconds;
|
||||
return previousMSeconds - 1000;
|
||||
}
|
||||
#endif
|
||||
return currentMSeconds >= (idleInterval * 1000); // normal idle
|
||||
}
|
||||
|
||||
return true; // this ledger should close now
|
||||
}
|
||||
|
||||
// Returns whether we have a consensus or not. If so, we expect all honest nodes
|
||||
// to already have everything they need to accept a consensus. Our vote is 'locked in'.
|
||||
bool ContinuousLedgerTiming::haveConsensus(
|
||||
int previousProposers, // proposers in the last closing (not including us)
|
||||
int currentProposers, // proposers in this closing so far (not including us)
|
||||
int currentAgree, // proposers who agree with us
|
||||
int currentFinished, // proposers who have validated a ledger after this one
|
||||
int previousAgreeTime, // how long it took to agree on the last ledger
|
||||
int currentAgreeTime, // how long we've been trying to agree
|
||||
bool forReal) // deciding whether to stop consensus process
|
||||
{
|
||||
cLog(lsTRACE) << boost::str(boost::format("CLC::haveConsensus: prop=%d/%d agree=%d validated=%d time=%d/%d%s") %
|
||||
currentProposers % previousProposers % currentAgree % currentFinished % currentAgreeTime % previousAgreeTime %
|
||||
(forReal ? "" : "X"));
|
||||
|
||||
if (currentAgreeTime <= LEDGER_MIN_CONSENSUS)
|
||||
return false;
|
||||
|
||||
if (currentProposers < (previousProposers * 3 / 4))
|
||||
{ // Less than 3/4 of the last ledger's proposers are present, we may need more time
|
||||
if (currentAgreeTime < (previousAgreeTime + LEDGER_MIN_CONSENSUS))
|
||||
{
|
||||
tLog(forReal, lsTRACE) << "too fast, not enough proposers";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If 80% of current proposers (plus us) agree on a set, we have consensus
|
||||
if (((currentAgree * 100 + 100) / (currentProposers + 1)) > 80)
|
||||
{
|
||||
tLog(forReal, lsINFO) << "normal consensus";
|
||||
return true;
|
||||
}
|
||||
|
||||
// If 50% of the nodes on your UNL have moved on, you should declare consensus
|
||||
if (((currentFinished * 100) / (currentProposers + 1)) > 50)
|
||||
{
|
||||
tLog(forReal, lsWARNING) << "We see no consensus, but 50% of nodes have moved on";
|
||||
return true;
|
||||
}
|
||||
|
||||
// no consensus yet
|
||||
tLog(forReal, lsTRACE) << "no consensus";
|
||||
return false;
|
||||
}
|
||||
|
||||
int ContinuousLedgerTiming::getNextLedgerTimeResolution(int previousResolution, bool previousAgree, int ledgerSeq)
|
||||
{
|
||||
assert(ledgerSeq);
|
||||
if ((!previousAgree) && ((ledgerSeq % LEDGER_RES_DECREASE) == 0))
|
||||
{ // reduce resolution
|
||||
int i = 1;
|
||||
while (LedgerTimeResolution[i] != previousResolution)
|
||||
++i;
|
||||
return LedgerTimeResolution[i + 1];
|
||||
}
|
||||
|
||||
if ((previousAgree) && ((ledgerSeq % LEDGER_RES_INCREASE) == 0))
|
||||
{ // increase resolution
|
||||
int i = 1;
|
||||
while (LedgerTimeResolution[i] != previousResolution)
|
||||
++i;
|
||||
return LedgerTimeResolution[i - 1];
|
||||
}
|
||||
|
||||
return previousResolution;
|
||||
}
|
||||
74
src/cpp/ripple/LedgerTiming.h
Normal file
74
src/cpp/ripple/LedgerTiming.h
Normal file
@@ -0,0 +1,74 @@
|
||||
#ifndef __LEDGERTIMING__
|
||||
#define __LEDGERTIMING__
|
||||
|
||||
// The number of seconds a ledger may remain idle before closing
|
||||
# define LEDGER_IDLE_INTERVAL 15
|
||||
|
||||
// The number of seconds a validation remains current after its ledger's close time
|
||||
// This is a safety to protect against very old validations and the time it takes to adjust
|
||||
// the close time accuracy window
|
||||
# define LEDGER_VAL_INTERVAL 600
|
||||
|
||||
// The number of seconds before a close time that we consider a validation acceptable
|
||||
// This protects against extreme clock errors
|
||||
# define LEDGER_EARLY_INTERVAL 240
|
||||
|
||||
// The number of milliseconds we wait minimum to ensure participation
|
||||
# define LEDGER_MIN_CONSENSUS 2000
|
||||
|
||||
// Initial resolution of ledger close time
|
||||
# define LEDGER_TIME_ACCURACY 30
|
||||
|
||||
// How often to increase resolution
|
||||
# define LEDGER_RES_INCREASE 8
|
||||
|
||||
// How often to decrease resolution
|
||||
# define LEDGER_RES_DECREASE 1
|
||||
|
||||
// How often we check state or change positions (in milliseconds)
|
||||
# define LEDGER_GRANULARITY 1000
|
||||
|
||||
// The percentage of active trusted validators that must be able to
|
||||
// keep up with the network or we consider the network overloaded
|
||||
# define LEDGER_NET_RATIO 70
|
||||
|
||||
// How long we consider a proposal fresh
|
||||
# define PROPOSE_FRESHNESS 20
|
||||
|
||||
// How often we force generating a new proposal to keep ours fresh
|
||||
# define PROPOSE_INTERVAL 12
|
||||
|
||||
// Avalanche tuning
|
||||
#define AV_INIT_CONSENSUS_PCT 50 // percentage of nodes on our UNL that must vote yes
|
||||
|
||||
#define AV_MID_CONSENSUS_TIME 50 // percentage of previous close time before we advance
|
||||
#define AV_MID_CONSENSUS_PCT 65 // percentage of nodes that most vote yes after advancing
|
||||
|
||||
#define AV_LATE_CONSENSUS_TIME 85 // percentage of previous close time before we advance
|
||||
#define AV_LATE_CONSENSUS_PCT 70 // percentage of nodes that most vote yes after advancing
|
||||
|
||||
|
||||
class ContinuousLedgerTiming
|
||||
{
|
||||
public:
|
||||
|
||||
static int LedgerTimeResolution[];
|
||||
|
||||
// Returns the number of seconds the ledger was or should be open
|
||||
// Call when a consensus is reached and when any transaction is relayed to be added
|
||||
static bool shouldClose(
|
||||
bool anyTransactions,
|
||||
int previousProposers, int proposersClosed,
|
||||
int previousSeconds, int currentSeconds,
|
||||
int idleInterval);
|
||||
|
||||
static bool haveConsensus(
|
||||
int previousProposers, int currentProposers,
|
||||
int currentAgree, int currentClosed,
|
||||
int previousAgreeTime, int currentAgreeTime,
|
||||
bool forReal);
|
||||
|
||||
static int getNextLedgerTimeResolution(int previousResolution, bool previousAgree, int ledgerSeq);
|
||||
};
|
||||
|
||||
#endif
|
||||
188
src/cpp/ripple/Log.cpp
Normal file
188
src/cpp/ripple/Log.cpp
Normal file
@@ -0,0 +1,188 @@
|
||||
|
||||
#include "Log.h"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
boost::recursive_mutex Log::sLock;
|
||||
|
||||
LogSeverity Log::sMinSeverity = lsINFO;
|
||||
|
||||
std::ofstream* Log::outStream = NULL;
|
||||
boost::filesystem::path *Log::pathToLog = NULL;
|
||||
uint32 Log::logRotateCounter = 0;
|
||||
|
||||
LogPartition* LogPartition::headLog = NULL;
|
||||
|
||||
LogPartition::LogPartition(const char *name) : mNextLog(headLog), mMinSeverity(lsWARNING)
|
||||
{
|
||||
const char *ptr = strrchr(name, '/');
|
||||
mName = (ptr == NULL) ? name : (ptr + 1);
|
||||
|
||||
size_t p = mName.find(".cpp");
|
||||
if (p != std::string::npos)
|
||||
mName.erase(mName.begin() + p, mName.end());
|
||||
|
||||
headLog = this;
|
||||
}
|
||||
|
||||
std::vector< std::pair<std::string, std::string> > LogPartition::getSeverities()
|
||||
{
|
||||
std::vector< std::pair<std::string, std::string> > sevs;
|
||||
|
||||
for (LogPartition *l = headLog; l != NULL; l = l->mNextLog)
|
||||
sevs.push_back(std::make_pair(l->mName, Log::severityToString(l->mMinSeverity)));
|
||||
|
||||
return sevs;
|
||||
}
|
||||
|
||||
Log::~Log()
|
||||
{
|
||||
std::string logMsg = boost::posix_time::to_simple_string(boost::posix_time::second_clock::universal_time());
|
||||
if (!mPartitionName.empty())
|
||||
logMsg += " " + mPartitionName + ":";
|
||||
else
|
||||
logMsg += " ";
|
||||
switch (mSeverity)
|
||||
{
|
||||
case lsTRACE: logMsg += "TRC "; break;
|
||||
case lsDEBUG: logMsg += "DBG "; break;
|
||||
case lsINFO: logMsg += "NFO "; break;
|
||||
case lsWARNING: logMsg += "WRN "; break;
|
||||
case lsERROR: logMsg += "ERR "; break;
|
||||
case lsFATAL: logMsg += "FTL "; break;
|
||||
case lsINVALID: assert(false); return;
|
||||
}
|
||||
logMsg += oss.str();
|
||||
boost::recursive_mutex::scoped_lock sl(sLock);
|
||||
if (mSeverity >= sMinSeverity)
|
||||
std::cerr << logMsg << std::endl;
|
||||
if (outStream != NULL)
|
||||
(*outStream) << logMsg << std::endl;
|
||||
}
|
||||
|
||||
|
||||
std::string Log::rotateLog(void)
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock sl(sLock);
|
||||
boost::filesystem::path abs_path;
|
||||
std::string abs_path_str;
|
||||
|
||||
uint32 failsafe = 0;
|
||||
|
||||
std::string abs_new_path_str;
|
||||
do {
|
||||
std::string s;
|
||||
std::stringstream out;
|
||||
|
||||
failsafe++;
|
||||
if (failsafe == std::numeric_limits<uint32>::max()) {
|
||||
return "unable to create new log file; too many log files!";
|
||||
}
|
||||
abs_path = boost::filesystem::absolute("");
|
||||
abs_path /= *pathToLog;
|
||||
abs_path_str = abs_path.parent_path().string();
|
||||
out << logRotateCounter;
|
||||
s = out.str();
|
||||
|
||||
|
||||
abs_new_path_str = abs_path_str + "/" + s + + "_" + pathToLog->filename().string();
|
||||
|
||||
logRotateCounter++;
|
||||
|
||||
} while (boost::filesystem::exists(boost::filesystem::path(abs_new_path_str)));
|
||||
|
||||
outStream->close();
|
||||
boost::filesystem::rename(abs_path, boost::filesystem::path(abs_new_path_str));
|
||||
|
||||
|
||||
|
||||
setLogFile(*pathToLog);
|
||||
|
||||
return abs_new_path_str;
|
||||
|
||||
}
|
||||
|
||||
void Log::setMinSeverity(LogSeverity s, bool all)
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock sl(sLock);
|
||||
sMinSeverity = s;
|
||||
if (all)
|
||||
LogPartition::setSeverity(s);
|
||||
}
|
||||
|
||||
LogSeverity Log::getMinSeverity()
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock sl(sLock);
|
||||
return sMinSeverity;
|
||||
}
|
||||
|
||||
std::string Log::severityToString(LogSeverity s)
|
||||
{
|
||||
switch (s)
|
||||
{
|
||||
case lsTRACE: return "Trace";
|
||||
case lsDEBUG: return "Debug";
|
||||
case lsINFO: return "Info";
|
||||
case lsWARNING: return "Warning";
|
||||
case lsERROR: return "Error";
|
||||
case lsFATAL: return "Fatal";
|
||||
default: assert(false); return "Unknown";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LogSeverity Log::stringToSeverity(const std::string& s)
|
||||
{
|
||||
if (boost::iequals(s, "trace"))
|
||||
return lsTRACE;
|
||||
if (boost::iequals(s, "debug"))
|
||||
return lsDEBUG;
|
||||
if (boost::iequals(s, "info") || boost::iequals(s, "information"))
|
||||
return lsINFO;
|
||||
if (boost::iequals(s, "warn") || boost::iequals(s, "warning") || boost::iequals(s, "warnings"))
|
||||
return lsWARNING;
|
||||
if (boost::iequals(s, "error") || boost::iequals(s, "errors"))
|
||||
return lsERROR;
|
||||
if (boost::iequals(s, "fatal") || boost::iequals(s, "fatals"))
|
||||
return lsFATAL;
|
||||
return lsINVALID;
|
||||
}
|
||||
|
||||
void Log::setLogFile(boost::filesystem::path path)
|
||||
{
|
||||
std::ofstream* newStream = new std::ofstream(path.c_str(), std::fstream::app);
|
||||
if (!newStream->good())
|
||||
{
|
||||
delete newStream;
|
||||
newStream = NULL;
|
||||
}
|
||||
|
||||
boost::recursive_mutex::scoped_lock sl(sLock);
|
||||
if (outStream != NULL)
|
||||
delete outStream;
|
||||
outStream = newStream;
|
||||
if (outStream)
|
||||
Log(lsINFO) << "Starting up";
|
||||
|
||||
pathToLog = new boost::filesystem::path(path);
|
||||
}
|
||||
|
||||
bool LogPartition::setSeverity(const std::string& partition, LogSeverity severity)
|
||||
{
|
||||
for (LogPartition *p = headLog; p != NULL; p = p->mNextLog)
|
||||
if (boost::iequals(p->mName, partition))
|
||||
{
|
||||
p->mMinSeverity = severity;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void LogPartition::setSeverity(LogSeverity severity)
|
||||
{
|
||||
for (LogPartition *p = headLog; p != NULL; p = p->mNextLog)
|
||||
p->mMinSeverity = severity;
|
||||
}
|
||||
106
src/cpp/ripple/Log.h
Normal file
106
src/cpp/ripple/Log.h
Normal file
@@ -0,0 +1,106 @@
|
||||
#ifndef __LOG__
|
||||
#define __LOG__
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <limits>
|
||||
|
||||
#include <boost/thread/recursive_mutex.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
// Ensure that we don't get value.h without writer.h
|
||||
#include "../json/json.h"
|
||||
|
||||
#include "types.h"
|
||||
|
||||
// Put at the beginning of a C++ file that needs its own log partition
|
||||
#define SETUP_LOG() static LogPartition logPartition(__FILE__)
|
||||
|
||||
// Standard conditional log
|
||||
#define cLog(x) if (!logPartition.doLog(x)) do {} while (0); else Log(x, logPartition)
|
||||
|
||||
// Log only if an additional condition 'c' is true. Condition is not computed if not needed
|
||||
#define tLog(c,x) if (!logPartition.doLog(x) || !(c)) do {} while(0); else Log(x, logPartition)
|
||||
|
||||
// Check if should log
|
||||
#define sLog(x) (logPartition.doLog(x))
|
||||
|
||||
|
||||
enum LogSeverity
|
||||
{
|
||||
lsINVALID = -1, // used to indicate an invalid severity
|
||||
lsTRACE = 0, // Very low-level progress information, details inside an operation
|
||||
lsDEBUG = 1, // Function-level progress information, operations
|
||||
lsINFO = 2, // Server-level progress information, major operations
|
||||
lsWARNING = 3, // Conditions that warrant human attention, may indicate a problem
|
||||
lsERROR = 4, // A condition that indicates a problem
|
||||
lsFATAL = 5 // A severe condition that indicates a server problem
|
||||
};
|
||||
|
||||
class LogPartition
|
||||
{
|
||||
protected:
|
||||
static LogPartition* headLog;
|
||||
|
||||
LogPartition* mNextLog;
|
||||
LogSeverity mMinSeverity;
|
||||
std::string mName;
|
||||
|
||||
public:
|
||||
LogPartition(const char *name);
|
||||
|
||||
bool doLog(LogSeverity s) { return s >= mMinSeverity; }
|
||||
const std::string& getName() const { return mName; }
|
||||
|
||||
static bool setSeverity(const std::string& partition, LogSeverity severity);
|
||||
static void setSeverity(LogSeverity severity);
|
||||
static std::vector< std::pair<std::string, std::string> > getSeverities();
|
||||
};
|
||||
|
||||
class Log
|
||||
{
|
||||
private:
|
||||
Log(const Log&); // no implementation
|
||||
Log& operator=(const Log&); // no implementation
|
||||
|
||||
protected:
|
||||
static boost::recursive_mutex sLock;
|
||||
static LogSeverity sMinSeverity;
|
||||
static std::ofstream* outStream;
|
||||
|
||||
mutable std::ostringstream oss;
|
||||
LogSeverity mSeverity;
|
||||
std::string mPartitionName;
|
||||
|
||||
static boost::filesystem::path *pathToLog;
|
||||
static uint32 logRotateCounter;
|
||||
|
||||
public:
|
||||
Log(LogSeverity s) : mSeverity(s)
|
||||
{ ; }
|
||||
|
||||
Log(LogSeverity s, const LogPartition& p) : mSeverity(s), mPartitionName(p.getName())
|
||||
{ ; }
|
||||
|
||||
~Log();
|
||||
|
||||
template<typename T> std::ostream& operator<<(const T& t) const
|
||||
{
|
||||
return oss << t;
|
||||
}
|
||||
|
||||
std::ostringstream& ref(void) const
|
||||
{
|
||||
return oss;
|
||||
}
|
||||
|
||||
static std::string severityToString(LogSeverity);
|
||||
static LogSeverity stringToSeverity(const std::string&);
|
||||
|
||||
static LogSeverity getMinSeverity();
|
||||
static void setMinSeverity(LogSeverity, bool all);
|
||||
static void setLogFile(boost::filesystem::path);
|
||||
static std::string rotateLog(void);
|
||||
};
|
||||
|
||||
#endif
|
||||
1252
src/cpp/ripple/NetworkOPs.cpp
Normal file
1252
src/cpp/ripple/NetworkOPs.cpp
Normal file
File diff suppressed because it is too large
Load Diff
242
src/cpp/ripple/NetworkOPs.h
Normal file
242
src/cpp/ripple/NetworkOPs.h
Normal file
@@ -0,0 +1,242 @@
|
||||
#ifndef __NETWORK_OPS__
|
||||
#define __NETWORK_OPS__
|
||||
|
||||
#include <boost/interprocess/sync/interprocess_upgradable_mutex.hpp>
|
||||
#include <boost/interprocess/sync/sharable_lock.hpp>
|
||||
#include <boost/unordered_map.hpp>
|
||||
#include <boost/unordered_set.hpp>
|
||||
|
||||
#include "AccountState.h"
|
||||
#include "LedgerMaster.h"
|
||||
#include "NicknameState.h"
|
||||
#include "RippleState.h"
|
||||
#include "SerializedValidation.h"
|
||||
#include "LedgerAcquire.h"
|
||||
#include "LedgerProposal.h"
|
||||
|
||||
// Operations that clients may wish to perform against the network
|
||||
// Master operational handler, server sequencer, network tracker
|
||||
|
||||
class Peer;
|
||||
class LedgerConsensus;
|
||||
|
||||
class InfoSub
|
||||
{
|
||||
public:
|
||||
|
||||
virtual ~InfoSub() { ; }
|
||||
|
||||
virtual void send(const Json::Value& jvObj) = 0;
|
||||
};
|
||||
|
||||
class NetworkOPs
|
||||
{
|
||||
public:
|
||||
enum Fault
|
||||
{ // exceptions these functions can throw
|
||||
IO_ERROR = 1,
|
||||
NO_NETWORK = 2,
|
||||
};
|
||||
|
||||
enum OperatingMode
|
||||
{ // how we process transactions or account balance requests
|
||||
omDISCONNECTED = 0, // not ready to process requests
|
||||
omCONNECTED = 1, // convinced we are talking to the network
|
||||
omTRACKING = 2, // convinced we agree with the network
|
||||
omFULL = 3 // we have the ledger and can even validate
|
||||
};
|
||||
|
||||
protected:
|
||||
typedef boost::unordered_map<uint160,boost::unordered_set<InfoSub*> > subInfoMapType;
|
||||
typedef boost::unordered_map<uint160,boost::unordered_set<InfoSub*> >::value_type subInfoMapValue;
|
||||
typedef boost::unordered_map<uint160,boost::unordered_set<InfoSub*> >::iterator subInfoMapIterator;
|
||||
|
||||
typedef boost::unordered_map<uint160,std::pair<InfoSub*,uint32> > subSubmitMapType;
|
||||
|
||||
OperatingMode mMode;
|
||||
bool mNeedNetworkLedger;
|
||||
boost::posix_time::ptime mConnectTime;
|
||||
boost::asio::deadline_timer mNetTimer;
|
||||
boost::shared_ptr<LedgerConsensus> mConsensus;
|
||||
boost::unordered_map<uint160,
|
||||
std::list<LedgerProposal::pointer> > mStoredProposals;
|
||||
|
||||
LedgerMaster* mLedgerMaster;
|
||||
LedgerAcquire::pointer mAcquiringLedger;
|
||||
|
||||
int mCloseTimeOffset;
|
||||
|
||||
// last ledger close
|
||||
int mLastCloseProposers, mLastCloseConvergeTime;
|
||||
uint256 mLastCloseHash;
|
||||
uint32 mLastCloseTime;
|
||||
uint32 mLastValidationTime;
|
||||
|
||||
// XXX Split into more locks.
|
||||
boost::interprocess::interprocess_upgradable_mutex mMonitorLock;
|
||||
subInfoMapType mSubAccount;
|
||||
subInfoMapType mSubRTAccount;
|
||||
subSubmitMapType mSubmitMap;
|
||||
|
||||
boost::unordered_set<InfoSub*> mSubLedger; // accepted ledgers
|
||||
boost::unordered_set<InfoSub*> mSubServer; // when server changes connectivity state
|
||||
boost::unordered_set<InfoSub*> mSubTransactions; // all accepted transactions
|
||||
boost::unordered_set<InfoSub*> mSubRTTransactions; // all proposed and accepted transactions
|
||||
|
||||
|
||||
void setMode(OperatingMode);
|
||||
|
||||
Json::Value transJson(const SerializedTransaction& stTxn, TER terResult, bool bAccepted, Ledger::ref lpCurrent, const std::string& strType);
|
||||
bool haveConsensusObject();
|
||||
|
||||
Json::Value pubBootstrapAccountInfo(Ledger::ref lpAccepted, const RippleAddress& naAccountID);
|
||||
|
||||
void pubAcceptedTransaction(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult);
|
||||
void pubAccountTransaction(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult,bool accepted);
|
||||
|
||||
public:
|
||||
NetworkOPs(boost::asio::io_service& io_service, LedgerMaster* pLedgerMaster);
|
||||
|
||||
// network information
|
||||
uint32 getNetworkTimeNC();
|
||||
uint32 getCloseTimeNC();
|
||||
uint32 getValidationTimeNC();
|
||||
void closeTimeOffset(int);
|
||||
boost::posix_time::ptime getNetworkTimePT();
|
||||
uint32 getLedgerID(const uint256& hash);
|
||||
uint32 getCurrentLedgerID();
|
||||
OperatingMode getOperatingMode() { return mMode; }
|
||||
inline bool available() {
|
||||
// XXX Later this can be relaxed to omCONNECTED
|
||||
return mMode >= omTRACKING;
|
||||
}
|
||||
|
||||
Ledger::pointer getClosedLedger() { return mLedgerMaster->getClosedLedger(); }
|
||||
Ledger::pointer getCurrentLedger() { return mLedgerMaster->getCurrentLedger(); }
|
||||
Ledger::pointer getLedgerByHash(const uint256& hash) { return mLedgerMaster->getLedgerByHash(hash); }
|
||||
Ledger::pointer getLedgerBySeq(const uint32 seq) { return mLedgerMaster->getLedgerBySeq(seq); }
|
||||
|
||||
uint256 getClosedLedgerHash()
|
||||
{ return mLedgerMaster->getClosedLedger()->getHash(); }
|
||||
|
||||
SLE::pointer getSLE(Ledger::pointer lpLedger, const uint256& uHash) { return lpLedger->getSLE(uHash); }
|
||||
|
||||
//
|
||||
// Transaction operations
|
||||
//
|
||||
Transaction::pointer submitTransaction(const Transaction::pointer& tpTrans);
|
||||
|
||||
Transaction::pointer processTransaction(Transaction::pointer transaction);
|
||||
Transaction::pointer findTransactionByID(const uint256& transactionID);
|
||||
int findTransactionsBySource(const uint256& uLedger, std::list<Transaction::pointer>&, const RippleAddress& sourceAccount,
|
||||
uint32 minSeq, uint32 maxSeq);
|
||||
int findTransactionsByDestination(std::list<Transaction::pointer>&, const RippleAddress& destinationAccount,
|
||||
uint32 startLedgerSeq, uint32 endLedgerSeq, int maxTransactions);
|
||||
|
||||
//
|
||||
// Account functions
|
||||
//
|
||||
|
||||
AccountState::pointer getAccountState(const uint256& uLedger, const RippleAddress& accountID);
|
||||
SLE::pointer getGenerator(const uint256& uLedger, const uint160& uGeneratorID);
|
||||
|
||||
//
|
||||
// Directory functions
|
||||
//
|
||||
|
||||
STVector256 getDirNodeInfo(const uint256& uLedger, const uint256& uRootIndex,
|
||||
uint64& uNodePrevious, uint64& uNodeNext);
|
||||
|
||||
//
|
||||
// Nickname functions
|
||||
//
|
||||
|
||||
NicknameState::pointer getNicknameState(const uint256& uLedger, const std::string& strNickname);
|
||||
|
||||
//
|
||||
// Owner functions
|
||||
//
|
||||
|
||||
Json::Value getOwnerInfo(const uint256& uLedger, const RippleAddress& naAccount);
|
||||
Json::Value getOwnerInfo(Ledger::pointer lpLedger, const RippleAddress& naAccount);
|
||||
|
||||
// raw object operations
|
||||
bool findRawLedger(const uint256& ledgerHash, std::vector<unsigned char>& rawLedger);
|
||||
bool findRawTransaction(const uint256& transactionHash, std::vector<unsigned char>& rawTransaction);
|
||||
bool findAccountNode(const uint256& nodeHash, std::vector<unsigned char>& rawAccountNode);
|
||||
bool findTransactionNode(const uint256& nodeHash, std::vector<unsigned char>& rawTransactionNode);
|
||||
|
||||
// tree synchronization operations
|
||||
bool getTransactionTreeNodes(uint32 ledgerSeq, const uint256& myNodeID,
|
||||
const std::vector<unsigned char>& myNode, std::list< std::vector<unsigned char> >& newNodes);
|
||||
bool getAccountStateNodes(uint32 ledgerSeq, const uint256& myNodeId,
|
||||
const std::vector<unsigned char>& myNode, std::list< std::vector<unsigned char> >& newNodes);
|
||||
|
||||
// ledger proposal/close functions
|
||||
void processTrustedProposal(LedgerProposal::pointer proposal, boost::shared_ptr<ripple::TMProposeSet> set,
|
||||
RippleAddress nodePublic, uint256 checkLedger, bool sigGood);
|
||||
bool gotTXData(const boost::shared_ptr<Peer>& peer, const uint256& hash,
|
||||
const std::list<SHAMapNode>& nodeIDs, const std::list< std::vector<unsigned char> >& nodeData);
|
||||
bool recvValidation(const SerializedValidation::pointer& val);
|
||||
SHAMap::pointer getTXMap(const uint256& hash);
|
||||
bool hasTXSet(const boost::shared_ptr<Peer>& peer, const uint256& set, ripple::TxSetStatus status);
|
||||
void mapComplete(const uint256& hash, SHAMap::ref map);
|
||||
|
||||
// network state machine
|
||||
void checkState(const boost::system::error_code& result);
|
||||
void switchLastClosedLedger(Ledger::pointer newLedger, bool duringConsensus); // Used for the "jump" case
|
||||
bool checkLastClosedLedger(const std::vector<Peer::pointer>&, uint256& networkClosed);
|
||||
int beginConsensus(const uint256& networkClosed, Ledger::ref closingLedger);
|
||||
void endConsensus(bool correctLCL);
|
||||
void setStandAlone() { setMode(omFULL); }
|
||||
void setStateTimer();
|
||||
void newLCL(int proposers, int convergeTime, const uint256& ledgerHash);
|
||||
void needNetworkLedger() { mNeedNetworkLedger = true; }
|
||||
void clearNeedNetworkLedger() { mNeedNetworkLedger = false; }
|
||||
bool isNeedNetworkLedger() { return mNeedNetworkLedger; }
|
||||
void consensusViewChange();
|
||||
int getPreviousProposers() { return mLastCloseProposers; }
|
||||
int getPreviousConvergeTime() { return mLastCloseConvergeTime; }
|
||||
uint32 getLastCloseTime() { return mLastCloseTime; }
|
||||
void setLastCloseTime(uint32 t) { mLastCloseTime = t; }
|
||||
Json::Value getServerInfo();
|
||||
uint32 acceptLedger();
|
||||
boost::unordered_map<uint160,
|
||||
std::list<LedgerProposal::pointer> >& peekStoredProposals() { return mStoredProposals; }
|
||||
void storeProposal(const LedgerProposal::pointer& proposal, const RippleAddress& peerPublic);
|
||||
uint256 getConsensusLCL();
|
||||
|
||||
// client information retrieval functions
|
||||
std::vector< std::pair<uint32, uint256> >
|
||||
getAffectedAccounts(const RippleAddress& account, uint32 minLedger, uint32 maxLedger);
|
||||
std::vector<RippleAddress> getLedgerAffectedAccounts(uint32 ledgerSeq);
|
||||
std::vector<SerializedTransaction> getLedgerTransactions(uint32 ledgerSeq);
|
||||
|
||||
//
|
||||
// Monitoring: publisher side
|
||||
//
|
||||
void pubLedger(Ledger::ref lpAccepted);
|
||||
void pubProposedTransaction(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult);
|
||||
|
||||
|
||||
//
|
||||
// Monitoring: subscriber side
|
||||
//
|
||||
void subAccount(InfoSub* ispListener, const boost::unordered_set<RippleAddress>& vnaAccountIDs,bool rt);
|
||||
void unsubAccount(InfoSub* ispListener, const boost::unordered_set<RippleAddress>& vnaAccountIDs,bool rt);
|
||||
|
||||
bool subLedger(InfoSub* ispListener, Json::Value& jvResult);
|
||||
bool unsubLedger(InfoSub* ispListener);
|
||||
|
||||
bool subServer(InfoSub* ispListener, Json::Value& jvResult);
|
||||
bool unsubServer(InfoSub* ispListener);
|
||||
|
||||
bool subTransactions(InfoSub* ispListener);
|
||||
bool unsubTransactions(InfoSub* ispListener);
|
||||
|
||||
bool subRTTransactions(InfoSub* ispListener);
|
||||
bool unsubRTTransactions(InfoSub* ispListener);
|
||||
};
|
||||
|
||||
#endif
|
||||
// vim:ts=4
|
||||
27
src/cpp/ripple/NetworkStatus.h
Normal file
27
src/cpp/ripple/NetworkStatus.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef __NETWORKSTATUS__
|
||||
#define __NETWORKSTATUS__
|
||||
|
||||
struct NSBit
|
||||
{ // a network status bit
|
||||
const char *name, *description;
|
||||
int number;
|
||||
};
|
||||
|
||||
struct NetworkStatus
|
||||
{
|
||||
static const int nsbConnected=0; // connected to the network
|
||||
static const int nsbAccepted=1; // accept this as the real network
|
||||
static const int nsbFastSynching=2; // catching up, skipping transactions
|
||||
static const int nsbSlowSynching=3; // catching up, txn by txn
|
||||
static const int nsbSynched=4; // in synch with the network
|
||||
static const int nsbIdentifiable=5; // not hiding our identity
|
||||
static const int nsbLedgerSync=6; // participating in ledger sync
|
||||
static const int nsbStuck=7; // unable to sync
|
||||
static const int nsbShuttingDown=8; // node is shutting down
|
||||
|
||||
static const int nnbCount=32;
|
||||
std::bitset<nnbCount> nsbValues;
|
||||
std::map<int,NSBit> nsbData;
|
||||
};
|
||||
|
||||
#endif
|
||||
29
src/cpp/ripple/NicknameState.cpp
Normal file
29
src/cpp/ripple/NicknameState.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "NicknameState.h"
|
||||
|
||||
NicknameState::NicknameState(SerializedLedgerEntry::pointer ledgerEntry) :
|
||||
mLedgerEntry(ledgerEntry)
|
||||
{
|
||||
if (!mLedgerEntry || mLedgerEntry->getType() != ltNICKNAME) return;
|
||||
}
|
||||
|
||||
bool NicknameState::haveMinimumOffer() const
|
||||
{
|
||||
return mLedgerEntry->isFieldPresent(sfMinimumOffer);
|
||||
}
|
||||
|
||||
STAmount NicknameState::getMinimumOffer() const
|
||||
{
|
||||
return mLedgerEntry->isFieldPresent(sfMinimumOffer)
|
||||
? mLedgerEntry->getFieldAmount(sfMinimumOffer)
|
||||
: STAmount();
|
||||
}
|
||||
|
||||
RippleAddress NicknameState::getAccountID() const
|
||||
{
|
||||
return mLedgerEntry->getFieldAccount(sfAccount);
|
||||
}
|
||||
|
||||
void NicknameState::addJson(Json::Value& val)
|
||||
{
|
||||
val = mLedgerEntry->getJson(0);
|
||||
}
|
||||
37
src/cpp/ripple/NicknameState.h
Normal file
37
src/cpp/ripple/NicknameState.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifndef _NICKNAMESTATE_
|
||||
#define _NICKNAMESTATE_
|
||||
|
||||
//
|
||||
// State of a nickname node.
|
||||
// - Isolate ledger entry format.
|
||||
//
|
||||
|
||||
#include "SerializedLedger.h"
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
class NicknameState
|
||||
{
|
||||
public:
|
||||
typedef boost::shared_ptr<NicknameState> pointer;
|
||||
|
||||
private:
|
||||
SerializedLedgerEntry::pointer mLedgerEntry;
|
||||
|
||||
public:
|
||||
NicknameState(SerializedLedgerEntry::pointer ledgerEntry); // For accounts in a ledger
|
||||
|
||||
bool haveMinimumOffer() const;
|
||||
STAmount getMinimumOffer() const;
|
||||
RippleAddress getAccountID() const;
|
||||
|
||||
SerializedLedgerEntry::pointer getSLE() { return mLedgerEntry; }
|
||||
const SerializedLedgerEntry& peekSLE() const { return *mLedgerEntry; }
|
||||
SerializedLedgerEntry& peekSLE() { return *mLedgerEntry; }
|
||||
|
||||
std::vector<unsigned char> getRaw() const;
|
||||
void addJson(Json::Value& value);
|
||||
};
|
||||
|
||||
#endif
|
||||
// vim:ts=4
|
||||
17
src/cpp/ripple/Operation.cpp
Normal file
17
src/cpp/ripple/Operation.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#include "Operation.h"
|
||||
#include "Config.h"
|
||||
|
||||
/*
|
||||
We also need to charge for each op
|
||||
|
||||
*/
|
||||
|
||||
namespace Script {
|
||||
|
||||
|
||||
int Operation::getFee()
|
||||
{
|
||||
return(theConfig.FEE_CONTRACT_OPERATION);
|
||||
}
|
||||
|
||||
}
|
||||
318
src/cpp/ripple/Operation.h
Normal file
318
src/cpp/ripple/Operation.h
Normal file
@@ -0,0 +1,318 @@
|
||||
#include "Interpreter.h"
|
||||
|
||||
namespace Script {
|
||||
|
||||
// Contracts are non typed have variable data types
|
||||
|
||||
|
||||
class Operation
|
||||
{
|
||||
public:
|
||||
// returns false if there was an error
|
||||
virtual bool work(Interpreter* interpreter)=0;
|
||||
|
||||
virtual int getFee();
|
||||
|
||||
virtual ~Operation() { ; }
|
||||
};
|
||||
|
||||
// this is just an Int in the code
|
||||
class IntOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work(Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer data=interpreter->getIntData();
|
||||
if(data->isInt32())
|
||||
{
|
||||
interpreter->pushStack( data );
|
||||
return(true);
|
||||
}
|
||||
return(false);
|
||||
}
|
||||
};
|
||||
|
||||
class FloatOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work(Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer data=interpreter->getFloatData();
|
||||
if(data->isFloat())
|
||||
{
|
||||
interpreter->pushStack( data );
|
||||
return(true);
|
||||
}
|
||||
return(false);
|
||||
}
|
||||
};
|
||||
|
||||
class Uint160Op : public Operation
|
||||
{
|
||||
public:
|
||||
bool work(Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer data=interpreter->getUint160Data();
|
||||
if(data->isUint160())
|
||||
{
|
||||
interpreter->pushStack( data );
|
||||
return(true);
|
||||
}
|
||||
return(false);
|
||||
}
|
||||
};
|
||||
|
||||
class AddOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work(Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer data1=interpreter->popStack();
|
||||
Data::pointer data2=interpreter->popStack();
|
||||
if( (data1->isInt32() || data1->isFloat()) &&
|
||||
(data2->isInt32() || data2->isFloat()) )
|
||||
{
|
||||
if(data1->isFloat() || data2->isFloat()) interpreter->pushStack(Data::pointer(new FloatData(data1->getFloat()+data2->getFloat())));
|
||||
else interpreter->pushStack(Data::pointer(new IntData(data1->getInt()+data2->getInt())));
|
||||
return(true);
|
||||
}else
|
||||
{
|
||||
return(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SubOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work(Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer data1=interpreter->popStack();
|
||||
Data::pointer data2=interpreter->popStack();
|
||||
if( (data1->isInt32() || data1->isFloat()) &&
|
||||
(data2->isInt32() || data2->isFloat()) )
|
||||
{
|
||||
if(data1->isFloat() || data2->isFloat()) interpreter->pushStack(Data::pointer(new FloatData(data1->getFloat()-data2->getFloat())));
|
||||
else interpreter->pushStack(Data::pointer(new IntData(data1->getInt()-data2->getInt())));
|
||||
return(true);
|
||||
}else
|
||||
{
|
||||
return(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class MulOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work(Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer data1=interpreter->popStack();
|
||||
Data::pointer data2=interpreter->popStack();
|
||||
if( (data1->isInt32() || data1->isFloat()) &&
|
||||
(data2->isInt32() || data2->isFloat()) )
|
||||
{
|
||||
if(data1->isFloat() || data2->isFloat()) interpreter->pushStack(Data::pointer(new FloatData(data1->getFloat()*data2->getFloat())));
|
||||
else interpreter->pushStack(Data::pointer(new IntData(data1->getInt()*data2->getInt())));
|
||||
return(true);
|
||||
}else
|
||||
{
|
||||
return(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class DivOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work(Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer data1=interpreter->popStack();
|
||||
Data::pointer data2=interpreter->popStack();
|
||||
if( (data1->isInt32() || data1->isFloat()) &&
|
||||
(data2->isInt32() || data2->isFloat()) )
|
||||
{
|
||||
if(data1->isFloat() || data2->isFloat()) interpreter->pushStack(Data::pointer(new FloatData(data1->getFloat()/data2->getFloat())));
|
||||
else interpreter->pushStack(Data::pointer(new IntData(data1->getInt()/data2->getInt())));
|
||||
return(true);
|
||||
}else
|
||||
{
|
||||
return(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class GtrOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work(Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer data1=interpreter->popStack();
|
||||
Data::pointer data2=interpreter->popStack();
|
||||
if( (data1->isInt32() || data1->isFloat()) &&
|
||||
(data2->isInt32() || data2->isFloat()) )
|
||||
{
|
||||
interpreter->pushStack(Data::pointer(new BoolData(data1->getFloat()>data2->getFloat())));
|
||||
return(true);
|
||||
}else
|
||||
{
|
||||
return(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class LessOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work(Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer data1=interpreter->popStack();
|
||||
Data::pointer data2=interpreter->popStack();
|
||||
if( (data1->isInt32() || data1->isFloat()) &&
|
||||
(data2->isInt32() || data2->isFloat()) )
|
||||
{
|
||||
interpreter->pushStack(Data::pointer(new FloatData(data1->getFloat()<data2->getFloat())));
|
||||
return(true);
|
||||
}else
|
||||
{
|
||||
return(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class ModOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work(Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer data1=interpreter->popStack();
|
||||
Data::pointer data2=interpreter->popStack();
|
||||
if( data1->isInt32() && data2->isInt32() )
|
||||
{
|
||||
interpreter->pushStack(Data::pointer(new IntData(data1->getInt()%data2->getInt())));
|
||||
return(true);
|
||||
}else
|
||||
{
|
||||
return(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class StartBlockOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work(Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer offset=interpreter->getIntData();
|
||||
return(interpreter->startBlock(offset->getInt()));
|
||||
}
|
||||
};
|
||||
|
||||
class EndBlockOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work(Interpreter* interpreter)
|
||||
{
|
||||
return(interpreter->endBlock());
|
||||
}
|
||||
};
|
||||
|
||||
class StopOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work(Interpreter* interpreter)
|
||||
{
|
||||
interpreter->stop();
|
||||
return(true);
|
||||
}
|
||||
};
|
||||
|
||||
class AcceptDataOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work(Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer data=interpreter->popStack();
|
||||
if(data->isInt32())
|
||||
{
|
||||
interpreter->pushStack( interpreter->getAcceptData(data->getInt()) );
|
||||
return(true);
|
||||
}
|
||||
return(false);
|
||||
}
|
||||
};
|
||||
|
||||
class JumpIfOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work(Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer offset=interpreter->getIntData();
|
||||
Data::pointer cond=interpreter->popStack();
|
||||
if(cond->isBool() && offset->isInt32())
|
||||
{
|
||||
if(cond->isTrue())
|
||||
{
|
||||
return(interpreter->jumpTo(offset->getInt()));
|
||||
}
|
||||
return(true);
|
||||
}
|
||||
return(false);
|
||||
}
|
||||
};
|
||||
|
||||
class JumpOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work(Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer offset=interpreter->getIntData();
|
||||
if(offset->isInt32())
|
||||
{
|
||||
return(interpreter->jumpTo(offset->getInt()));
|
||||
}
|
||||
return(false);
|
||||
}
|
||||
};
|
||||
|
||||
class SendXRPOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work(Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer sourceID=interpreter->popStack();
|
||||
Data::pointer destID=interpreter->popStack();
|
||||
Data::pointer amount=interpreter->popStack();
|
||||
if(sourceID->isUint160() && destID->isUint160() && amount->isInt32() && interpreter->canSign(sourceID->getUint160()))
|
||||
{
|
||||
// make sure:
|
||||
// source is either, this contract, issuer, or acceptor
|
||||
|
||||
// TODO do the send
|
||||
//interpreter->pushStack( send result);
|
||||
|
||||
return(true);
|
||||
}
|
||||
|
||||
return(false);
|
||||
}
|
||||
};
|
||||
|
||||
class GetDataOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work(Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer index=interpreter->popStack();
|
||||
if(index->isInt32())
|
||||
{
|
||||
interpreter->pushStack( interpreter->getContractData(index->getInt()));
|
||||
return(true);
|
||||
}
|
||||
|
||||
return(false);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
23
src/cpp/ripple/OrderBook.cpp
Normal file
23
src/cpp/ripple/OrderBook.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include "OrderBook.h"
|
||||
#include "Ledger.h"
|
||||
|
||||
OrderBook::pointer OrderBook::newOrderBook(SerializedLedgerEntry::pointer ledgerEntry)
|
||||
{
|
||||
if(ledgerEntry->getType() != ltOFFER) return( OrderBook::pointer());
|
||||
|
||||
return( OrderBook::pointer(new OrderBook(ledgerEntry)));
|
||||
}
|
||||
|
||||
OrderBook::OrderBook(SerializedLedgerEntry::pointer ledgerEntry)
|
||||
{
|
||||
const STAmount saTakerGets = ledgerEntry->getFieldAmount(sfTakerGets);
|
||||
const STAmount saTakerPays = ledgerEntry->getFieldAmount(sfTakerPays);
|
||||
|
||||
mCurrencyIn = saTakerGets.getCurrency();
|
||||
mCurrencyOut = saTakerPays.getCurrency();
|
||||
mIssuerIn = saTakerGets.getIssuer();
|
||||
mIssuerOut = saTakerPays.getIssuer();
|
||||
|
||||
mBookBase=Ledger::getBookBase(mCurrencyOut,mIssuerOut,mCurrencyIn,mIssuerIn);
|
||||
}
|
||||
// vim:ts=4
|
||||
34
src/cpp/ripple/OrderBook.h
Normal file
34
src/cpp/ripple/OrderBook.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#include "SerializedLedger.h"
|
||||
#include <boost/shared_ptr.hpp>
|
||||
/*
|
||||
Encapsulates the SLE for an orderbook
|
||||
*/
|
||||
class OrderBook
|
||||
{
|
||||
uint256 mBookBase;
|
||||
|
||||
uint160 mCurrencyIn;
|
||||
uint160 mCurrencyOut;
|
||||
uint160 mIssuerIn;
|
||||
uint160 mIssuerOut;
|
||||
|
||||
//SerializedLedgerEntry::pointer mLedgerEntry;
|
||||
OrderBook(SerializedLedgerEntry::pointer ledgerEntry); // For accounts in a ledger
|
||||
public:
|
||||
typedef boost::shared_ptr<OrderBook> pointer;
|
||||
|
||||
// returns NULL if ledgerEntry doesn't point to an order
|
||||
// if ledgerEntry is an Order it creates the OrderBook this order would live in
|
||||
static OrderBook::pointer newOrderBook(SerializedLedgerEntry::pointer ledgerEntry);
|
||||
|
||||
uint256& getBookBase(){ return(mBookBase); }
|
||||
uint160& getCurrencyIn(){ return(mCurrencyIn); }
|
||||
uint160& getCurrencyOut(){ return(mCurrencyOut); }
|
||||
uint160& getIssuerIn(){ return(mIssuerIn); }
|
||||
uint160& getIssuerOut(){ return(mIssuerOut); }
|
||||
|
||||
// looks through the best offers to see how much it would cost to take the given amount
|
||||
STAmount& getTakePrice(STAmount& takeAmount);
|
||||
|
||||
|
||||
};
|
||||
56
src/cpp/ripple/OrderBookDB.cpp
Normal file
56
src/cpp/ripple/OrderBookDB.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#include "OrderBookDB.h"
|
||||
#include "Log.h"
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
|
||||
// TODO: this would be way faster if we could just look under the order dirs
|
||||
OrderBookDB::OrderBookDB(Ledger::pointer ledger)
|
||||
{
|
||||
// walk through the entire ledger looking for orderbook entries
|
||||
uint256 currentIndex=ledger->getFirstLedgerIndex();
|
||||
while(currentIndex.isNonZero())
|
||||
{
|
||||
SLE::pointer entry=ledger->getSLE(currentIndex);
|
||||
|
||||
OrderBook::pointer book=OrderBook::newOrderBook(entry);
|
||||
if(book)
|
||||
{
|
||||
if( mKnownMap.find(book->getBookBase()) != mKnownMap.end() )
|
||||
{
|
||||
mKnownMap[book->getBookBase()]=true;
|
||||
|
||||
if(!book->getCurrencyIn())
|
||||
{ // XRP
|
||||
mXRPOrders.push_back(book);
|
||||
}else
|
||||
{
|
||||
mIssuerMap[book->getIssuerIn()].push_back(book);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentIndex=ledger->getNextLedgerIndex(currentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// return list of all orderbooks that want IssuerID
|
||||
std::vector<OrderBook::pointer>& OrderBookDB::getBooks(const uint160& issuerID)
|
||||
{
|
||||
if( mIssuerMap.find(issuerID) == mIssuerMap.end() ) return mEmptyVector;
|
||||
else return( mIssuerMap[issuerID]);
|
||||
}
|
||||
|
||||
// return list of all orderbooks that want this issuerID and currencyID
|
||||
void OrderBookDB::getBooks(const uint160& issuerID, const uint160& currencyID, std::vector<OrderBook::pointer>& bookRet)
|
||||
{
|
||||
if( mIssuerMap.find(issuerID) == mIssuerMap.end() )
|
||||
{
|
||||
BOOST_FOREACH(OrderBook::pointer book, mIssuerMap[issuerID])
|
||||
{
|
||||
if(book->getCurrencyIn()==currencyID)
|
||||
{
|
||||
bookRet.push_back(book);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/cpp/ripple/OrderBookDB.h
Normal file
30
src/cpp/ripple/OrderBookDB.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "Ledger.h"
|
||||
#include "OrderBook.h"
|
||||
|
||||
/*
|
||||
we can eventually make this cached and just update it as transactions come in.
|
||||
But for now it is probably faster to just generate it each time
|
||||
*/
|
||||
|
||||
class OrderBookDB
|
||||
{
|
||||
std::vector<OrderBook::pointer> mEmptyVector;
|
||||
std::vector<OrderBook::pointer> mXRPOrders;
|
||||
std::map<uint160, std::vector<OrderBook::pointer> > mIssuerMap;
|
||||
|
||||
std::map<uint256, bool > mKnownMap;
|
||||
|
||||
public:
|
||||
OrderBookDB(Ledger::pointer ledger);
|
||||
|
||||
// return list of all orderbooks that want XRP
|
||||
std::vector<OrderBook::pointer>& getXRPInBooks(){ return mXRPOrders; }
|
||||
// return list of all orderbooks that want IssuerID
|
||||
std::vector<OrderBook::pointer>& getBooks(const uint160& issuerID);
|
||||
// return list of all orderbooks that want this issuerID and currencyID
|
||||
void getBooks(const uint160& issuerID, const uint160& currencyID, std::vector<OrderBook::pointer>& bookRet);
|
||||
|
||||
// returns the best rate we can find
|
||||
float getPrice(uint160& currencyIn,uint160& currencyOut);
|
||||
|
||||
};
|
||||
53
src/cpp/ripple/PackedMessage.cpp
Normal file
53
src/cpp/ripple/PackedMessage.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include "PackedMessage.h"
|
||||
|
||||
|
||||
void PackedMessage::encodeHeader(unsigned size, int type)
|
||||
{
|
||||
assert(mBuffer.size() >= HEADER_SIZE);
|
||||
mBuffer[0] = static_cast<boost::uint8_t>((size >> 24) & 0xFF);
|
||||
mBuffer[1] = static_cast<boost::uint8_t>((size >> 16) & 0xFF);
|
||||
mBuffer[2] = static_cast<boost::uint8_t>((size >> 8) & 0xFF);
|
||||
mBuffer[3] = static_cast<boost::uint8_t>(size & 0xFF);
|
||||
mBuffer[4] = static_cast<boost::uint8_t>((type >> 8) & 0xFF);
|
||||
mBuffer[5] = static_cast<boost::uint8_t>(type & 0xFF);
|
||||
}
|
||||
|
||||
PackedMessage::PackedMessage(const ::google::protobuf::Message &message, int type)
|
||||
{
|
||||
unsigned msg_size = message.ByteSize();
|
||||
assert(msg_size);
|
||||
mBuffer.resize(HEADER_SIZE + msg_size);
|
||||
encodeHeader(msg_size, type);
|
||||
if (msg_size)
|
||||
{
|
||||
message.SerializeToArray(&mBuffer[HEADER_SIZE], msg_size);
|
||||
#ifdef DEBUG
|
||||
// std::cerr << "PackedMessage: type=" << type << ", datalen=" << msg_size << std::endl;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
bool PackedMessage::operator == (const PackedMessage& other)
|
||||
{
|
||||
return (mBuffer == other.mBuffer);
|
||||
}
|
||||
|
||||
unsigned PackedMessage::getLength(std::vector<uint8_t>& buf)
|
||||
{
|
||||
if(buf.size() < HEADER_SIZE)
|
||||
return 0;
|
||||
|
||||
int ret = buf[0];
|
||||
ret <<= 8; ret |= buf[1]; ret <<= 8; ret |= buf[2]; ret <<= 8; ret |= buf[3];
|
||||
return ret;
|
||||
}
|
||||
|
||||
int PackedMessage::getType(std::vector<uint8_t>& buf)
|
||||
{
|
||||
if(buf.size() < HEADER_SIZE)
|
||||
return 0;
|
||||
|
||||
int ret = buf[4];
|
||||
ret <<= 8; ret |= buf[5];
|
||||
return ret;
|
||||
}
|
||||
76
src/cpp/ripple/PackedMessage.h
Normal file
76
src/cpp/ripple/PackedMessage.h
Normal file
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// packaging of messages into length/type-prepended buffers
|
||||
// ready for transmission.
|
||||
|
||||
#ifndef PACKEDMESSAGE_H
|
||||
#define PACKEDMESSAGE_H
|
||||
|
||||
#include <string>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <cstdio>
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/enable_shared_from_this.hpp>
|
||||
#include <boost/cstdint.hpp>
|
||||
|
||||
#include "ripple.pb.h"
|
||||
|
||||
// The header size for packed messages
|
||||
// len(4)+type(2)
|
||||
const unsigned HEADER_SIZE = 6;
|
||||
|
||||
|
||||
// PackedMessage implements simple "packing" of protocol buffers Messages into
|
||||
// a string prepended by a header specifying the message length.
|
||||
// MessageType should be a Message class generated by the protobuf compiler.
|
||||
//
|
||||
|
||||
class PackedMessage : public boost::enable_shared_from_this<PackedMessage>
|
||||
{
|
||||
|
||||
std::vector<uint8_t> mBuffer;
|
||||
|
||||
// Encodes the size and type into a header at the beginning of buf
|
||||
//
|
||||
void encodeHeader(unsigned size, int type);
|
||||
|
||||
public:
|
||||
typedef boost::shared_ptr< ::google::protobuf::Message > MessagePointer;
|
||||
typedef boost::shared_ptr<PackedMessage> pointer;
|
||||
|
||||
PackedMessage(const ::google::protobuf::Message& message, int type);
|
||||
|
||||
std::vector<uint8_t>& getBuffer() { return(mBuffer); }
|
||||
|
||||
static unsigned getLength(std::vector<uint8_t>& buf);
|
||||
static int getType(std::vector<uint8_t>& buf);
|
||||
|
||||
bool operator == (const PackedMessage& other);
|
||||
|
||||
/*
|
||||
void setMsg(MessagePointer msg, int type);
|
||||
|
||||
MessagePointer getMsg();
|
||||
|
||||
// Pack the message into the given data_buffer. The buffer is resized to
|
||||
// exactly fit the message.
|
||||
// Return false in case of an error, true if successful.
|
||||
//
|
||||
bool pack(data_buffer& buf) const;
|
||||
|
||||
// Given a buffer with the first HEADER_SIZE bytes representing the header,
|
||||
// decode the header and return the message length. Return 0 in case of
|
||||
// an error.
|
||||
//
|
||||
unsigned decodeHeader(const data_buffer& buf) const;
|
||||
|
||||
// Unpack and store a message from the given packed buffer.
|
||||
// Return true if unpacking successful, false otherwise.
|
||||
//
|
||||
bool unpack(const data_buffer& buf);
|
||||
*/
|
||||
};
|
||||
|
||||
#endif /* PACKEDMESSAGE_H */
|
||||
|
||||
120
src/cpp/ripple/ParseSection.cpp
Normal file
120
src/cpp/ripple/ParseSection.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
#include "ParseSection.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#define SECTION_DEFAULT_NAME ""
|
||||
|
||||
section ParseSection(const std::string& strInput, const bool bTrim)
|
||||
{
|
||||
std::string strData(strInput);
|
||||
std::vector<std::string> vLines;
|
||||
section secResult;
|
||||
|
||||
// Convert DOS format to unix.
|
||||
boost::algorithm::replace_all(strData, "\r\n", "\n");
|
||||
|
||||
// Convert MacOS format to unix.
|
||||
boost::algorithm::replace_all(strData, "\r", "\n");
|
||||
|
||||
boost::algorithm::split(vLines, strData, boost::algorithm::is_any_of("\n"));
|
||||
|
||||
// Set the default section name.
|
||||
std::string strSection = SECTION_DEFAULT_NAME;
|
||||
|
||||
// Initialize the default section.
|
||||
secResult[strSection] = section::mapped_type();
|
||||
|
||||
// Parse each line.
|
||||
BOOST_FOREACH(std::string& strValue, vLines)
|
||||
{
|
||||
if (strValue.empty() || strValue[0] == '#')
|
||||
{
|
||||
// Blank line or comment, do nothing.
|
||||
|
||||
nothing();
|
||||
}
|
||||
else if (strValue[0] == '[' && strValue[strValue.length()-1] == ']') {
|
||||
// New section.
|
||||
|
||||
strSection = strValue.substr(1, strValue.length()-2);
|
||||
secResult[strSection] = section::mapped_type();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Another line for section.
|
||||
if (bTrim)
|
||||
boost::algorithm::trim(strValue);
|
||||
|
||||
if (!strValue.empty())
|
||||
secResult[strSection].push_back(strValue);
|
||||
}
|
||||
}
|
||||
|
||||
return secResult;
|
||||
}
|
||||
|
||||
void sectionEntriesPrint(std::vector<std::string>* vspEntries, const std::string& strSection)
|
||||
{
|
||||
std::cerr << "[" << strSection << "]" << std::endl;
|
||||
|
||||
if (vspEntries)
|
||||
{
|
||||
BOOST_FOREACH(std::string& strValue, *vspEntries)
|
||||
{
|
||||
std::cerr << strValue << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sectionPrint(section secInput)
|
||||
{
|
||||
BOOST_FOREACH(section::value_type& pairSection, secInput)
|
||||
{
|
||||
sectionEntriesPrint(&pairSection.second, pairSection.first);
|
||||
}
|
||||
}
|
||||
|
||||
section::mapped_type* sectionEntries(section& secSource, const std::string& strSection)
|
||||
{
|
||||
section::iterator it;
|
||||
section::mapped_type* smtResult;
|
||||
|
||||
it = secSource.find(strSection);
|
||||
if (it == secSource.end())
|
||||
{
|
||||
smtResult = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
//section::mapped_type& vecEntries = it->second;
|
||||
|
||||
smtResult = &(it->second);
|
||||
}
|
||||
|
||||
return smtResult;
|
||||
}
|
||||
|
||||
int sectionCount(section& secSource, const std::string& strSection)
|
||||
{
|
||||
section::mapped_type* pmtEntries = sectionEntries(secSource, strSection);
|
||||
|
||||
return pmtEntries ? -1 : pmtEntries->size();
|
||||
}
|
||||
|
||||
bool sectionSingleB(section& secSource, const std::string& strSection, std::string& strValue)
|
||||
{
|
||||
section::mapped_type* pmtEntries = sectionEntries(secSource, strSection);
|
||||
bool bSingle = pmtEntries && 1 == pmtEntries->size();
|
||||
|
||||
if (bSingle)
|
||||
{
|
||||
strValue = (*pmtEntries)[0];
|
||||
}
|
||||
|
||||
return bSingle;
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
17
src/cpp/ripple/ParseSection.h
Normal file
17
src/cpp/ripple/ParseSection.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef _PARSE_SECTION_
|
||||
#define _PARSE_SECTION_
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
typedef std::map<const std::string, std::vector<std::string> > section;
|
||||
|
||||
section ParseSection(const std::string& strInput, const bool bTrim);
|
||||
void sectionPrint(section secInput);
|
||||
void sectionEntriesPrint(std::vector<std::string>* vspEntries, const std::string& strSection);
|
||||
bool sectionSingleB(section& secSource, const std::string& strSection, std::string& strValue);
|
||||
int sectionCount(section& secSource, const std::string& strSection);
|
||||
section::mapped_type* sectionEntries(section& secSource, const std::string& strSection);
|
||||
|
||||
#endif
|
||||
267
src/cpp/ripple/Pathfinder.cpp
Normal file
267
src/cpp/ripple/Pathfinder.cpp
Normal file
@@ -0,0 +1,267 @@
|
||||
#include "Pathfinder.h"
|
||||
#include "Application.h"
|
||||
#include "RippleLines.h"
|
||||
#include "Log.h"
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
/*
|
||||
JED: V IIII
|
||||
|
||||
we just need to find a succession of the highest quality paths there until we find enough width
|
||||
|
||||
Don't do branching within each path
|
||||
|
||||
We have a list of paths we are working on but how do we compare the ones that are terminating in a different currency?
|
||||
|
||||
Loops
|
||||
|
||||
TODO: what is a good way to come up with multiple paths?
|
||||
Maybe just change the sort criteria?
|
||||
first a low cost one and then a fat short one?
|
||||
|
||||
|
||||
OrderDB:
|
||||
getXRPOffers();
|
||||
|
||||
// return list of all orderbooks that want XRP
|
||||
// return list of all orderbooks that want IssuerID
|
||||
// return list of all orderbooks that want this issuerID and currencyID
|
||||
*/
|
||||
|
||||
/*
|
||||
Test sending to XRP
|
||||
Test XRP to XRP
|
||||
Test offer in middle
|
||||
Test XRP to USD
|
||||
Test USD to EUR
|
||||
*/
|
||||
|
||||
|
||||
// we sort the options by:
|
||||
// cost of path
|
||||
// length of path
|
||||
// width of path
|
||||
// correct currency at the end
|
||||
|
||||
|
||||
|
||||
bool sortPathOptions(PathOption::pointer first, PathOption::pointer second)
|
||||
{
|
||||
if(first->mTotalCost<second->mTotalCost) return(true);
|
||||
if(first->mTotalCost>second->mTotalCost) return(false);
|
||||
|
||||
if(first->mCorrectCurrency && !second->mCorrectCurrency) return(true);
|
||||
if(!first->mCorrectCurrency && second->mCorrectCurrency) return(false);
|
||||
|
||||
if(first->mPath.getElementCount()<second->mPath.getElementCount()) return(true);
|
||||
if(first->mPath.getElementCount()>second->mPath.getElementCount()) return(false);
|
||||
|
||||
if(first->mMinWidth<second->mMinWidth) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
PathOption::PathOption(uint160& srcAccount,uint160& srcCurrencyID,const uint160& dstCurrencyID)
|
||||
{
|
||||
mCurrentAccount=srcAccount;
|
||||
mCurrencyID=srcCurrencyID;
|
||||
mCorrectCurrency=(srcCurrencyID==dstCurrencyID);
|
||||
mQuality=0;
|
||||
mMinWidth=STAmount(dstCurrencyID,99999,80); // this will get lowered when we convert back to the correct currency
|
||||
}
|
||||
|
||||
PathOption::PathOption(PathOption::pointer other)
|
||||
{
|
||||
// TODO:
|
||||
}
|
||||
|
||||
|
||||
Pathfinder::Pathfinder(RippleAddress& srcAccountID, RippleAddress& dstAccountID, uint160& srcCurrencyID, STAmount dstAmount) :
|
||||
mSrcAccountID(srcAccountID.getAccountID()), mDstAccountID(dstAccountID.getAccountID()), mDstAmount(dstAmount), mSrcCurrencyID(srcCurrencyID), mOrderBook(theApp->getMasterLedger().getCurrentLedger())
|
||||
{
|
||||
mLedger=theApp->getMasterLedger().getCurrentLedger();
|
||||
}
|
||||
|
||||
bool Pathfinder::findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet)
|
||||
{
|
||||
if(mLedger) {
|
||||
std::queue<STPath> pqueue;
|
||||
STPathElement ele(mSrcAccountID,
|
||||
mSrcCurrencyID,
|
||||
uint160());
|
||||
STPath path;
|
||||
path.addElement(ele);
|
||||
pqueue.push(path);
|
||||
while(pqueue.size()) {
|
||||
|
||||
STPath path = pqueue.front();
|
||||
pqueue.pop();
|
||||
// get the first path from the queue
|
||||
|
||||
ele = path.mPath.back();
|
||||
// get the last node from the path
|
||||
|
||||
if (ele.mAccountID == mDstAccountID) {
|
||||
path.mPath.erase(path.mPath.begin());
|
||||
path.mPath.erase(path.mPath.begin() + path.mPath.size()-1);
|
||||
if (path.mPath.size() == 0) {
|
||||
continue;
|
||||
}
|
||||
retPathSet.addPath(path);
|
||||
return true;
|
||||
}
|
||||
// found the destination
|
||||
|
||||
if (!ele.mCurrencyID) {
|
||||
BOOST_FOREACH(OrderBook::pointer book,mOrderBook.getXRPInBooks())
|
||||
{
|
||||
//if (!path.hasSeen(line->getAccountIDPeer().getAccountID()))
|
||||
{
|
||||
|
||||
STPath new_path(path);
|
||||
STPathElement new_ele(uint160(), book->getCurrencyOut(), book->getIssuerOut());
|
||||
new_path.mPath.push_back(new_ele);
|
||||
new_path.mCurrencyID = book->getCurrencyOut();
|
||||
new_path.mCurrentAccount = book->getCurrencyOut();
|
||||
|
||||
pqueue.push(new_path);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
RippleLines rippleLines(ele.mAccountID);
|
||||
BOOST_FOREACH(RippleState::pointer line,rippleLines.getLines())
|
||||
{
|
||||
if (!path.hasSeen(line->getAccountIDPeer().getAccountID()))
|
||||
{
|
||||
STPath new_path(path);
|
||||
STPathElement new_ele(line->getAccountIDPeer().getAccountID(),
|
||||
ele.mCurrencyID,
|
||||
uint160());
|
||||
|
||||
new_path.mPath.push_back(new_ele);
|
||||
pqueue.push(new_path);
|
||||
}
|
||||
} // BOOST_FOREACHE
|
||||
|
||||
// every offer that wants the source currency
|
||||
std::vector<OrderBook::pointer> books;
|
||||
mOrderBook.getBooks(path.mCurrentAccount, path.mCurrencyID, books);
|
||||
|
||||
BOOST_FOREACH(OrderBook::pointer book,books)
|
||||
{
|
||||
STPath new_path(path);
|
||||
STPathElement new_ele(uint160(), book->getCurrencyOut(), book->getIssuerOut());
|
||||
|
||||
new_path.mPath.push_back(new_ele);
|
||||
new_path.mCurrentAccount=book->getIssuerOut();
|
||||
new_path.mCurrencyID=book->getCurrencyOut();
|
||||
|
||||
pqueue.push(new_path);
|
||||
|
||||
}
|
||||
|
||||
} // else
|
||||
// enumerate all adjacent nodes, construct a new path and push it into the queue
|
||||
} // While
|
||||
} // if there is a ledger
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Pathfinder::checkComplete(STPathSet& retPathSet)
|
||||
{
|
||||
if(mCompletePaths.size())
|
||||
{ // TODO: look through these and pick the most promising
|
||||
int count=0;
|
||||
BOOST_FOREACH(PathOption::pointer pathOption,mCompletePaths)
|
||||
{
|
||||
retPathSet.addPath(pathOption->mPath);
|
||||
count++;
|
||||
if(count>2) return(true);
|
||||
}
|
||||
return(true);
|
||||
}
|
||||
return(false);
|
||||
}
|
||||
|
||||
|
||||
// get all the options from this accountID
|
||||
// if source is XRP
|
||||
// every offer that wants XRP
|
||||
// else
|
||||
// every ripple line that starts with the source currency
|
||||
// every offer that we can take that wants the source currency
|
||||
|
||||
void Pathfinder::addOptions(PathOption::pointer tail)
|
||||
{
|
||||
if(!tail->mCurrencyID)
|
||||
{ // source XRP
|
||||
BOOST_FOREACH(OrderBook::pointer book,mOrderBook.getXRPInBooks())
|
||||
{
|
||||
PathOption::pointer pathOption(new PathOption(tail));
|
||||
|
||||
STPathElement ele(uint160(), book->getCurrencyOut(), book->getIssuerOut());
|
||||
pathOption->mPath.addElement(ele);
|
||||
|
||||
pathOption->mCurrentAccount=book->getIssuerOut();
|
||||
pathOption->mCurrencyID=book->getCurrencyOut();
|
||||
addPathOption(pathOption);
|
||||
}
|
||||
}else
|
||||
{ // ripple
|
||||
RippleLines rippleLines(tail->mCurrentAccount);
|
||||
BOOST_FOREACH(RippleState::pointer line,rippleLines.getLines())
|
||||
{
|
||||
// TODO: make sure we can move in the correct direction
|
||||
STAmount balance=line->getBalance();
|
||||
if(balance.getCurrency()==tail->mCurrencyID)
|
||||
{ // we have a ripple line from the tail to somewhere else
|
||||
PathOption::pointer pathOption(new PathOption(tail));
|
||||
|
||||
STPathElement ele(line->getAccountIDPeer().getAccountID(), uint160(), uint160());
|
||||
pathOption->mPath.addElement(ele);
|
||||
|
||||
|
||||
pathOption->mCurrentAccount=line->getAccountIDPeer().getAccountID();
|
||||
addPathOption(pathOption);
|
||||
}
|
||||
}
|
||||
|
||||
// every offer that wants the source currency
|
||||
std::vector<OrderBook::pointer> books;
|
||||
mOrderBook.getBooks(tail->mCurrentAccount, tail->mCurrencyID, books);
|
||||
|
||||
BOOST_FOREACH(OrderBook::pointer book,books)
|
||||
{
|
||||
PathOption::pointer pathOption(new PathOption(tail));
|
||||
|
||||
STPathElement ele(uint160(), book->getCurrencyOut(), book->getIssuerOut());
|
||||
pathOption->mPath.addElement(ele);
|
||||
|
||||
pathOption->mCurrentAccount=book->getIssuerOut();
|
||||
pathOption->mCurrencyID=book->getCurrencyOut();
|
||||
addPathOption(pathOption);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Pathfinder::addPathOption(PathOption::pointer pathOption)
|
||||
{
|
||||
if(pathOption->mCurrencyID==mDstAmount.getCurrency())
|
||||
{
|
||||
pathOption->mCorrectCurrency=true;
|
||||
|
||||
if(pathOption->mCurrentAccount==mDstAccountID)
|
||||
{ // this path is complete
|
||||
mCompletePaths.push_back(pathOption);
|
||||
}else mBuildingPaths.push_back(pathOption);
|
||||
}
|
||||
else
|
||||
{
|
||||
pathOption->mCorrectCurrency=false;
|
||||
mBuildingPaths.push_back(pathOption);
|
||||
}
|
||||
}
|
||||
// vim:ts=4
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user