From ef5dae9f67a57746f3f6abc6a99927041017ac61 Mon Sep 17 00:00:00 2001 From: jed Date: Fri, 2 Nov 2012 22:25:35 -0700 Subject: [PATCH] . --- newcoin.vcxproj | 2 + newcoin.vcxproj.filters | 12 + src/Application.cpp | 2 +- src/Application.h | 5 +- src/RPCHandler.cpp | 174 ++++++- src/RPCHandler.h | 2 + src/WSConnection.cpp | 734 ++++++++++++++++++++++++++ src/WSConnection.h | 75 +++ src/WSDoor.cpp | 1079 +-------------------------------------- src/WSHandler.h | 124 +++++ 10 files changed, 1126 insertions(+), 1083 deletions(-) create mode 100644 src/WSConnection.cpp create mode 100644 src/WSConnection.h create mode 100644 src/WSHandler.h diff --git a/newcoin.vcxproj b/newcoin.vcxproj index 3109a074aa..8e61792388 100644 --- a/newcoin.vcxproj +++ b/newcoin.vcxproj @@ -171,6 +171,7 @@ + @@ -263,6 +264,7 @@ + diff --git a/newcoin.vcxproj.filters b/newcoin.vcxproj.filters index 8aa2cf9ad0..4c14bf8ec3 100644 --- a/newcoin.vcxproj.filters +++ b/newcoin.vcxproj.filters @@ -309,6 +309,15 @@ Source Files + + Source Files + + + Source Files + + + Source Files + @@ -566,6 +575,9 @@ Header Files + + Header Files + diff --git a/src/Application.cpp b/src/Application.cpp index 37f497827a..aa15bc6539 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -42,7 +42,7 @@ Application::Application() : mNetOps(mIOService, &mMasterLedger), mTempNodeCache("NodeCache", 16384, 90), mHashedObjectStore(16384, 300), mSNTPClient(mAuxService), mRpcDB(NULL), mTxnDB(NULL), mLedgerDB(NULL), mWalletDB(NULL), mHashNodeDB(NULL), mNetNodeDB(NULL), - mConnectionPool(mIOService), mPeerDoor(NULL), mRPCDoor(NULL), mSweepTimer(mAuxService) + mConnectionPool(mIOService), mPeerDoor(NULL), mRPCDoor(NULL), mSweepTimer(mAuxService), mRPCHandler(&mNetOps) { RAND_bytes(mNonce256.begin(), mNonce256.size()); RAND_bytes(reinterpret_cast(&mNonceST), sizeof(mNonceST)); diff --git a/src/Application.h b/src/Application.h index aeca10973f..fca7a53016 100644 --- a/src/Application.h +++ b/src/Application.h @@ -19,7 +19,7 @@ #include "SNTPClient.h" #include "../database/database.h" #include "JobQueue.h" - +#include "RPCHandler.h" class RPCDoor; class PeerDoor; @@ -55,6 +55,7 @@ class Application HashedObjectStore mHashedObjectStore; SNTPClient mSNTPClient; JobQueue mJobQueue; + RPCHandler mRPCHandler; DatabaseCon *mRpcDB, *mTxnDB, *mLedgerDB, *mWalletDB, *mHashNodeDB, *mNetNodeDB; @@ -96,6 +97,8 @@ public: 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); } diff --git a/src/RPCHandler.cpp b/src/RPCHandler.cpp index 6bff3a15cb..b8eb1c28ac 100644 --- a/src/RPCHandler.cpp +++ b/src/RPCHandler.cpp @@ -1612,18 +1612,182 @@ Json::Value RPCHandler::doRippleLinesGet(const Json::Value ¶ms) // submit private_key json Json::Value RPCHandler::doSubmit(const Json::Value& params) { - RippleAddress naSeed; - std::string txJSON= params[1u].asString(); + Json::Value txJSON; + Json::Reader reader; + if(reader.parse(params[1u].asString(),txJSON)) + { + return handleJSONSubmit(params[0u].asString(), txJSON ); + } - if (!naSeed.setSeedGeneric(params[0u].asString())) + return rpcError(rpcSRC_ACT_MALFORMED); +} + +Json::Value RPCHandler::handleJSONSubmit(std::string& key, Json::Value& txJSON) +{ + return rpcError(rpcSRC_ACT_MALFORMED); + /* + Json::Value jvResult; + RippleAddress naSeed; + RippleAddress naAccount; + + if (!naSeed.setSeedGeneric(key)) + { + return rpcError(rpcBAD_SEED); + } + if (!txJSON.isMember("Account")) + { + return rpcError(rpcBAD_SEED); + } + if (!naAccount.setAccountID(txJSON["Account"].asString())) { return rpcError(rpcBAD_SEED); } + + Ledger::pointer lpCurrent = mNetOps->getCurrentLedger(); + SLE::pointer sleAccountRoot = mNetOps->getSLE(lpCurrent, Ledger::getAccountRootIndex(naAccount.getAccountID())); + + if (!sleAccountRoot) + { + // XXX Ignore transactions for accounts not created. + return rpcError(rpcBAD_SEED); + } + + bool bHaveAuthKey = false; + RippleAddress naAuthorizedPublic; - // TODO - return rpcError(rpcSRC_ACT_MALFORMED); + RippleAddress naSecret = RippleAddress::createSeedGeneric(key); + RippleAddress naMasterGenerator = RippleAddress::createGeneratorPublic(naSecret); + + // Find the index of Account from the master generator, so we can generate the public and private keys. + RippleAddress naMasterAccountPublic; + unsigned int iIndex = 0; + bool bFound = false; + + // Don't look at ledger entries to determine if the account exists. Don't want to leak to thin server that these accounts are + // related. + while (!bFound && iIndex != theConfig.ACCOUNT_PROBE_MAX) + { + naMasterAccountPublic.setAccountPublic(naMasterGenerator, iIndex); + + Log(lsWARNING) << "authorize: " << iIndex << " : " << naMasterAccountPublic.humanAccountID() << " : " << naAccount.humanAccountID(); + + bFound = naAccount.getAccountID() == naMasterAccountPublic.getAccountID(); + if (!bFound) + ++iIndex; + } + + if (!bFound) + { + return rpcError(rpcBAD_SEED); + } + + // Use the generator to determine the associated public and private keys. + RippleAddress naGenerator = RippleAddress::createGeneratorPublic(naSecret); + RippleAddress naAccountPublic = RippleAddress::createAccountPublic(naGenerator, iIndex); + RippleAddress naAccountPrivate = RippleAddress::createAccountPrivate(naGenerator, naSecret, iIndex); + + if (bHaveAuthKey + // The generated pair must match authorized... + && naAuthorizedPublic.getAccountID() != naAccountPublic.getAccountID() + // ... or the master key must have been used. + && naAccount.getAccountID() != naAccountPublic.getAccountID()) + { + // std::cerr << "iIndex: " << iIndex << std::endl; + // std::cerr << "sfAuthorizedKey: " << strHex(asSrc->getAuthorizedKey().getAccountID()) << std::endl; + // std::cerr << "naAccountPublic: " << strHex(naAccountPublic.getAccountID()) << std::endl; + + return rpcError(rpcBAD_SEED); + } + + std::auto_ptr sopTrans; + + try + { + sopTrans = STObject::parseJson(jvRequest["transaction"]); + } + catch (std::exception& e) + { + jvResult["error"] = "malformedTransaction"; + jvResult["error_exception"] = e.what(); + return; + } + + sopTrans->setFieldVL(sfSigningPubKey, naAccountPublic.getAccountPublic()); + + SerializedTransaction::pointer stpTrans; + + try + { + stpTrans = boost::make_shared(*sopTrans); + } + catch (std::exception& e) + { + jvResult["error"] = "invalidTransaction"; + jvResult["error_exception"] = e.what(); + return jvResult; + } + + stpTrans->sign(naAccountPrivate); + + Transaction::pointer tpTrans; + + try + { + tpTrans = boost::make_shared(stpTrans, false); + } + catch (std::exception& e) + { + jvResult["error"] = "internalTransaction"; + jvResult["error_exception"] = e.what(); + return(jvResult); + } + + try + { + tpTrans = mNetwork.submitTransaction(tpTrans); + + if (!tpTrans) { + jvResult["error"] = "invalidTransaction"; + jvResult["error_exception"] = "Unable to sterilize transaction."; + return(jvResult); + } + } + catch (std::exception& e) + { + jvResult["error"] = "internalSubmit"; + jvResult["error_exception"] = e.what(); + return(jvResult); + } + + try + { + jvResult["transaction"] = tpTrans->getJson(0); + + if (temUNCERTAIN != tpTrans->getResult()) + { + std::string sToken; + std::string sHuman; + + transResultInfo(tpTrans->getResult(), sToken, sHuman); + + jvResult["engine_result"] = sToken; + jvResult["engine_result_code"] = tpTrans->getResult(); + jvResult["engine_result_message"] = sHuman; + } + return(jvResult); + } + catch (std::exception& e) + { + jvResult["error"] = "internalJson"; + jvResult["error_exception"] = e.what(); + return(jvResult); + } + } + + */ + } // send regular_seed paying_account account_id amount [currency] [issuer] [send_max] [send_currency] [send_issuer] diff --git a/src/RPCHandler.h b/src/RPCHandler.h index c001e7dffa..cd9c45001c 100644 --- a/src/RPCHandler.h +++ b/src/RPCHandler.h @@ -160,6 +160,8 @@ public: Json::Value doCommand(const std::string& command, Json::Value& params,int role); Json::Value rpcError(int iError); + Json::Value handleJSONSubmit(std::string& key, Json::Value& txJSON); + }; #endif diff --git a/src/WSConnection.cpp b/src/WSConnection.cpp new file mode 100644 index 0000000000..769b017e73 --- /dev/null +++ b/src/WSConnection.cpp @@ -0,0 +1,734 @@ +#include "WSConnection.h" +#include "WSHandler.h" + +#include "../json/reader.h" +#include "../json/writer.h" +// +// WSConnection +// + +SETUP_LOG(); + +WSConnection::~WSConnection() +{ + mNetwork.unsubTransaction(this); + mNetwork.unsubLedger(this); + mNetwork.unsubLedgerAccounts(this); + mNetwork.unsubAccountInfo(this, mSubAccountInfo); + mNetwork.unsubAccountTransaction(this, mSubAccountTransaction); +} + +void WSConnection::send(const Json::Value& jvObj) +{ + mHandler->send(mConnection, jvObj); +} + +// +// Utilities +// + +Json::Value WSConnection::invokeCommand(Json::Value& jvRequest) +{ + static struct { + const char* pCommand; + doFuncPtr dfpFunc; + } commandsA[] = { + // Request-Response Commands: + { "ledger_accept", &WSConnection::doLedgerAccept }, + { "ledger_closed", &WSConnection::doLedgerClosed }, + { "ledger_current", &WSConnection::doLedgerCurrent }, + { "ledger_entry", &WSConnection::doLedgerEntry }, + { "submit", &WSConnection::doSubmit }, + { "transaction_entry", &WSConnection::doTransactionEntry }, + { "subscribe", &WSConnection::doSubscribe }, + { "unsubscribe", &WSConnection::doUnsubscribe }, + + // deprecated + { "account_info_subscribe", &WSConnection::doAccountInfoSubscribe }, + { "account_info_unsubscribe", &WSConnection::doAccountInfoUnsubscribe }, + { "account_transaction_subscribe", &WSConnection::doAccountTransactionSubscribe }, + { "account_transaction_unsubscribe", &WSConnection::doAccountTransactionUnsubscribe }, + { "ledger_accounts_subscribe", &WSConnection::doLedgerAccountsSubcribe }, + { "ledger_accounts_unsubscribe", &WSConnection::doLedgerAccountsUnsubscribe }, + { "server_subscribe", &WSConnection::doServerSubscribe }, + { "server_unsubscribe", &WSConnection::doServerUnsubscribe }, + { "transaction_subscribe", &WSConnection::doTransactionSubcribe }, + { "transaction_unsubscribe", &WSConnection::doTransactionUnsubscribe }, + }; + + if (!jvRequest.isMember("command")) + { + Json::Value jvResult(Json::objectValue); + + jvResult["type"] = "response"; + jvResult["result"] = "error"; + jvResult["error"] = "missingCommand"; + jvResult["command"] = jvRequest; + + return jvResult; + } + + std::string strCommand = jvRequest["command"].asString(); + + int i = NUMBER(commandsA); + + while (i-- && strCommand != commandsA[i].pCommand) + ; + + Json::Value jvResult(Json::objectValue); + + jvResult["type"] = "response"; + + if (i < 0) + { + jvResult["error"] = "unknownCommand"; // Unknown command. + } + else + { + (this->*(commandsA[i].dfpFunc))(jvResult, jvRequest); + } + + if (jvRequest.isMember("id")) + { + jvResult["id"] = jvRequest["id"]; + } + + if (jvResult.isMember("error")) + { + jvResult["result"] = "error"; + jvResult["request"] = jvRequest; + } + else + { + jvResult["result"] = "success"; + } + + return jvResult; +} + +boost::unordered_set WSConnection::parseAccountIds(const Json::Value& jvArray) +{ + boost::unordered_set usnaResult; + + for (Json::Value::const_iterator it = jvArray.begin(); it != jvArray.end(); it++) + { + RippleAddress naString; + + if (!(*it).isString() || !naString.setAccountID((*it).asString())) + { + usnaResult.clear(); + break; + } + else + { + (void) usnaResult.insert(naString); + } + } + + return usnaResult; +} + +// +// Commands +// + +/* +server : Sends a message anytime the server status changes such as network connectivity. +ledger : Sends a message at every ledger close. +transactions : Sends a message for every transaction that makes it into a ledger. +rt_transactions +accounts +rt_accounts +*/ +// TODO +void WSConnection::doSubscribe(Json::Value& jvResult, Json::Value& jvRequest) +{ + if (jvRequest.isMember("streams")) + { + for (Json::Value::iterator it = jvRequest["streams"].begin(); it != jvRequest["streams"].end(); it++) + { + if ((*it).isString() ) + { + std::string streamName=(*it).asString(); + + if(streamName=="server") + { + mNetwork.subLedgerAccounts(this); + }else if(streamName=="ledger") + { + mNetwork.subLedgerAccounts(this); + }else if(streamName=="transactions") + { + mNetwork.subTransaction(this); + }else if(streamName=="rt_transactions") + { + mNetwork.subTransaction(this); // TODO + }else + { + jvResult["error"] = str(boost::format("Unknown stream: %s") % streamName); + } + }else + { + jvResult["error"] = "malformedSteam"; + } + } + } + + if (jvRequest.isMember("rt_accounts")) + { + boost::unordered_set usnaAccoundIds = parseAccountIds(jvRequest["rt_accounts"]); + + if (usnaAccoundIds.empty()) + { + jvResult["error"] = "malformedAccount"; + }else + { + boost::mutex::scoped_lock sl(mLock); + + BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds) + { + mSubAccountInfo.insert(naAccountID); + } + + mNetwork.subAccountInfo(this, usnaAccoundIds); + } + } + + if (jvRequest.isMember("accounts")) + { + boost::unordered_set usnaAccoundIds = parseAccountIds(jvRequest["accounts"]); + + if (usnaAccoundIds.empty()) + { + jvResult["error"] = "malformedAccount"; + }else + { + boost::mutex::scoped_lock sl(mLock); + + BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds) + { + mSubAccountInfo.insert(naAccountID); + } + + mNetwork.subAccountInfo(this, usnaAccoundIds); + } + } +} + +void WSConnection::doUnsubscribe(Json::Value& jvResult, Json::Value& jvRequest) +{ + +} + +void WSConnection::doAccountInfoSubscribe(Json::Value& jvResult, Json::Value& jvRequest) +{ + if (!jvRequest.isMember("accounts")) + { + jvResult["error"] = "missingField"; + } + else if (jvRequest["accounts"].empty()) + { + jvResult["error"] = "emptySet"; + } + else + { + boost::unordered_set usnaAccoundIds = parseAccountIds(jvRequest["accounts"]); + + + } +} + +void WSConnection::doAccountInfoUnsubscribe(Json::Value& jvResult, Json::Value& jvRequest) +{ + if (!jvRequest.isMember("accounts")) + { + jvResult["error"] = "missingField"; + } + else if (jvRequest["accounts"].empty()) + { + jvResult["error"] = "emptySet"; + } + else + { + boost::unordered_set usnaAccoundIds = parseAccountIds(jvRequest["accounts"]); + + if (usnaAccoundIds.empty()) + { + jvResult["error"] = "malformedAccount"; + } + else + { + boost::mutex::scoped_lock sl(mLock); + + BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds) + { + mSubAccountInfo.erase(naAccountID); + } + + mNetwork.unsubAccountInfo(this, usnaAccoundIds); + } + } +} + +void WSConnection::doAccountTransactionSubscribe(Json::Value& jvResult, Json::Value& jvRequest) +{ + if (!jvRequest.isMember("accounts")) + { + jvResult["error"] = "missingField"; + } + else if (jvRequest["accounts"].empty()) + { + jvResult["error"] = "emptySet"; + } + else + { + boost::unordered_set usnaAccoundIds = parseAccountIds(jvRequest["accounts"]); + + if (usnaAccoundIds.empty()) + { + jvResult["error"] = "malformedAccount"; + } + else + { + boost::mutex::scoped_lock sl(mLock); + + BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds) + { + mSubAccountTransaction.insert(naAccountID); + } + + mNetwork.subAccountTransaction(this, usnaAccoundIds); + } + } +} + +void WSConnection::doAccountTransactionUnsubscribe(Json::Value& jvResult, Json::Value& jvRequest) +{ + if (!jvRequest.isMember("accounts")) + { + jvResult["error"] = "missingField"; + } + else if (jvRequest["accounts"].empty()) + { + jvResult["error"] = "emptySet"; + } + else + { + boost::unordered_set usnaAccoundIds = parseAccountIds(jvRequest["accounts"]); + + if (usnaAccoundIds.empty()) + { + jvResult["error"] = "malformedAccount"; + } + else + { + boost::mutex::scoped_lock sl(mLock); + + BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds) + { + mSubAccountTransaction.erase(naAccountID); + } + + mNetwork.unsubAccountTransaction(this, usnaAccoundIds); + } + } +} + +void WSConnection::doLedgerAccountsSubcribe(Json::Value& jvResult, Json::Value& jvRequest) +{ + if (!mNetwork.subLedgerAccounts(this)) + { + jvResult["error"] = "ledgerAccountsSubscribed"; + } +} + +void WSConnection::doLedgerAccountsUnsubscribe(Json::Value& jvResult, Json::Value& jvRequest) +{ + if (!mNetwork.unsubLedgerAccounts(this)) + { + jvResult["error"] = "ledgerAccountsNotSubscribed"; + } +} + +void WSConnection::doLedgerAccept(Json::Value& jvResult, Json::Value& jvRequest) +{ + if (!theConfig.RUN_STANDALONE) + { + jvResult["error"] = "notStandAlone"; + } + else + { + mNetwork.acceptLedger(); + + jvResult["ledger_current_index"] = mNetwork.getCurrentLedgerID(); + } +} + +void WSConnection::doLedgerClosed(Json::Value& jvResult, Json::Value& jvRequest) +{ + uint256 uLedger = mNetwork.getClosedLedger(); + + jvResult["ledger_closed_index"] = mNetwork.getLedgerID(uLedger); + jvResult["ledger_closed"] = uLedger.ToString(); +} + +void WSConnection::doLedgerCurrent(Json::Value& jvResult, Json::Value& jvRequest) +{ + jvResult["ledger_current_index"] = mNetwork.getCurrentLedgerID(); +} + +void WSConnection::doLedgerEntry(Json::Value& jvResult, Json::Value& jvRequest) +{ + NetworkOPs& noNetwork = mNetwork; + uint256 uLedger = jvRequest.isMember("ledger_closed") ? uint256(jvRequest["ledger_closed"].asString()) : 0; + uint32 uLedgerIndex = jvRequest.isMember("ledger_index") && jvRequest["ledger_index"].isNumeric() ? jvRequest["ledger_index"].asUInt() : 0; + + Ledger::pointer lpLedger; + + if (!!uLedger) + { + // Ledger directly specified. + lpLedger = noNetwork.getLedgerByHash(uLedger); + + if (!lpLedger) + { + jvResult["error"] = "ledgerNotFound"; + return; + } + + uLedgerIndex = lpLedger->getLedgerSeq(); // Set the current index, override if needed. + } + else if (!!uLedgerIndex) + { + lpLedger = noNetwork.getLedgerBySeq(uLedgerIndex); + + if (!lpLedger) + { + jvResult["error"] = "ledgerNotFound"; // ledger_index from future? + return; + } + } + else + { + // Default to current ledger. + lpLedger = noNetwork.getCurrentLedger(); + uLedgerIndex = lpLedger->getLedgerSeq(); // Set the current index. + } + + if (lpLedger->isClosed()) + { + if (!!uLedger) + jvResult["ledger_closed"] = uLedger.ToString(); + + jvResult["ledger_closed_index"] = uLedgerIndex; + } + else + { + jvResult["ledger_current_index"] = uLedgerIndex; + } + + uint256 uNodeIndex; + bool bNodeBinary = false; + + if (jvRequest.isMember("index")) + { + // XXX Needs to provide proof. + uNodeIndex.SetHex(jvRequest["index"].asString()); + bNodeBinary = true; + } + else if (jvRequest.isMember("account_root")) + { + RippleAddress naAccount; + + if (!naAccount.setAccountID(jvRequest["account_root"].asString()) + || !naAccount.getAccountID()) + { + jvResult["error"] = "malformedAddress"; + } + else + { + uNodeIndex = Ledger::getAccountRootIndex(naAccount.getAccountID()); + } + } + else if (jvRequest.isMember("directory")) + { + + if (!jvRequest.isObject()) + { + uNodeIndex.SetHex(jvRequest["directory"].asString()); + } + else if (jvRequest["directory"].isMember("sub_index") + && !jvRequest["directory"]["sub_index"].isIntegral()) + { + jvResult["error"] = "malformedRequest"; + } + else + { + uint64 uSubIndex = jvRequest["directory"].isMember("sub_index") + ? jvRequest["directory"]["sub_index"].asUInt() + : 0; + + if (jvRequest["directory"].isMember("dir_root")) + { + uint256 uDirRoot; + + uDirRoot.SetHex(jvRequest["dir_root"].asString()); + + uNodeIndex = Ledger::getDirNodeIndex(uDirRoot, uSubIndex); + } + else if (jvRequest["directory"].isMember("owner")) + { + RippleAddress naOwnerID; + + if (!naOwnerID.setAccountID(jvRequest["directory"]["owner"].asString())) + { + jvResult["error"] = "malformedAddress"; + } + else + { + uint256 uDirRoot = Ledger::getOwnerDirIndex(naOwnerID.getAccountID()); + + uNodeIndex = Ledger::getDirNodeIndex(uDirRoot, uSubIndex); + } + } + else + { + jvResult["error"] = "malformedRequest"; + } + } + } + else if (jvRequest.isMember("generator")) + { + RippleAddress naGeneratorID; + + if (!jvRequest.isObject()) + { + uNodeIndex.SetHex(jvRequest["generator"].asString()); + } + else if (!jvRequest["generator"].isMember("regular_seed")) + { + jvResult["error"] = "malformedRequest"; + } + else if (!naGeneratorID.setSeedGeneric(jvRequest["generator"]["regular_seed"].asString())) + { + jvResult["error"] = "malformedAddress"; + } + else + { + RippleAddress na0Public; // To find the generator's index. + RippleAddress naGenerator = RippleAddress::createGeneratorPublic(naGeneratorID); + + na0Public.setAccountPublic(naGenerator, 0); + + uNodeIndex = Ledger::getGeneratorIndex(na0Public.getAccountID()); + } + } + else if (jvRequest.isMember("offer")) + { + RippleAddress naAccountID; + + if (!jvRequest.isObject()) + { + uNodeIndex.SetHex(jvRequest["offer"].asString()); + } + else if (!jvRequest["offer"].isMember("account") + || !jvRequest["offer"].isMember("seq") + || !jvRequest["offer"]["seq"].isIntegral()) + { + jvResult["error"] = "malformedRequest"; + } + else if (!naAccountID.setAccountID(jvRequest["offer"]["account"].asString())) + { + jvResult["error"] = "malformedAddress"; + } + else + { + uint32 uSequence = jvRequest["offer"]["seq"].asUInt(); + + uNodeIndex = Ledger::getOfferIndex(naAccountID.getAccountID(), uSequence); + } + } + else if (jvRequest.isMember("ripple_state")) + { + RippleAddress naA; + RippleAddress naB; + uint160 uCurrency; + Json::Value jvRippleState = jvRequest["ripple_state"]; + + if (!jvRippleState.isMember("currency") + || !jvRippleState.isMember("accounts") + || !jvRippleState["accounts"].isArray() + || 2 != jvRippleState["accounts"].size() + || !jvRippleState["accounts"][0u].isString() + || !jvRippleState["accounts"][1u].isString() + || jvRippleState["accounts"][0u].asString() == jvRippleState["accounts"][1u].asString() + ) { + + cLog(lsINFO) + << boost::str(boost::format("ledger_entry: ripple_state: accounts: %d currency: %d array: %d size: %d equal: %d") + % jvRippleState.isMember("accounts") + % jvRippleState.isMember("currency") + % jvRippleState["accounts"].isArray() + % jvRippleState["accounts"].size() + % (jvRippleState["accounts"][0u].asString() == jvRippleState["accounts"][1u].asString()) + ); + + jvResult["error"] = "malformedRequest"; + } + else if (!naA.setAccountID(jvRippleState["accounts"][0u].asString()) + || !naB.setAccountID(jvRippleState["accounts"][1u].asString())) { + jvResult["error"] = "malformedAddress"; + } + else if (!STAmount::currencyFromString(uCurrency, jvRippleState["currency"].asString())) { + jvResult["error"] = "malformedCurrency"; + } + else + { + uNodeIndex = Ledger::getRippleStateIndex(naA, naB, uCurrency); + } + } + else + { + jvResult["error"] = "unknownOption"; + } + + if (!!uNodeIndex) + { + SLE::pointer sleNode = noNetwork.getSLE(lpLedger, uNodeIndex); + + if (!sleNode) + { + // Not found. + // XXX Should also provide proof. + jvResult["error"] = "entryNotFound"; + } + else if (bNodeBinary) + { + // XXX Should also provide proof. + Serializer s; + + sleNode->add(s); + + jvResult["node_binary"] = strHex(s.peekData()); + jvResult["index"] = uNodeIndex.ToString(); + } + else + { + jvResult["node"] = sleNode->getJson(0); + jvResult["index"] = uNodeIndex.ToString(); + } + } +} + +// The objective is to allow the client to know the server's status. The only thing that show the server is fully operating is the +// stream of ledger_closeds. Therefore, that is all that is provided. A client can drop servers that do not provide recent +// ledger_closeds. +void WSConnection::doServerSubscribe(Json::Value& jvResult, Json::Value& jvRequest) +{ + if (!mNetwork.subLedger(this)) + { + jvResult["error"] = "serverSubscribed"; + } + else + { + if (theConfig.RUN_STANDALONE) + jvResult["stand_alone"] = 1; + + if (NetworkOPs::omDISCONNECTED != mNetwork.getOperatingMode()) { + jvResult["ledger_closed"] = mNetwork.getClosedLedger().ToString(); + jvResult["ledger_current_index"] = mNetwork.getCurrentLedgerID(); + } + } +} + +void WSConnection::doServerUnsubscribe(Json::Value& jvResult, Json::Value& jvRequest) +{ + if (!mNetwork.unsubLedger(this)) + { + jvResult["error"] = "serverNotSubscribed"; + } +} + +void WSConnection::doRPC(Json::Value& jvResult, Json::Value& jvRequest) +{ + if (jvRequest.isMember("command") && jvRequest.isMember("params")) + { + jvResult=theApp->getRPCHandler().doCommand(jvRequest["command"].asString(),jvRequest["params"],RPCHandler::GUEST); + + }else jvResult["error"] = "fieldNotCommand"; + +} + +// XXX Currently requires secret. Allow signed transaction as an alternative. +void WSConnection::doSubmit(Json::Value& jvResult, Json::Value& jvRequest) +{ + if (!jvRequest.isMember("tx_json")) + { + jvResult["error"] = "fieldNotFoundTransaction"; + }else if (!jvRequest.isMember("key")) + { + jvResult["error"] = "fieldNotFoundKey"; + }else jvResult=theApp->getRPCHandler().handleJSONSubmit(jvRequest["key"].asString(),jvRequest["tx_json"]); +} + +void WSConnection::doTransactionEntry(Json::Value& jvResult, Json::Value& jvRequest) +{ + if (!jvRequest.isMember("transaction")) + { + jvResult["error"] = "fieldNotFoundTransaction"; + } + if (!jvRequest.isMember("ledger_closed")) + { + jvResult["error"] = "notYetImplemented"; // XXX We don't support any transaction yet. + } + else + { + uint256 uTransID; + // XXX Relying on trusted WSS client. Would be better to have a strict routine, returning success or failure. + uTransID.SetHex(jvRequest["transaction"].asString()); + + uint256 uLedgerID; + // XXX Relying on trusted WSS client. Would be better to have a strict routine, returning success or failure. + uLedgerID.SetHex(jvRequest["ledger_closed"].asString()); + + Ledger::pointer lpLedger = theApp->getMasterLedger().getLedgerByHash(uLedgerID); + + if (!lpLedger) { + jvResult["error"] = "ledgerNotFound"; + } + else + { + Transaction::pointer tpTrans; + TransactionMetaSet::pointer tmTrans; + + if (!lpLedger-> getTransaction(uTransID, tpTrans, tmTrans)) + { + jvResult["error"] = "transactionNotFound"; + } + else + { + jvResult["transaction"] = tpTrans->getJson(0); + jvResult["metadata"] = tmTrans->getJson(0); + // 'accounts' + // 'engine_...' + // 'ledger_...' + } + } + } +} + +void WSConnection::doTransactionSubcribe(Json::Value& jvResult, Json::Value& jvRequest) +{ + if (!mNetwork.subTransaction(this)) + { + jvResult["error"] = "TransactionsSubscribed"; + } +} + +void WSConnection::doTransactionUnsubscribe(Json::Value& jvResult, Json::Value& jvRequest) +{ + if (!mNetwork.unsubTransaction(this)) + { + jvResult["error"] = "TransactionsNotSubscribed"; + } +} diff --git a/src/WSConnection.h b/src/WSConnection.h new file mode 100644 index 0000000000..fee4cc7ad0 --- /dev/null +++ b/src/WSConnection.h @@ -0,0 +1,75 @@ +#include "../websocketpp/src/sockets/tls.hpp" +#include "../websocketpp/src/websocketpp.hpp" +#include "WSDoor.h" +#include "Application.h" + +#include "Log.h" +#include "NetworkOPs.h" + +template +class WSServerHandler; +// +// Storage for connection specific info +// - Subscriptions +// +class WSConnection : public InfoSub +{ +public: + typedef websocketpp::WSDOOR_SERVER::handler::connection_ptr connection_ptr; + typedef websocketpp::WSDOOR_SERVER::handler::message_ptr message_ptr; + +protected: + typedef void (WSConnection::*doFuncPtr)(Json::Value& jvResult, Json::Value &jvRequest); + + boost::mutex mLock; + boost::unordered_set mSubAccountInfo; + boost::unordered_set mSubAccountTransaction; + + WSServerHandler* mHandler; + connection_ptr mConnection; + NetworkOPs& mNetwork; + +public: + // WSConnection() + // : mHandler((WSServerHandler*)(NULL)), + // mConnection(connection_ptr()) { ; } + + WSConnection(WSServerHandler* wshpHandler, connection_ptr cpConnection) + : mHandler(wshpHandler), mConnection(cpConnection), mNetwork(theApp->getOPs()) { ; } + + virtual ~WSConnection(); + + // Implement overridden functions from base class: + void send(const Json::Value& jvObj); + + // Utilities + Json::Value invokeCommand(Json::Value& jvRequest); + boost::unordered_set parseAccountIds(const Json::Value& jvArray); + + // Commands + void doSubmit(Json::Value& jvResult, Json::Value& jvRequest); + void doRPC(Json::Value& jvResult, Json::Value& jvRequest); + void doSubscribe(Json::Value& jvResult, Json::Value& jvRequest); + void doUnsubscribe(Json::Value& jvResult, Json::Value& jvRequest); + + + + // deprecated + void doLedgerAccept(Json::Value& jvResult, Json::Value& jvRequest); + void doLedgerClosed(Json::Value& jvResult, Json::Value& jvRequest); + void doLedgerCurrent(Json::Value& jvResult, Json::Value& jvRequest); + void doLedgerEntry(Json::Value& jvResult, Json::Value& jvRequest); + void doTransactionEntry(Json::Value& jvResult, Json::Value& jvRequest); + + void doAccountInfoSubscribe(Json::Value& jvResult, Json::Value& jvRequest); + void doAccountInfoUnsubscribe(Json::Value& jvResult, Json::Value& jvRequest); + void doAccountTransactionSubscribe(Json::Value& jvResult, Json::Value& jvRequest); + void doAccountTransactionUnsubscribe(Json::Value& jvResult, Json::Value& jvRequest); + + void doServerSubscribe(Json::Value& jvResult, Json::Value& jvRequest); + void doServerUnsubscribe(Json::Value& jvResult, Json::Value& jvRequest); + void doLedgerAccountsSubcribe(Json::Value& jvResult, Json::Value& jvRequest); + void doLedgerAccountsUnsubscribe(Json::Value& jvResult, Json::Value& jvRequest); + void doTransactionSubcribe(Json::Value& jvResult, Json::Value& jvRequest); + void doTransactionUnsubscribe(Json::Value& jvResult, Json::Value& jvRequest); +}; \ No newline at end of file diff --git a/src/WSDoor.cpp b/src/WSDoor.cpp index 9d5304a5de..9629841590 100644 --- a/src/WSDoor.cpp +++ b/src/WSDoor.cpp @@ -1,12 +1,14 @@ #include "WSDoor.h" + #include "Application.h" #include "Config.h" #include "Log.h" #include "NetworkOPs.h" #include "utils.h" - +#include "WSConnection.h" +#include "WSHandler.h" #include #include @@ -14,8 +16,6 @@ #include #include -#include "../json/reader.h" -#include "../json/writer.h" SETUP_LOG(); @@ -39,192 +39,9 @@ static DH* handleTmpDh(SSL* ssl, int is_export, int iKeyLength) return 512 == iKeyLength ? theApp->getWallet().getDh512() : theApp->getWallet().getDh1024(); } -template -class WSServerHandler; - -// -// Storage for connection specific info -// - Subscriptions -// -class WSConnection : public InfoSub -{ -public: - typedef websocketpp::WSDOOR_SERVER::handler::connection_ptr connection_ptr; - typedef websocketpp::WSDOOR_SERVER::handler::message_ptr message_ptr; - -protected: - typedef void (WSConnection::*doFuncPtr)(Json::Value& jvResult, const Json::Value &jvRequest); - - boost::mutex mLock; - boost::unordered_set mSubAccountInfo; - boost::unordered_set mSubAccountTransaction; - - WSServerHandler* mHandler; - connection_ptr mConnection; - NetworkOPs& mNetwork; - -public: -// WSConnection() -// : mHandler((WSServerHandler*)(NULL)), -// mConnection(connection_ptr()) { ; } - - WSConnection(WSServerHandler* wshpHandler, connection_ptr cpConnection) - : mHandler(wshpHandler), mConnection(cpConnection), mNetwork(theApp->getOPs()) { ; } - - virtual ~WSConnection(); - - // Implement overridden functions from base class: - void send(const Json::Value& jvObj); - - // Utilities - Json::Value invokeCommand(const Json::Value& jvRequest); - boost::unordered_set parseAccountIds(const Json::Value& jvArray); - - // Commands - void doSubmit(Json::Value& jvResult, const Json::Value& jvRequest); - void doRPC(Json::Value& jvResult, const Json::Value& jvRequest); - void doSubscribe(Json::Value& jvResult, const Json::Value& jvRequest); - void doUnsubscribe(Json::Value& jvResult, const Json::Value& jvRequest); - // deprecated - void doLedgerAccept(Json::Value& jvResult, const Json::Value& jvRequest); - void doLedgerClosed(Json::Value& jvResult, const Json::Value& jvRequest); - void doLedgerCurrent(Json::Value& jvResult, const Json::Value& jvRequest); - void doLedgerEntry(Json::Value& jvResult, const Json::Value& jvRequest); - void doTransactionEntry(Json::Value& jvResult, const Json::Value& jvRequest); - - void doAccountInfoSubscribe(Json::Value& jvResult, const Json::Value& jvRequest); - void doAccountInfoUnsubscribe(Json::Value& jvResult, const Json::Value& jvRequest); - void doAccountTransactionSubscribe(Json::Value& jvResult, const Json::Value& jvRequest); - void doAccountTransactionUnsubscribe(Json::Value& jvResult, const Json::Value& jvRequest); - - void doServerSubscribe(Json::Value& jvResult, const Json::Value& jvRequest); - void doServerUnsubscribe(Json::Value& jvResult, const Json::Value& jvRequest); - void doLedgerAccountsSubcribe(Json::Value& jvResult, const Json::Value& jvRequest); - void doLedgerAccountsUnsubscribe(Json::Value& jvResult, const Json::Value& jvRequest); - void doTransactionSubcribe(Json::Value& jvResult, const Json::Value& jvRequest); - void doTransactionUnsubscribe(Json::Value& jvResult, const Json::Value& jvRequest); -}; - -// A single instance of this object is made. -// This instance dispatches all events. There is no per connection persistence. -template -class WSServerHandler : public endpoint_type::handler -{ -public: - typedef typename endpoint_type::handler::connection_ptr connection_ptr; - typedef typename endpoint_type::handler::message_ptr message_ptr; - - // Private reasons to close. - enum { - crTooSlow = 4000, // Client is too slow. - }; - -private: - boost::shared_ptr mCtx; - -protected: - boost::mutex mMapLock; - // For each connection maintain an assoicated object to track subscriptions. - boost::unordered_map > mMap; - -public: - WSServerHandler(boost::shared_ptr spCtx) : mCtx(spCtx) {} - - boost::shared_ptr on_tls_init() - { - return mCtx; - } - - void send(connection_ptr cpClient, message_ptr mpMessage) - { - try - { - cpClient->send(mpMessage->get_payload(), mpMessage->get_opcode()); - } - catch (...) - { - cpClient->close(websocketpp::close::status::value(crTooSlow), std::string("Client is too slow.")); - } - } - - void send(connection_ptr cpClient, const std::string& strMessage) - { - try - { - cLog(lsDEBUG) << "Ws:: Sending '" << strMessage << "'"; - - cpClient->send(strMessage); - } - catch (...) - { - cpClient->close(websocketpp::close::status::value(crTooSlow), std::string("Client is too slow.")); - } - } - - void send(connection_ptr cpClient, const Json::Value& jvObj) - { - Json::FastWriter jfwWriter; - - // cLog(lsDEBUG) << "Ws:: Object '" << jfwWriter.write(jvObj) << "'"; - - send(cpClient, jfwWriter.write(jvObj)); - } - - void on_open(connection_ptr cpClient) - { - boost::mutex::scoped_lock sl(mMapLock); - - mMap[cpClient] = boost::make_shared(this, cpClient); - } - - void on_close(connection_ptr cpClient) - { - boost::mutex::scoped_lock sl(mMapLock); - - mMap.erase(cpClient); - } - - void on_message(connection_ptr cpClient, message_ptr mpMessage) - { - Json::Value jvRequest; - Json::Reader jrReader; - - if (mpMessage->get_opcode() != websocketpp::frame::opcode::TEXT) - { - Json::Value jvResult(Json::objectValue); - - jvResult["type"] = "error"; - jvResult["error"] = "wsTextRequired"; // We only accept text messages. - - send(cpClient, jvResult); - } - else if (!jrReader.parse(mpMessage->get_payload(), jvRequest) || jvRequest.isNull() || !jvRequest.isObject()) - { - Json::Value jvResult(Json::objectValue); - - jvResult["type"] = "error"; - jvResult["error"] = "jsonInvalid"; // Received invalid json. - jvResult["value"] = mpMessage->get_payload(); - - send(cpClient, jvResult); - } - else - { - send(cpClient, mMap[cpClient]->invokeCommand(jvRequest)); - } - } - - // Respond to http requests. - void http(connection_ptr cpClient) - { - cpClient->set_body( - "" SYSTEM_NAME " Test" - "

" SYSTEM_NAME " Test

This page shows http(s) connectivity is working.

"); - } -}; void WSDoor::startListening() { @@ -284,895 +101,5 @@ void WSDoor::stop() } } -// -// WSConnection -// - -WSConnection::~WSConnection() -{ - theApp->getOPs().unsubTransaction(this); - theApp->getOPs().unsubLedger(this); - theApp->getOPs().unsubLedgerAccounts(this); - theApp->getOPs().unsubAccountInfo(this, mSubAccountInfo); - theApp->getOPs().unsubAccountTransaction(this, mSubAccountTransaction); -} - -void WSConnection::send(const Json::Value& jvObj) -{ - mHandler->send(mConnection, jvObj); -} - -// -// Utilities -// - -Json::Value WSConnection::invokeCommand(const Json::Value& jvRequest) -{ - static struct { - const char* pCommand; - doFuncPtr dfpFunc; - } commandsA[] = { - // Request-Response Commands: - { "ledger_accept", &WSConnection::doLedgerAccept }, - { "ledger_closed", &WSConnection::doLedgerClosed }, - { "ledger_current", &WSConnection::doLedgerCurrent }, - { "ledger_entry", &WSConnection::doLedgerEntry }, - { "submit", &WSConnection::doSubmit }, - { "transaction_entry", &WSConnection::doTransactionEntry }, - { "subscribe", &WSConnection::doSubscribe }, - { "unsubscribe", &WSConnection::doUnsubscribe }, - - // deprecated - { "account_info_subscribe", &WSConnection::doAccountInfoSubscribe }, - { "account_info_unsubscribe", &WSConnection::doAccountInfoUnsubscribe }, - { "account_transaction_subscribe", &WSConnection::doAccountTransactionSubscribe }, - { "account_transaction_unsubscribe", &WSConnection::doAccountTransactionUnsubscribe }, - { "ledger_accounts_subscribe", &WSConnection::doLedgerAccountsSubcribe }, - { "ledger_accounts_unsubscribe", &WSConnection::doLedgerAccountsUnsubscribe }, - { "server_subscribe", &WSConnection::doServerSubscribe }, - { "server_unsubscribe", &WSConnection::doServerUnsubscribe }, - { "transaction_subscribe", &WSConnection::doTransactionSubcribe }, - { "transaction_unsubscribe", &WSConnection::doTransactionUnsubscribe }, - }; - - if (!jvRequest.isMember("command")) - { - Json::Value jvResult(Json::objectValue); - - jvResult["type"] = "response"; - jvResult["result"] = "error"; - jvResult["error"] = "missingCommand"; - jvResult["command"] = jvRequest; - - return jvResult; - } - - std::string strCommand = jvRequest["command"].asString(); - - int i = NUMBER(commandsA); - - while (i-- && strCommand != commandsA[i].pCommand) - ; - - Json::Value jvResult(Json::objectValue); - - jvResult["type"] = "response"; - - if (i < 0) - { - jvResult["error"] = "unknownCommand"; // Unknown command. - } - else - { - (this->*(commandsA[i].dfpFunc))(jvResult, jvRequest); - } - - if (jvRequest.isMember("id")) - { - jvResult["id"] = jvRequest["id"]; - } - - if (jvResult.isMember("error")) - { - jvResult["result"] = "error"; - jvResult["request"] = jvRequest; - } - else - { - jvResult["result"] = "success"; - } - - return jvResult; -} - -boost::unordered_set WSConnection::parseAccountIds(const Json::Value& jvArray) -{ - boost::unordered_set usnaResult; - - for (Json::Value::const_iterator it = jvArray.begin(); it != jvArray.end(); it++) - { - RippleAddress naString; - - if (!(*it).isString() || !naString.setAccountID((*it).asString())) - { - usnaResult.clear(); - break; - } - else - { - (void) usnaResult.insert(naString); - } - } - - return usnaResult; -} - -// -// Commands -// - -/* -server : Sends a message anytime the server status changes such as network connectivity. -ledger : Sends a message at every ledger close. -transactions : Sends a message for every transaction that makes it into a ledger. -rt_transactions -accounts -rt_accounts -*/ -// TODO -void WSConnection::doSubscribe(Json::Value& jvResult, const Json::Value& jvRequest) -{ - if (jvRequest.isMember("streams")) - { - for (Json::Value::const_iterator it = jvRequest["streams"].begin(); it != jvRequest["streams"].end(); it++) - { - if ((*it).isString() ) - { - std::string streamName=(*it).asString(); - - if(streamName=="server") - { - mNetwork.subLedgerAccounts(this); - }else if(streamName=="ledger") - { - mNetwork.subLedgerAccounts(this); - }else if(streamName=="transactions") - { - mNetwork.subTransaction(this); - }else if(streamName=="rt_transactions") - { - mNetwork.subTransaction(this); // TODO - }else - { - jvResult["error"] = str(boost::format("Unknown stream: %s") % streamName); - } - }else - { - jvResult["error"] = "malformedSteam"; - } - } - } - - if (jvRequest.isMember("rt_accounts")) - { - boost::unordered_set usnaAccoundIds = parseAccountIds(jvRequest["rt_accounts"]); - - if (usnaAccoundIds.empty()) - { - jvResult["error"] = "malformedAccount"; - }else - { - boost::mutex::scoped_lock sl(mLock); - - BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds) - { - mSubAccountInfo.insert(naAccountID); - } - - mNetwork.subAccountInfo(this, usnaAccoundIds); - } - } - - if (jvRequest.isMember("accounts")) - { - boost::unordered_set usnaAccoundIds = parseAccountIds(jvRequest["accounts"]); - - if (usnaAccoundIds.empty()) - { - jvResult["error"] = "malformedAccount"; - }else - { - boost::mutex::scoped_lock sl(mLock); - - BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds) - { - mSubAccountInfo.insert(naAccountID); - } - - mNetwork.subAccountInfo(this, usnaAccoundIds); - } - } -} - -void WSConnection::doUnsubscribe(Json::Value& jvResult, const Json::Value& jvRequest) -{ - -} - -void WSConnection::doAccountInfoSubscribe(Json::Value& jvResult, const Json::Value& jvRequest) -{ - if (!jvRequest.isMember("accounts")) - { - jvResult["error"] = "missingField"; - } - else if (jvRequest["accounts"].empty()) - { - jvResult["error"] = "emptySet"; - } - else - { - boost::unordered_set usnaAccoundIds = parseAccountIds(jvRequest["accounts"]); - - - } -} - -void WSConnection::doAccountInfoUnsubscribe(Json::Value& jvResult, const Json::Value& jvRequest) -{ - if (!jvRequest.isMember("accounts")) - { - jvResult["error"] = "missingField"; - } - else if (jvRequest["accounts"].empty()) - { - jvResult["error"] = "emptySet"; - } - else - { - boost::unordered_set usnaAccoundIds = parseAccountIds(jvRequest["accounts"]); - - if (usnaAccoundIds.empty()) - { - jvResult["error"] = "malformedAccount"; - } - else - { - boost::mutex::scoped_lock sl(mLock); - - BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds) - { - mSubAccountInfo.erase(naAccountID); - } - - mNetwork.unsubAccountInfo(this, usnaAccoundIds); - } - } -} - -void WSConnection::doAccountTransactionSubscribe(Json::Value& jvResult, const Json::Value& jvRequest) -{ - if (!jvRequest.isMember("accounts")) - { - jvResult["error"] = "missingField"; - } - else if (jvRequest["accounts"].empty()) - { - jvResult["error"] = "emptySet"; - } - else - { - boost::unordered_set usnaAccoundIds = parseAccountIds(jvRequest["accounts"]); - - if (usnaAccoundIds.empty()) - { - jvResult["error"] = "malformedAccount"; - } - else - { - boost::mutex::scoped_lock sl(mLock); - - BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds) - { - mSubAccountTransaction.insert(naAccountID); - } - - mNetwork.subAccountTransaction(this, usnaAccoundIds); - } - } -} - -void WSConnection::doAccountTransactionUnsubscribe(Json::Value& jvResult, const Json::Value& jvRequest) -{ - if (!jvRequest.isMember("accounts")) - { - jvResult["error"] = "missingField"; - } - else if (jvRequest["accounts"].empty()) - { - jvResult["error"] = "emptySet"; - } - else - { - boost::unordered_set usnaAccoundIds = parseAccountIds(jvRequest["accounts"]); - - if (usnaAccoundIds.empty()) - { - jvResult["error"] = "malformedAccount"; - } - else - { - boost::mutex::scoped_lock sl(mLock); - - BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds) - { - mSubAccountTransaction.erase(naAccountID); - } - - mNetwork.unsubAccountTransaction(this, usnaAccoundIds); - } - } -} - -void WSConnection::doLedgerAccountsSubcribe(Json::Value& jvResult, const Json::Value& jvRequest) -{ - if (!mNetwork.subLedgerAccounts(this)) - { - jvResult["error"] = "ledgerAccountsSubscribed"; - } -} - -void WSConnection::doLedgerAccountsUnsubscribe(Json::Value& jvResult, const Json::Value& jvRequest) -{ - if (!mNetwork.unsubLedgerAccounts(this)) - { - jvResult["error"] = "ledgerAccountsNotSubscribed"; - } -} - -void WSConnection::doLedgerAccept(Json::Value& jvResult, const Json::Value& jvRequest) -{ - if (!theConfig.RUN_STANDALONE) - { - jvResult["error"] = "notStandAlone"; - } - else - { - mNetwork.acceptLedger(); - - jvResult["ledger_current_index"] = mNetwork.getCurrentLedgerID(); - } -} - -void WSConnection::doLedgerClosed(Json::Value& jvResult, const Json::Value& jvRequest) -{ - uint256 uLedger = mNetwork.getClosedLedger(); - - jvResult["ledger_closed_index"] = mNetwork.getLedgerID(uLedger); - jvResult["ledger_closed"] = uLedger.ToString(); -} - -void WSConnection::doLedgerCurrent(Json::Value& jvResult, const Json::Value& jvRequest) -{ - jvResult["ledger_current_index"] = mNetwork.getCurrentLedgerID(); -} - -void WSConnection::doLedgerEntry(Json::Value& jvResult, const Json::Value& jvRequest) -{ - NetworkOPs& noNetwork = mNetwork; - uint256 uLedger = jvRequest.isMember("ledger_closed") ? uint256(jvRequest["ledger_closed"].asString()) : 0; - uint32 uLedgerIndex = jvRequest.isMember("ledger_index") && jvRequest["ledger_index"].isNumeric() ? jvRequest["ledger_index"].asUInt() : 0; - - Ledger::pointer lpLedger; - - if (!!uLedger) - { - // Ledger directly specified. - lpLedger = noNetwork.getLedgerByHash(uLedger); - - if (!lpLedger) - { - jvResult["error"] = "ledgerNotFound"; - return; - } - - uLedgerIndex = lpLedger->getLedgerSeq(); // Set the current index, override if needed. - } - else if (!!uLedgerIndex) - { - lpLedger = noNetwork.getLedgerBySeq(uLedgerIndex); - - if (!lpLedger) - { - jvResult["error"] = "ledgerNotFound"; // ledger_index from future? - return; - } - } - else - { - // Default to current ledger. - lpLedger = noNetwork.getCurrentLedger(); - uLedgerIndex = lpLedger->getLedgerSeq(); // Set the current index. - } - - if (lpLedger->isClosed()) - { - if (!!uLedger) - jvResult["ledger_closed"] = uLedger.ToString(); - - jvResult["ledger_closed_index"] = uLedgerIndex; - } - else - { - jvResult["ledger_current_index"] = uLedgerIndex; - } - - uint256 uNodeIndex; - bool bNodeBinary = false; - - if (jvRequest.isMember("index")) - { - // XXX Needs to provide proof. - uNodeIndex.SetHex(jvRequest["index"].asString()); - bNodeBinary = true; - } - else if (jvRequest.isMember("account_root")) - { - RippleAddress naAccount; - - if (!naAccount.setAccountID(jvRequest["account_root"].asString()) - || !naAccount.getAccountID()) - { - jvResult["error"] = "malformedAddress"; - } - else - { - uNodeIndex = Ledger::getAccountRootIndex(naAccount.getAccountID()); - } - } - else if (jvRequest.isMember("directory")) - { - - if (!jvRequest.isObject()) - { - uNodeIndex.SetHex(jvRequest["directory"].asString()); - } - else if (jvRequest["directory"].isMember("sub_index") - && !jvRequest["directory"]["sub_index"].isIntegral()) - { - jvResult["error"] = "malformedRequest"; - } - else - { - uint64 uSubIndex = jvRequest["directory"].isMember("sub_index") - ? jvRequest["directory"]["sub_index"].asUInt() - : 0; - - if (jvRequest["directory"].isMember("dir_root")) - { - uint256 uDirRoot; - - uDirRoot.SetHex(jvRequest["dir_root"].asString()); - - uNodeIndex = Ledger::getDirNodeIndex(uDirRoot, uSubIndex); - } - else if (jvRequest["directory"].isMember("owner")) - { - RippleAddress naOwnerID; - - if (!naOwnerID.setAccountID(jvRequest["directory"]["owner"].asString())) - { - jvResult["error"] = "malformedAddress"; - } - else - { - uint256 uDirRoot = Ledger::getOwnerDirIndex(naOwnerID.getAccountID()); - - uNodeIndex = Ledger::getDirNodeIndex(uDirRoot, uSubIndex); - } - } - else - { - jvResult["error"] = "malformedRequest"; - } - } - } - else if (jvRequest.isMember("generator")) - { - RippleAddress naGeneratorID; - - if (!jvRequest.isObject()) - { - uNodeIndex.SetHex(jvRequest["generator"].asString()); - } - else if (!jvRequest["generator"].isMember("regular_seed")) - { - jvResult["error"] = "malformedRequest"; - } - else if (!naGeneratorID.setSeedGeneric(jvRequest["generator"]["regular_seed"].asString())) - { - jvResult["error"] = "malformedAddress"; - } - else - { - RippleAddress na0Public; // To find the generator's index. - RippleAddress naGenerator = RippleAddress::createGeneratorPublic(naGeneratorID); - - na0Public.setAccountPublic(naGenerator, 0); - - uNodeIndex = Ledger::getGeneratorIndex(na0Public.getAccountID()); - } - } - else if (jvRequest.isMember("offer")) - { - RippleAddress naAccountID; - - if (!jvRequest.isObject()) - { - uNodeIndex.SetHex(jvRequest["offer"].asString()); - } - else if (!jvRequest["offer"].isMember("account") - || !jvRequest["offer"].isMember("seq") - || !jvRequest["offer"]["seq"].isIntegral()) - { - jvResult["error"] = "malformedRequest"; - } - else if (!naAccountID.setAccountID(jvRequest["offer"]["account"].asString())) - { - jvResult["error"] = "malformedAddress"; - } - else - { - uint32 uSequence = jvRequest["offer"]["seq"].asUInt(); - - uNodeIndex = Ledger::getOfferIndex(naAccountID.getAccountID(), uSequence); - } - } - else if (jvRequest.isMember("ripple_state")) - { - RippleAddress naA; - RippleAddress naB; - uint160 uCurrency; - Json::Value jvRippleState = jvRequest["ripple_state"]; - - if (!jvRippleState.isMember("currency") - || !jvRippleState.isMember("accounts") - || !jvRippleState["accounts"].isArray() - || 2 != jvRippleState["accounts"].size() - || !jvRippleState["accounts"][0u].isString() - || !jvRippleState["accounts"][1u].isString() - || jvRippleState["accounts"][0u].asString() == jvRippleState["accounts"][1u].asString() - ) { - - cLog(lsINFO) - << boost::str(boost::format("ledger_entry: ripple_state: accounts: %d currency: %d array: %d size: %d equal: %d") - % jvRippleState.isMember("accounts") - % jvRippleState.isMember("currency") - % jvRippleState["accounts"].isArray() - % jvRippleState["accounts"].size() - % (jvRippleState["accounts"][0u].asString() == jvRippleState["accounts"][1u].asString()) - ); - - jvResult["error"] = "malformedRequest"; - } - else if (!naA.setAccountID(jvRippleState["accounts"][0u].asString()) - || !naB.setAccountID(jvRippleState["accounts"][1u].asString())) { - jvResult["error"] = "malformedAddress"; - } - else if (!STAmount::currencyFromString(uCurrency, jvRippleState["currency"].asString())) { - jvResult["error"] = "malformedCurrency"; - } - else - { - uNodeIndex = Ledger::getRippleStateIndex(naA, naB, uCurrency); - } - } - else - { - jvResult["error"] = "unknownOption"; - } - - if (!!uNodeIndex) - { - SLE::pointer sleNode = noNetwork.getSLE(lpLedger, uNodeIndex); - - if (!sleNode) - { - // Not found. - // XXX Should also provide proof. - jvResult["error"] = "entryNotFound"; - } - else if (bNodeBinary) - { - // XXX Should also provide proof. - Serializer s; - - sleNode->add(s); - - jvResult["node_binary"] = strHex(s.peekData()); - jvResult["index"] = uNodeIndex.ToString(); - } - else - { - jvResult["node"] = sleNode->getJson(0); - jvResult["index"] = uNodeIndex.ToString(); - } - } -} - -// The objective is to allow the client to know the server's status. The only thing that show the server is fully operating is the -// stream of ledger_closeds. Therefore, that is all that is provided. A client can drop servers that do not provide recent -// ledger_closeds. -void WSConnection::doServerSubscribe(Json::Value& jvResult, const Json::Value& jvRequest) -{ - if (!mNetwork.subLedger(this)) - { - jvResult["error"] = "serverSubscribed"; - } - else - { - if (theConfig.RUN_STANDALONE) - jvResult["stand_alone"] = 1; - - if (NetworkOPs::omDISCONNECTED != mNetwork.getOperatingMode()) { - jvResult["ledger_closed"] = mNetwork.getClosedLedger().ToString(); - jvResult["ledger_current_index"] = mNetwork.getCurrentLedgerID(); - } - } -} - -void WSConnection::doServerUnsubscribe(Json::Value& jvResult, const Json::Value& jvRequest) -{ - if (!mNetwork.unsubLedger(this)) - { - jvResult["error"] = "serverNotSubscribed"; - } -} - -void WSConnection::doRPC(Json::Value& jvResult, const Json::Value& jvRequest) -{ - if (jvRequest.isMember("command")) - { - // TODO - }else jvResult["error"] = "fieldNotCommand"; - -} - -// XXX Current requires secret. Allow signed transaction as an alternative. -void WSConnection::doSubmit(Json::Value& jvResult, const Json::Value& jvRequest) -{ - RippleAddress naAccount; - - if (!jvRequest.isMember("transaction")) - { - jvResult["error"] = "fieldNotFoundTransaction"; - } - else if (!jvRequest["transaction"].isMember("Account")) - { - jvResult["error"] = "fieldNotFoundAccount"; - } - else if (!naAccount.setAccountID(jvRequest["transaction"]["Account"].asString())) - { - jvResult["error"] = "malformedAccount"; - } - else if (!jvRequest.isMember("secret")) - { - jvResult["error"] = "fieldNotFoundSecret"; - } - else - { - Ledger::pointer lpCurrent = mNetwork.getCurrentLedger(); - SLE::pointer sleAccountRoot = mNetwork.getSLE(lpCurrent, Ledger::getAccountRootIndex(naAccount.getAccountID())); - - if (!sleAccountRoot) - { - // XXX Ignore transactions for accounts not created. - - jvResult["error"] = "accountNotFound"; - return; - } - - bool bHaveAuthKey = false; - RippleAddress naAuthorizedPublic; -#if 0 - - if (sleAccountRoot->isFieldPresent(sfAuthorizedKey)) - { - naAuthorizedPublic = mLedgerEntry->getFieldAccount(sfAuthorizedKey); - // Json::Value obj = getMasterGenerator(uLedger, naRegularSeed, naMasterGenerator); - } -#endif - - RippleAddress naSecret = RippleAddress::createSeedGeneric(jvRequest["secret"].asString()); - RippleAddress naMasterGenerator = RippleAddress::createGeneratorPublic(naSecret); - - // Find the index of Account from the master generator, so we can generate the public and private keys. - RippleAddress naMasterAccountPublic; - unsigned int iIndex = 0; - bool bFound = false; - - // Don't look at ledger entries to determine if the account exists. Don't want to leak to thin server that these accounts are - // related. - while (!bFound && iIndex != theConfig.ACCOUNT_PROBE_MAX) - { - naMasterAccountPublic.setAccountPublic(naMasterGenerator, iIndex); - - Log(lsWARNING) << "authorize: " << iIndex << " : " << naMasterAccountPublic.humanAccountID() << " : " << naAccount.humanAccountID(); - - bFound = naAccount.getAccountID() == naMasterAccountPublic.getAccountID(); - if (!bFound) - ++iIndex; - } - - if (!bFound) - { - jvResult["error"] = "accountNotMatched"; - return; - } - - // Use the generator to determine the associated public and private keys. - RippleAddress naGenerator = RippleAddress::createGeneratorPublic(naSecret); - RippleAddress naAccountPublic = RippleAddress::createAccountPublic(naGenerator, iIndex); - RippleAddress naAccountPrivate = RippleAddress::createAccountPrivate(naGenerator, naSecret, iIndex); - - if (bHaveAuthKey - // The generated pair must match authorized... - && naAuthorizedPublic.getAccountID() != naAccountPublic.getAccountID() - // ... or the master key must have been used. - && naAccount.getAccountID() != naAccountPublic.getAccountID()) - { - // std::cerr << "iIndex: " << iIndex << std::endl; - // std::cerr << "sfAuthorizedKey: " << strHex(asSrc->getAuthorizedKey().getAccountID()) << std::endl; - // std::cerr << "naAccountPublic: " << strHex(naAccountPublic.getAccountID()) << std::endl; - - jvResult["error"] = "passwordChanged"; - return; - } - - std::auto_ptr sopTrans; - - try - { - sopTrans = STObject::parseJson(jvRequest["transaction"]); - } - catch (std::exception& e) - { - jvResult["error"] = "malformedTransaction"; - jvResult["error_exception"] = e.what(); - return; - } - - sopTrans->setFieldVL(sfSigningPubKey, naAccountPublic.getAccountPublic()); - - SerializedTransaction::pointer stpTrans; - - try - { - stpTrans = boost::make_shared(*sopTrans); - } - catch (std::exception& e) - { - jvResult["error"] = "invalidTransaction"; - jvResult["error_exception"] = e.what(); - return; - } - - stpTrans->sign(naAccountPrivate); - - Transaction::pointer tpTrans; - - try - { - tpTrans = boost::make_shared(stpTrans, false); - } - catch (std::exception& e) - { - jvResult["error"] = "internalTransaction"; - jvResult["error_exception"] = e.what(); - return; - } - - try - { - tpTrans = mNetwork.submitTransaction(tpTrans); - - if (!tpTrans) { - jvResult["error"] = "invalidTransaction"; - jvResult["error_exception"] = "Unable to sterilize transaction."; - return; - } - } - catch (std::exception& e) - { - jvResult["error"] = "internalSubmit"; - jvResult["error_exception"] = e.what(); - return; - } - - try - { - jvResult["transaction"] = tpTrans->getJson(0); - - if (temUNCERTAIN != tpTrans->getResult()) - { - std::string sToken; - std::string sHuman; - - transResultInfo(tpTrans->getResult(), sToken, sHuman); - - jvResult["engine_result"] = sToken; - jvResult["engine_result_code"] = tpTrans->getResult(); - jvResult["engine_result_message"] = sHuman; - } - } - catch (std::exception& e) - { - jvResult["error"] = "internalJson"; - jvResult["error_exception"] = e.what(); - return; - } - } -} - -void WSConnection::doTransactionEntry(Json::Value& jvResult, const Json::Value& jvRequest) -{ - if (!jvRequest.isMember("transaction")) - { - jvResult["error"] = "fieldNotFoundTransaction"; - } - if (!jvRequest.isMember("ledger_closed")) - { - jvResult["error"] = "notYetImplemented"; // XXX We don't support any transaction yet. - } - else - { - uint256 uTransID; - // XXX Relying on trusted WSS client. Would be better to have a strict routine, returning success or failure. - uTransID.SetHex(jvRequest["transaction"].asString()); - - uint256 uLedgerID; - // XXX Relying on trusted WSS client. Would be better to have a strict routine, returning success or failure. - uLedgerID.SetHex(jvRequest["ledger_closed"].asString()); - - Ledger::pointer lpLedger = theApp->getMasterLedger().getLedgerByHash(uLedgerID); - - if (!lpLedger) { - jvResult["error"] = "ledgerNotFound"; - } - else - { - Transaction::pointer tpTrans; - TransactionMetaSet::pointer tmTrans; - - if (!lpLedger-> getTransaction(uTransID, tpTrans, tmTrans)) - { - jvResult["error"] = "transactionNotFound"; - } - else - { - jvResult["transaction"] = tpTrans->getJson(0); - jvResult["metadata"] = tmTrans->getJson(0); - // 'accounts' - // 'engine_...' - // 'ledger_...' - } - } - } -} - -void WSConnection::doTransactionSubcribe(Json::Value& jvResult, const Json::Value& jvRequest) -{ - if (!mNetwork.subTransaction(this)) - { - jvResult["error"] = "TransactionsSubscribed"; - } -} - -void WSConnection::doTransactionUnsubscribe(Json::Value& jvResult, const Json::Value& jvRequest) -{ - if (!mNetwork.unsubTransaction(this)) - { - jvResult["error"] = "TransactionsNotSubscribed"; - } -} // vim:ts=4 diff --git a/src/WSHandler.h b/src/WSHandler.h new file mode 100644 index 0000000000..e400da35f7 --- /dev/null +++ b/src/WSHandler.h @@ -0,0 +1,124 @@ +#ifndef __WSHANDLER__ +#define __WSHANDLER__ + +class WSConnection; + +// A single instance of this object is made. +// This instance dispatches all events. There is no per connection persistence. +template +class WSServerHandler : public endpoint_type::handler +{ +public: + typedef typename endpoint_type::handler::connection_ptr connection_ptr; + typedef typename endpoint_type::handler::message_ptr message_ptr; + + // Private reasons to close. + enum { + crTooSlow = 4000, // Client is too slow. + }; + +private: + boost::shared_ptr mCtx; + +protected: + boost::mutex mMapLock; + // For each connection maintain an associated object to track subscriptions. + boost::unordered_map > mMap; + +public: + WSServerHandler(boost::shared_ptr spCtx) : mCtx(spCtx) {} + + boost::shared_ptr on_tls_init() + { + return mCtx; + } + + void send(connection_ptr cpClient, message_ptr mpMessage) + { + try + { + cpClient->send(mpMessage->get_payload(), mpMessage->get_opcode()); + } + catch (...) + { + cpClient->close(websocketpp::close::status::value(crTooSlow), std::string("Client is too slow.")); + } + } + + void send(connection_ptr cpClient, const std::string& strMessage) + { + try + { + cLog(lsDEBUG) << "Ws:: Sending '" << strMessage << "'"; + + cpClient->send(strMessage); + } + catch (...) + { + cpClient->close(websocketpp::close::status::value(crTooSlow), std::string("Client is too slow.")); + } + } + + void send(connection_ptr cpClient, const Json::Value& jvObj) + { + Json::FastWriter jfwWriter; + + // cLog(lsDEBUG) << "Ws:: Object '" << jfwWriter.write(jvObj) << "'"; + + send(cpClient, jfwWriter.write(jvObj)); + } + + void on_open(connection_ptr cpClient) + { + boost::mutex::scoped_lock sl(mMapLock); + + mMap[cpClient] = boost::make_shared(this, cpClient); + } + + void on_close(connection_ptr cpClient) + { + boost::mutex::scoped_lock sl(mMapLock); + + mMap.erase(cpClient); + } + + void on_message(connection_ptr cpClient, message_ptr mpMessage) + { + Json::Value jvRequest; + Json::Reader jrReader; + + if (mpMessage->get_opcode() != websocketpp::frame::opcode::TEXT) + { + Json::Value jvResult(Json::objectValue); + + jvResult["type"] = "error"; + jvResult["error"] = "wsTextRequired"; // We only accept text messages. + + send(cpClient, jvResult); + } + else if (!jrReader.parse(mpMessage->get_payload(), jvRequest) || jvRequest.isNull() || !jvRequest.isObject()) + { + Json::Value jvResult(Json::objectValue); + + jvResult["type"] = "error"; + jvResult["error"] = "jsonInvalid"; // Received invalid json. + jvResult["value"] = mpMessage->get_payload(); + + send(cpClient, jvResult); + } + else + { + send(cpClient, mMap[cpClient]->invokeCommand(jvRequest)); + } + } + + // Respond to http requests. + void http(connection_ptr cpClient) + { + cpClient->set_body( + "" SYSTEM_NAME " Test" + "

" SYSTEM_NAME " Test

This page shows http(s) connectivity is working.

"); + } +}; + +#endif