Paweł Bylica
10 years ago
123 changed files with 10449 additions and 970 deletions
@ -0,0 +1,139 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file DappHost.cpp
|
|||
* @author Arkadiy Paronyan <arkadiy@ethdev.org> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include "DappHost.h" |
|||
#include <QUrl> |
|||
#include <microhttpd.h> |
|||
#include <boost/algorithm/string.hpp> |
|||
#include <libdevcore/Common.h> |
|||
|
|||
using namespace dev; |
|||
|
|||
DappHost::DappHost(int _port, int _threads): |
|||
m_port(_port), m_threads(_threads), m_running(false), m_daemon(nullptr) |
|||
{ |
|||
startListening(); |
|||
} |
|||
|
|||
DappHost::~DappHost() |
|||
{ |
|||
stopListening(); |
|||
} |
|||
|
|||
void DappHost::startListening() |
|||
{ |
|||
if(!this->m_running) |
|||
{ |
|||
this->m_daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, this->m_port, nullptr, nullptr, &DappHost::callback, this, MHD_OPTION_THREAD_POOL_SIZE, this->m_threads, MHD_OPTION_END); |
|||
if (this->m_daemon != nullptr) |
|||
this->m_running = true; |
|||
} |
|||
} |
|||
|
|||
void DappHost::stopListening() |
|||
{ |
|||
if(this->m_running) |
|||
{ |
|||
MHD_stop_daemon(this->m_daemon); |
|||
this->m_running = false; |
|||
} |
|||
} |
|||
|
|||
void DappHost::sendOptionsResponse(MHD_Connection* _connection) |
|||
{ |
|||
MHD_Response *result = MHD_create_response_from_data(0, NULL, 0, 1); |
|||
MHD_add_response_header(result, "Allow", "GET, OPTIONS"); |
|||
MHD_add_response_header(result, "Access-Control-Allow-Headers", "origin, content-type, accept"); |
|||
MHD_add_response_header(result, "DAV", "1"); |
|||
MHD_queue_response(_connection, MHD_HTTP_OK, result); |
|||
MHD_destroy_response(result); |
|||
} |
|||
|
|||
void DappHost::sendNotAllowedResponse(MHD_Connection* _connection) |
|||
{ |
|||
MHD_Response *result = MHD_create_response_from_data(0, NULL, 0, 1); |
|||
MHD_add_response_header(result, "Allow", "GET, OPTIONS"); |
|||
MHD_queue_response(_connection, MHD_HTTP_METHOD_NOT_ALLOWED, result); |
|||
MHD_destroy_response(result); |
|||
} |
|||
|
|||
void DappHost::sendResponse(std::string const& _url, MHD_Connection* _connection) |
|||
{ |
|||
QUrl requestUrl(QString::fromStdString(_url)); |
|||
QString path = requestUrl.path().toLower(); |
|||
if (path.isEmpty()) |
|||
path = "/"; |
|||
|
|||
bytesConstRef response; |
|||
unsigned code = MHD_HTTP_NOT_FOUND; |
|||
std::string contentType; |
|||
|
|||
while (!path.isEmpty()) |
|||
{ |
|||
auto iter = m_entriesByPath.find(path); |
|||
if (iter != m_entriesByPath.end()) |
|||
{ |
|||
ManifestEntry const* entry = iter->second; |
|||
auto contentIter = m_dapp.content.find(entry->hash); |
|||
if (contentIter == m_dapp.content.end()) |
|||
break; |
|||
|
|||
response = bytesConstRef(contentIter->second.data(), contentIter->second.size()); |
|||
code = entry->httpStatus != 0 ? entry->httpStatus : MHD_HTTP_OK; |
|||
contentType = entry->contentType; |
|||
break; |
|||
} |
|||
path.truncate(path.length() - 1); |
|||
path = path.mid(0, path.lastIndexOf('/')); |
|||
} |
|||
|
|||
MHD_Response *result = MHD_create_response_from_data(response.size(), const_cast<byte*>(response.data()), 0, 1); |
|||
if (!contentType.empty()) |
|||
MHD_add_response_header(result, "Content-Type", contentType.c_str()); |
|||
MHD_queue_response(_connection, code, result); |
|||
MHD_destroy_response(result); |
|||
} |
|||
|
|||
int DappHost::callback(void* _cls, MHD_Connection* _connection, char const* _url, char const* _method, char const* _version, char const* _uploadData, size_t* _uploadDataSize, void** _conCls) |
|||
{ |
|||
(void)_version; |
|||
(void)_uploadData; |
|||
(void)_uploadDataSize; |
|||
(void)_conCls; |
|||
DappHost* host = static_cast<DappHost*>(_cls); |
|||
if (std::string("GET") == _method) |
|||
host->sendResponse(std::string(_url), _connection); |
|||
else if (std::string("OPTIONS") == _method) |
|||
host->sendOptionsResponse(_connection); |
|||
else |
|||
host->sendNotAllowedResponse(_connection); |
|||
return MHD_YES; |
|||
} |
|||
|
|||
QUrl DappHost::hostDapp(Dapp&& _dapp) |
|||
{ |
|||
m_dapp = std::move(_dapp); |
|||
m_entriesByPath.clear(); |
|||
for (ManifestEntry const& entry: m_dapp.manifest.entries) |
|||
m_entriesByPath[QString::fromStdString(entry.path)] = &entry; |
|||
|
|||
return QUrl(QString("http://localhost:%1/").arg(m_port)); |
|||
} |
@ -0,0 +1,58 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file DappHost.h
|
|||
* @author Arkadiy Paronyan <arkadiy@ethdev.org> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <map> |
|||
#include <QUrl> |
|||
#include <QString> |
|||
#include "DappLoader.h" |
|||
|
|||
struct MHD_Daemon; |
|||
struct MHD_Connection; |
|||
|
|||
/// DApp web server. Servers web content, resolves paths by hashes
|
|||
class DappHost |
|||
{ |
|||
public: |
|||
/// @param _port Network pork to listen for incoming connections
|
|||
/// @param _threads Max number of threads to process requests
|
|||
DappHost(int _port, int _threads = 10); |
|||
virtual ~DappHost(); |
|||
/// Load and host a dapp. Previsous dapp in discarded. Synchronous
|
|||
QUrl hostDapp(Dapp&& _dapp); |
|||
|
|||
private: |
|||
void startListening(); |
|||
void stopListening(); |
|||
void sendOptionsResponse(MHD_Connection* _connection); |
|||
void sendNotAllowedResponse(MHD_Connection* _connection); |
|||
void sendResponse(std::string const& _url, MHD_Connection* _connection); |
|||
static int callback(void* _cls, MHD_Connection* _connection, char const* _url, char const* _method, char const* _version, char const* _uploadData, size_t* _uploadDataSize, void** _conCls); |
|||
|
|||
int m_port; |
|||
int m_threads; |
|||
bool m_running; |
|||
MHD_Daemon* m_daemon; |
|||
Dapp m_dapp; |
|||
std::map<QString, ManifestEntry const*> m_entriesByPath; |
|||
}; |
|||
|
@ -0,0 +1,203 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file DappLoader.cpp
|
|||
* @author Arkadiy Paronyan <arkadiy@ethdev.org> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include <algorithm> |
|||
#include <json/json.h> |
|||
#include <QUrl> |
|||
#include <QStringList> |
|||
#include <QNetworkAccessManager> |
|||
#include <QNetworkReply> |
|||
#include <libdevcore/Common.h> |
|||
#include <libdevcore/RLP.h> |
|||
#include <libdevcrypto/CryptoPP.h> |
|||
#include <libdevcrypto/SHA3.h> |
|||
#include <libethcore/CommonJS.h> |
|||
#include <libethereum/Client.h> |
|||
#include <libwebthree/WebThree.h> |
|||
#include "DappLoader.h" |
|||
|
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
using namespace dev::crypto; |
|||
|
|||
Address c_registrar = Address("0000000000000000000000000000000000000a28"); |
|||
Address c_urlHint = Address("0000000000000000000000000000000000000a29"); |
|||
|
|||
QString contentsOfQResource(std::string const& res); |
|||
|
|||
DappLoader::DappLoader(QObject* _parent, WebThreeDirect* _web3): |
|||
QObject(_parent), m_web3(_web3) |
|||
{ |
|||
connect(&m_net, &QNetworkAccessManager::finished, this, &DappLoader::downloadComplete); |
|||
} |
|||
|
|||
DappLocation DappLoader::resolveAppUri(QString const& _uri) |
|||
{ |
|||
QUrl url(_uri); |
|||
if (!url.scheme().isEmpty() && url.scheme() != "eth") |
|||
throw dev::Exception(); //TODO:
|
|||
|
|||
QStringList parts = url.host().split('.', QString::SkipEmptyParts); |
|||
QStringList domainParts; |
|||
std::reverse(parts.begin(), parts.end()); |
|||
parts.append(url.path().split('/', QString::SkipEmptyParts)); |
|||
|
|||
Address address = c_registrar; |
|||
Address lastAddress; |
|||
int partIndex = 0; |
|||
|
|||
h256 contentHash; |
|||
|
|||
while (address && partIndex < parts.length()) |
|||
{ |
|||
lastAddress = address; |
|||
string32 name = { 0 }; |
|||
QByteArray utf8 = parts[partIndex].toUtf8(); |
|||
std::copy(utf8.data(), utf8.data() + utf8.size(), name.data()); |
|||
address = abiOut<Address>(web3()->ethereum()->call(address, abiIn("addr(string32)", name))); |
|||
domainParts.append(parts[partIndex]); |
|||
if (!address) |
|||
{ |
|||
//we have the address of the last part, try to get content hash
|
|||
contentHash = abiOut<h256>(web3()->ethereum()->call(lastAddress, abiIn("content(string32)", name))); |
|||
if (!contentHash) |
|||
throw dev::Exception() << errinfo_comment("Can't resolve address"); |
|||
} |
|||
++partIndex; |
|||
} |
|||
|
|||
|
|||
string32 contentUrl = abiOut<string32>(web3()->ethereum()->call(c_urlHint, abiIn("url(hash256)", contentHash))); |
|||
QString domain = domainParts.join('/'); |
|||
parts.erase(parts.begin(), parts.begin() + partIndex); |
|||
QString path = parts.join('/'); |
|||
QString contentUrlString = QString::fromUtf8(std::string(contentUrl.data(), contentUrl.size()).c_str()); |
|||
if (!contentUrlString.startsWith("http://") || !contentUrlString.startsWith("https://")) |
|||
contentUrlString = "http://" + contentUrlString; |
|||
return DappLocation { domain, path, contentUrlString, contentHash }; |
|||
} |
|||
|
|||
void DappLoader::downloadComplete(QNetworkReply* _reply) |
|||
{ |
|||
try |
|||
{ |
|||
//try to interpret as rlp
|
|||
QByteArray data = _reply->readAll(); |
|||
_reply->deleteLater(); |
|||
|
|||
h256 expected = m_uriHashes[_reply->request().url()]; |
|||
bytes package(reinterpret_cast<unsigned char const*>(data.constData()), reinterpret_cast<unsigned char const*>(data.constData() + data.size())); |
|||
Secp256k1 dec; |
|||
dec.decrypt(expected, package); |
|||
h256 got = sha3(package); |
|||
if (got != expected) |
|||
{ |
|||
//try base64
|
|||
data = QByteArray::fromBase64(data); |
|||
package = bytes(reinterpret_cast<unsigned char const*>(data.constData()), reinterpret_cast<unsigned char const*>(data.constData() + data.size())); |
|||
dec.decrypt(expected, package); |
|||
got = sha3(package); |
|||
if (got != expected) |
|||
throw dev::Exception() << errinfo_comment("Dapp content hash does not match"); |
|||
} |
|||
|
|||
RLP rlp(package); |
|||
loadDapp(rlp); |
|||
} |
|||
catch (...) |
|||
{ |
|||
qWarning() << tr("Error downloading DApp: ") << boost::current_exception_diagnostic_information().c_str(); |
|||
emit dappError(); |
|||
} |
|||
|
|||
} |
|||
|
|||
void DappLoader::loadDapp(RLP const& _rlp) |
|||
{ |
|||
Dapp dapp; |
|||
unsigned len = _rlp.itemCountStrict(); |
|||
dapp.manifest = loadManifest(_rlp[0].toString()); |
|||
for (unsigned c = 1; c < len; ++c) |
|||
{ |
|||
bytesConstRef content = _rlp[c].toBytesConstRef(); |
|||
h256 hash = sha3(content); |
|||
auto entry = std::find_if(dapp.manifest.entries.cbegin(), dapp.manifest.entries.cend(), [=](ManifestEntry const& _e) { return _e.hash == hash; }); |
|||
if (entry != dapp.manifest.entries.cend()) |
|||
{ |
|||
if (entry->path == "/deployment.js") |
|||
{ |
|||
//inject web3 code
|
|||
QString code; |
|||
code += contentsOfQResource(":/js/bignumber.min.js"); |
|||
code += "\n"; |
|||
code += contentsOfQResource(":/js/webthree.js"); |
|||
code += "\n"; |
|||
code += contentsOfQResource(":/js/setup.js"); |
|||
code += "\n"; |
|||
QByteArray res = code.toLatin1(); |
|||
bytes b(res.data(), res.data() + res.size()); |
|||
b.insert(b.end(), content.begin(), content.end()); |
|||
dapp.content[hash] = b; |
|||
} |
|||
else |
|||
dapp.content[hash] = content.toBytes(); |
|||
} |
|||
else |
|||
throw dev::Exception() << errinfo_comment("Dapp content hash does not match"); |
|||
} |
|||
emit dappReady(dapp); |
|||
} |
|||
|
|||
Manifest DappLoader::loadManifest(std::string const& _manifest) |
|||
{ |
|||
/// https://github.com/ethereum/go-ethereum/wiki/URL-Scheme
|
|||
Manifest manifest; |
|||
Json::Reader jsonReader; |
|||
Json::Value root; |
|||
jsonReader.parse(_manifest, root, false); |
|||
|
|||
Json::Value entries = root["entries"]; |
|||
for (Json::ValueIterator it = entries.begin(); it != entries.end(); ++it) |
|||
{ |
|||
Json::Value const& entryValue = *it; |
|||
std::string path = entryValue["path"].asString(); |
|||
if (path.size() == 0 || path[0] != '/') |
|||
path = "/" + path; |
|||
std::string contentType = entryValue["contentType"].asString(); |
|||
std::string strHash = entryValue["hash"].asString(); |
|||
if (strHash.length() == 64) |
|||
strHash = "0x" + strHash; |
|||
h256 hash = jsToFixed<32>(strHash); |
|||
unsigned httpStatus = entryValue["status"].asInt(); |
|||
manifest.entries.push_back(ManifestEntry{ path, hash, contentType, httpStatus }); |
|||
} |
|||
return manifest; |
|||
} |
|||
|
|||
void DappLoader::loadDapp(QString const& _uri) |
|||
{ |
|||
DappLocation location = resolveAppUri(_uri); |
|||
QUrl uri(location.contentUri); |
|||
QNetworkRequest request(uri); |
|||
m_uriHashes[uri] = location.contentHash; |
|||
m_net.get(request); |
|||
} |
|||
|
@ -0,0 +1,94 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file DappLoader.h
|
|||
* @author Arkadiy Paronyan <arkadiy@ethdev.org> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <map> |
|||
#include <string> |
|||
#include <vector> |
|||
#include <QObject> |
|||
#include <QString> |
|||
#include <QUrl> |
|||
#include <QNetworkAccessManager> |
|||
#include <libdevcore/FixedHash.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
class WebThreeDirect; |
|||
class RLP; |
|||
} |
|||
|
|||
struct ManifestEntry |
|||
{ |
|||
std::string path; |
|||
dev::h256 hash; |
|||
std::string contentType; |
|||
unsigned httpStatus; |
|||
}; |
|||
|
|||
struct Manifest |
|||
{ |
|||
std::vector<ManifestEntry> entries; |
|||
}; |
|||
|
|||
struct Dapp |
|||
{ |
|||
Manifest manifest; |
|||
std::map<dev::h256, dev::bytes> content; |
|||
}; |
|||
|
|||
|
|||
struct DappLocation |
|||
{ |
|||
QString canonDomain; |
|||
QString path; |
|||
QString contentUri; |
|||
dev::h256 contentHash; |
|||
}; |
|||
|
|||
///Downloads, unpacks and prepares DApps for hosting
|
|||
class DappLoader: public QObject |
|||
{ |
|||
Q_OBJECT |
|||
public: |
|||
DappLoader(QObject* _parent, dev::WebThreeDirect* _web3); |
|||
///Load a new DApp. Resolves a name with a name reg contract. Asynchronous. dappReady is emitted once everything is read, dappError othervise
|
|||
///@param _uri Eth name path
|
|||
void loadDapp(QString const& _uri); |
|||
|
|||
signals: |
|||
void dappReady(Dapp& _dapp); |
|||
void dappError(); |
|||
|
|||
private slots: |
|||
void downloadComplete(QNetworkReply* _reply); |
|||
|
|||
private: |
|||
dev::WebThreeDirect* web3() const { return m_web3; } |
|||
DappLocation resolveAppUri(QString const& _uri); |
|||
void loadDapp(dev::RLP const& _rlp); |
|||
Manifest loadManifest(std::string const& _manifest); |
|||
|
|||
dev::WebThreeDirect* m_web3; |
|||
QNetworkAccessManager m_net; |
|||
std::map<QUrl, dev::h256> m_uriHashes; |
|||
}; |
|||
|
@ -0,0 +1,28 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file WebPage.cpp
|
|||
* @author Arkadiy Paronyan arkadiy@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include "WebPage.h" |
|||
|
|||
void WebPage::javaScriptConsoleMessage(QWebEnginePage::JavaScriptConsoleMessageLevel _level, const QString& _message, int _lineNumber, const QString& _sourceID) |
|||
{ |
|||
QString prefix = _level == QWebEnginePage::ErrorMessageLevel ? "error" : _level == QWebEnginePage::WarningMessageLevel ? "warning" : ""; |
|||
emit consoleMessage(QString("%1(%2:%3):%4").arg(prefix).arg(_sourceID).arg(_lineNumber).arg(_message)); |
|||
} |
@ -0,0 +1,40 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file WebPage.h
|
|||
* @author Arkadiy Paronyan arkadiy@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <QString> |
|||
#pragma GCC diagnostic push |
|||
#pragma GCC diagnostic ignored "-Wpedantic" //QtWebEngineWidgets/qwebenginecertificateerror.h:78:348: error: extra ';'
|
|||
#include <QtWebEngineWidgets/QWebEnginePage> |
|||
#pragma GCC diagnostic pop |
|||
|
|||
class WebPage: public QWebEnginePage |
|||
{ |
|||
Q_OBJECT |
|||
public: |
|||
WebPage(QObject* _parent): QWebEnginePage(_parent) { } |
|||
signals: |
|||
void consoleMessage(QString const& _msg); |
|||
|
|||
protected: |
|||
void javaScriptConsoleMessage(QWebEnginePage::JavaScriptConsoleMessageLevel _level, const QString& _message, int _lineNumber, const QString& _sourceID) override; |
|||
}; |
File diff suppressed because it is too large
@ -0,0 +1,601 @@ |
|||
// ratio_io |
|||
// |
|||
// (C) Copyright Howard Hinnant |
|||
// Use, modification and distribution are subject to the Boost Software License, |
|||
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at |
|||
// http://www.boost.org/LICENSE_1_0.txt). |
|||
|
|||
#ifndef _RATIO_IO |
|||
#define _RATIO_IO |
|||
|
|||
/* |
|||
|
|||
ratio_io synopsis |
|||
|
|||
#include <ratio> |
|||
#include <string> |
|||
|
|||
namespace std |
|||
{ |
|||
|
|||
template <class Ratio, class charT> |
|||
struct ratio_string |
|||
{ |
|||
static basic_string<charT> symbol(); |
|||
static basic_string<charT> prefix(); |
|||
}; |
|||
|
|||
} // std |
|||
|
|||
*/ |
|||
|
|||
#include <ratio> |
|||
#include <string> |
|||
#include <sstream> |
|||
|
|||
namespace std { |
|||
|
|||
template <class _Ratio, class _CharT> |
|||
struct ratio_string |
|||
{ |
|||
static basic_string<_CharT> symbol() {return prefix();} |
|||
static basic_string<_CharT> prefix(); |
|||
}; |
|||
|
|||
template <class _Ratio, class _CharT> |
|||
basic_string<_CharT> |
|||
ratio_string<_Ratio, _CharT>::prefix() |
|||
{ |
|||
basic_ostringstream<_CharT> __os; |
|||
__os << _CharT('[') << _Ratio::num << _CharT('/') |
|||
<< _Ratio::den << _CharT(']'); |
|||
return __os.str(); |
|||
} |
|||
|
|||
// atto |
|||
|
|||
template <> |
|||
struct ratio_string<atto, char> |
|||
{ |
|||
static string symbol() {return string(1, 'a');} |
|||
static string prefix() {return string("atto");} |
|||
}; |
|||
|
|||
#if HAS_UNICODE_SUPPORT |
|||
|
|||
template <> |
|||
struct ratio_string<atto, char16_t> |
|||
{ |
|||
static u16string symbol() {return u16string(1, u'a');} |
|||
static u16string prefix() {return u16string(u"atto");} |
|||
}; |
|||
|
|||
template <> |
|||
struct ratio_string<atto, char32_t> |
|||
{ |
|||
static u32string symbol() {return u32string(1, U'a');} |
|||
static u32string prefix() {return u32string(U"atto");} |
|||
}; |
|||
|
|||
#endif |
|||
|
|||
template <> |
|||
struct ratio_string<atto, wchar_t> |
|||
{ |
|||
static wstring symbol() {return wstring(1, L'a');} |
|||
static wstring prefix() {return wstring(L"atto");} |
|||
}; |
|||
|
|||
// femto |
|||
|
|||
template <> |
|||
struct ratio_string<femto, char> |
|||
{ |
|||
static string symbol() {return string(1, 'f');} |
|||
static string prefix() {return string("femto");} |
|||
}; |
|||
|
|||
#if HAS_UNICODE_SUPPORT |
|||
|
|||
template <> |
|||
struct ratio_string<femto, char16_t> |
|||
{ |
|||
static u16string symbol() {return u16string(1, u'f');} |
|||
static u16string prefix() {return u16string(u"femto");} |
|||
}; |
|||
|
|||
template <> |
|||
struct ratio_string<femto, char32_t> |
|||
{ |
|||
static u32string symbol() {return u32string(1, U'f');} |
|||
static u32string prefix() {return u32string(U"femto");} |
|||
}; |
|||
|
|||
#endif |
|||
|
|||
template <> |
|||
struct ratio_string<femto, wchar_t> |
|||
{ |
|||
static wstring symbol() {return wstring(1, L'f');} |
|||
static wstring prefix() {return wstring(L"femto");} |
|||
}; |
|||
|
|||
// pico |
|||
|
|||
template <> |
|||
struct ratio_string<pico, char> |
|||
{ |
|||
static string symbol() {return string(1, 'p');} |
|||
static string prefix() {return string("pico");} |
|||
}; |
|||
|
|||
#if HAS_UNICODE_SUPPORT |
|||
|
|||
template <> |
|||
struct ratio_string<pico, char16_t> |
|||
{ |
|||
static u16string symbol() {return u16string(1, u'p');} |
|||
static u16string prefix() {return u16string(u"pico");} |
|||
}; |
|||
|
|||
template <> |
|||
struct ratio_string<pico, char32_t> |
|||
{ |
|||
static u32string symbol() {return u32string(1, U'p');} |
|||
static u32string prefix() {return u32string(U"pico");} |
|||
}; |
|||
|
|||
#endif |
|||
|
|||
template <> |
|||
struct ratio_string<pico, wchar_t> |
|||
{ |
|||
static wstring symbol() {return wstring(1, L'p');} |
|||
static wstring prefix() {return wstring(L"pico");} |
|||
}; |
|||
|
|||
// nano |
|||
|
|||
template <> |
|||
struct ratio_string<nano, char> |
|||
{ |
|||
static string symbol() {return string(1, 'n');} |
|||
static string prefix() {return string("nano");} |
|||
}; |
|||
|
|||
#if HAS_UNICODE_SUPPORT |
|||
|
|||
template <> |
|||
struct ratio_string<nano, char16_t> |
|||
{ |
|||
static u16string symbol() {return u16string(1, u'n');} |
|||
static u16string prefix() {return u16string(u"nano");} |
|||
}; |
|||
|
|||
template <> |
|||
struct ratio_string<nano, char32_t> |
|||
{ |
|||
static u32string symbol() {return u32string(1, U'n');} |
|||
static u32string prefix() {return u32string(U"nano");} |
|||
}; |
|||
|
|||
#endif |
|||
|
|||
template <> |
|||
struct ratio_string<nano, wchar_t> |
|||
{ |
|||
static wstring symbol() {return wstring(1, L'n');} |
|||
static wstring prefix() {return wstring(L"nano");} |
|||
}; |
|||
|
|||
// micro |
|||
|
|||
template <> |
|||
struct ratio_string<micro, char> |
|||
{ |
|||
static string symbol() {return string("\xC2\xB5");} |
|||
static string prefix() {return string("micro");} |
|||
}; |
|||
|
|||
#if HAS_UNICODE_SUPPORT |
|||
|
|||
template <> |
|||
struct ratio_string<micro, char16_t> |
|||
{ |
|||
static u16string symbol() {return u16string(1, u'\xB5');} |
|||
static u16string prefix() {return u16string(u"micro");} |
|||
}; |
|||
|
|||
template <> |
|||
struct ratio_string<micro, char32_t> |
|||
{ |
|||
static u32string symbol() {return u32string(1, U'\xB5');} |
|||
static u32string prefix() {return u32string(U"micro");} |
|||
}; |
|||
|
|||
#endif |
|||
|
|||
template <> |
|||
struct ratio_string<micro, wchar_t> |
|||
{ |
|||
static wstring symbol() {return wstring(1, L'\xB5');} |
|||
static wstring prefix() {return wstring(L"micro");} |
|||
}; |
|||
|
|||
// milli |
|||
|
|||
template <> |
|||
struct ratio_string<milli, char> |
|||
{ |
|||
static string symbol() {return string(1, 'm');} |
|||
static string prefix() {return string("milli");} |
|||
}; |
|||
|
|||
#if HAS_UNICODE_SUPPORT |
|||
|
|||
template <> |
|||
struct ratio_string<milli, char16_t> |
|||
{ |
|||
static u16string symbol() {return u16string(1, u'm');} |
|||
static u16string prefix() {return u16string(u"milli");} |
|||
}; |
|||
|
|||
template <> |
|||
struct ratio_string<milli, char32_t> |
|||
{ |
|||
static u32string symbol() {return u32string(1, U'm');} |
|||
static u32string prefix() {return u32string(U"milli");} |
|||
}; |
|||
|
|||
#endif |
|||
|
|||
template <> |
|||
struct ratio_string<milli, wchar_t> |
|||
{ |
|||
static wstring symbol() {return wstring(1, L'm');} |
|||
static wstring prefix() {return wstring(L"milli");} |
|||
}; |
|||
|
|||
// centi |
|||
|
|||
template <> |
|||
struct ratio_string<centi, char> |
|||
{ |
|||
static string symbol() {return string(1, 'c');} |
|||
static string prefix() {return string("centi");} |
|||
}; |
|||
|
|||
#if HAS_UNICODE_SUPPORT |
|||
|
|||
template <> |
|||
struct ratio_string<centi, char16_t> |
|||
{ |
|||
static u16string symbol() {return u16string(1, u'c');} |
|||
static u16string prefix() {return u16string(u"centi");} |
|||
}; |
|||
|
|||
template <> |
|||
struct ratio_string<centi, char32_t> |
|||
{ |
|||
static u32string symbol() {return u32string(1, U'c');} |
|||
static u32string prefix() {return u32string(U"centi");} |
|||
}; |
|||
|
|||
#endif |
|||
|
|||
template <> |
|||
struct ratio_string<centi, wchar_t> |
|||
{ |
|||
static wstring symbol() {return wstring(1, L'c');} |
|||
static wstring prefix() {return wstring(L"centi");} |
|||
}; |
|||
|
|||
// deci |
|||
|
|||
template <> |
|||
struct ratio_string<deci, char> |
|||
{ |
|||
static string symbol() {return string(1, 'd');} |
|||
static string prefix() {return string("deci");} |
|||
}; |
|||
|
|||
#if HAS_UNICODE_SUPPORT |
|||
|
|||
template <> |
|||
struct ratio_string<deci, char16_t> |
|||
{ |
|||
static u16string symbol() {return u16string(1, u'd');} |
|||
static u16string prefix() {return u16string(u"deci");} |
|||
}; |
|||
|
|||
template <> |
|||
struct ratio_string<deci, char32_t> |
|||
{ |
|||
static u32string symbol() {return u32string(1, U'd');} |
|||
static u32string prefix() {return u32string(U"deci");} |
|||
}; |
|||
|
|||
#endif |
|||
|
|||
template <> |
|||
struct ratio_string<deci, wchar_t> |
|||
{ |
|||
static wstring symbol() {return wstring(1, L'd');} |
|||
static wstring prefix() {return wstring(L"deci");} |
|||
}; |
|||
|
|||
// deca |
|||
|
|||
template <> |
|||
struct ratio_string<deca, char> |
|||
{ |
|||
static string symbol() {return string("da");} |
|||
static string prefix() {return string("deca");} |
|||
}; |
|||
|
|||
#if HAS_UNICODE_SUPPORT |
|||
|
|||
template <> |
|||
struct ratio_string<deca, char16_t> |
|||
{ |
|||
static u16string symbol() {return u16string(u"da");} |
|||
static u16string prefix() {return u16string(u"deca");} |
|||
}; |
|||
|
|||
template <> |
|||
struct ratio_string<deca, char32_t> |
|||
{ |
|||
static u32string symbol() {return u32string(U"da");} |
|||
static u32string prefix() {return u32string(U"deca");} |
|||
}; |
|||
|
|||
#endif |
|||
|
|||
template <> |
|||
struct ratio_string<deca, wchar_t> |
|||
{ |
|||
static wstring symbol() {return wstring(L"da");} |
|||
static wstring prefix() {return wstring(L"deca");} |
|||
}; |
|||
|
|||
// hecto |
|||
|
|||
template <> |
|||
struct ratio_string<hecto, char> |
|||
{ |
|||
static string symbol() {return string(1, 'h');} |
|||
static string prefix() {return string("hecto");} |
|||
}; |
|||
|
|||
#if HAS_UNICODE_SUPPORT |
|||
|
|||
template <> |
|||
struct ratio_string<hecto, char16_t> |
|||
{ |
|||
static u16string symbol() {return u16string(1, u'h');} |
|||
static u16string prefix() {return u16string(u"hecto");} |
|||
}; |
|||
|
|||
template <> |
|||
struct ratio_string<hecto, char32_t> |
|||
{ |
|||
static u32string symbol() {return u32string(1, U'h');} |
|||
static u32string prefix() {return u32string(U"hecto");} |
|||
}; |
|||
|
|||
#endif |
|||
|
|||
template <> |
|||
struct ratio_string<hecto, wchar_t> |
|||
{ |
|||
static wstring symbol() {return wstring(1, L'h');} |
|||
static wstring prefix() {return wstring(L"hecto");} |
|||
}; |
|||
|
|||
// kilo |
|||
|
|||
template <> |
|||
struct ratio_string<kilo, char> |
|||
{ |
|||
static string symbol() {return string(1, 'k');} |
|||
static string prefix() {return string("kilo");} |
|||
}; |
|||
|
|||
#if HAS_UNICODE_SUPPORT |
|||
|
|||
template <> |
|||
struct ratio_string<kilo, char16_t> |
|||
{ |
|||
static u16string symbol() {return u16string(1, u'k');} |
|||
static u16string prefix() {return u16string(u"kilo");} |
|||
}; |
|||
|
|||
template <> |
|||
struct ratio_string<kilo, char32_t> |
|||
{ |
|||
static u32string symbol() {return u32string(1, U'k');} |
|||
static u32string prefix() {return u32string(U"kilo");} |
|||
}; |
|||
|
|||
#endif |
|||
|
|||
template <> |
|||
struct ratio_string<kilo, wchar_t> |
|||
{ |
|||
static wstring symbol() {return wstring(1, L'k');} |
|||
static wstring prefix() {return wstring(L"kilo");} |
|||
}; |
|||
|
|||
// mega |
|||
|
|||
template <> |
|||
struct ratio_string<mega, char> |
|||
{ |
|||
static string symbol() {return string(1, 'M');} |
|||
static string prefix() {return string("mega");} |
|||
}; |
|||
|
|||
#if HAS_UNICODE_SUPPORT |
|||
|
|||
template <> |
|||
struct ratio_string<mega, char16_t> |
|||
{ |
|||
static u16string symbol() {return u16string(1, u'M');} |
|||
static u16string prefix() {return u16string(u"mega");} |
|||
}; |
|||
|
|||
template <> |
|||
struct ratio_string<mega, char32_t> |
|||
{ |
|||
static u32string symbol() {return u32string(1, U'M');} |
|||
static u32string prefix() {return u32string(U"mega");} |
|||
}; |
|||
|
|||
#endif |
|||
|
|||
template <> |
|||
struct ratio_string<mega, wchar_t> |
|||
{ |
|||
static wstring symbol() {return wstring(1, L'M');} |
|||
static wstring prefix() {return wstring(L"mega");} |
|||
}; |
|||
|
|||
// giga |
|||
|
|||
template <> |
|||
struct ratio_string<giga, char> |
|||
{ |
|||
static string symbol() {return string(1, 'G');} |
|||
static string prefix() {return string("giga");} |
|||
}; |
|||
|
|||
#if HAS_UNICODE_SUPPORT |
|||
|
|||
template <> |
|||
struct ratio_string<giga, char16_t> |
|||
{ |
|||
static u16string symbol() {return u16string(1, u'G');} |
|||
static u16string prefix() {return u16string(u"giga");} |
|||
}; |
|||
|
|||
template <> |
|||
struct ratio_string<giga, char32_t> |
|||
{ |
|||
static u32string symbol() {return u32string(1, U'G');} |
|||
static u32string prefix() {return u32string(U"giga");} |
|||
}; |
|||
|
|||
#endif |
|||
|
|||
template <> |
|||
struct ratio_string<giga, wchar_t> |
|||
{ |
|||
static wstring symbol() {return wstring(1, L'G');} |
|||
static wstring prefix() {return wstring(L"giga");} |
|||
}; |
|||
|
|||
// tera |
|||
|
|||
template <> |
|||
struct ratio_string<tera, char> |
|||
{ |
|||
static string symbol() {return string(1, 'T');} |
|||
static string prefix() {return string("tera");} |
|||
}; |
|||
|
|||
#if HAS_UNICODE_SUPPORT |
|||
|
|||
template <> |
|||
struct ratio_string<tera, char16_t> |
|||
{ |
|||
static u16string symbol() {return u16string(1, u'T');} |
|||
static u16string prefix() {return u16string(u"tera");} |
|||
}; |
|||
|
|||
template <> |
|||
struct ratio_string<tera, char32_t> |
|||
{ |
|||
static u32string symbol() {return u32string(1, U'T');} |
|||
static u32string prefix() {return u32string(U"tera");} |
|||
}; |
|||
|
|||
#endif |
|||
|
|||
template <> |
|||
struct ratio_string<tera, wchar_t> |
|||
{ |
|||
static wstring symbol() {return wstring(1, L'T');} |
|||
static wstring prefix() {return wstring(L"tera");} |
|||
}; |
|||
|
|||
// peta |
|||
|
|||
template <> |
|||
struct ratio_string<peta, char> |
|||
{ |
|||
static string symbol() {return string(1, 'P');} |
|||
static string prefix() {return string("peta");} |
|||
}; |
|||
|
|||
#if HAS_UNICODE_SUPPORT |
|||
|
|||
template <> |
|||
struct ratio_string<peta, char16_t> |
|||
{ |
|||
static u16string symbol() {return u16string(1, u'P');} |
|||
static u16string prefix() {return u16string(u"peta");} |
|||
}; |
|||
|
|||
template <> |
|||
struct ratio_string<peta, char32_t> |
|||
{ |
|||
static u32string symbol() {return u32string(1, U'P');} |
|||
static u32string prefix() {return u32string(U"peta");} |
|||
}; |
|||
|
|||
#endif |
|||
|
|||
template <> |
|||
struct ratio_string<peta, wchar_t> |
|||
{ |
|||
static wstring symbol() {return wstring(1, L'P');} |
|||
static wstring prefix() {return wstring(L"peta");} |
|||
}; |
|||
|
|||
// exa |
|||
|
|||
template <> |
|||
struct ratio_string<exa, char> |
|||
{ |
|||
static string symbol() {return string(1, 'E');} |
|||
static string prefix() {return string("exa");} |
|||
}; |
|||
|
|||
#if HAS_UNICODE_SUPPORT |
|||
|
|||
template <> |
|||
struct ratio_string<exa, char16_t> |
|||
{ |
|||
static u16string symbol() {return u16string(1, u'E');} |
|||
static u16string prefix() {return u16string(u"exa");} |
|||
}; |
|||
|
|||
template <> |
|||
struct ratio_string<exa, char32_t> |
|||
{ |
|||
static u32string symbol() {return u32string(1, U'E');} |
|||
static u32string prefix() {return u32string(U"exa");} |
|||
}; |
|||
|
|||
#endif |
|||
|
|||
template <> |
|||
struct ratio_string<exa, wchar_t> |
|||
{ |
|||
static wstring symbol() {return wstring(1, L'E');} |
|||
static wstring prefix() {return wstring(L"exa");} |
|||
}; |
|||
|
|||
} |
|||
|
|||
#endif // _RATIO_IO |
@ -1,102 +0,0 @@ |
|||
|
|||
/** |
|||
* This plugin exposes 'evaluateExpression' method which should be used |
|||
* to evaluate natspec description |
|||
*/ |
|||
|
|||
/// Object which should be used by NatspecExpressionEvaluator
|
|||
/// abi - abi of the contract that will be used
|
|||
/// method - name of the method that is called
|
|||
/// params - input params of the method that will be called
|
|||
var globals = { |
|||
abi: [], |
|||
method: "", |
|||
params: [] |
|||
}; |
|||
|
|||
/// Helper method
|
|||
/// Should be called to copy values from object to global context
|
|||
var copyToContext = function (obj, context) { |
|||
var keys = Object.keys(obj); |
|||
keys.forEach(function (key) { |
|||
context[key] = obj[key]; |
|||
}); |
|||
} |
|||
|
|||
/// Helper method
|
|||
/// Should be called to get method with given name from the abi
|
|||
/// @param contract's abi
|
|||
/// @param name of the method that we are looking for
|
|||
var getMethodWithName = function(abi, name) { |
|||
for (var i = 0; i < abi.length; i++) { |
|||
if (abi[i].name === name) { |
|||
return abi[i]; |
|||
} |
|||
} |
|||
//console.warn('could not find method with name: ' + name);
|
|||
return undefined; |
|||
}; |
|||
|
|||
/// Function called to get all contract's storage values
|
|||
/// @returns hashmap with contract properties which are used
|
|||
/// TODO: check if this function will be used
|
|||
var getContractProperties = function (address, abi) { |
|||
return {}; |
|||
}; |
|||
|
|||
/// Function called to get all contract's methods
|
|||
/// @returns hashmap with used contract's methods
|
|||
/// TODO: check if this function will be used
|
|||
var getContractMethods = function (address, abi) { |
|||
//return web3.eth.contract(address, abi); // commented out web3 usage
|
|||
return {}; |
|||
}; |
|||
|
|||
/// Function called to get all contract method input variables
|
|||
/// @returns hashmap with all contract's method input variables
|
|||
var getMethodInputParams = function (method, params) { |
|||
return method.inputs.reduce(function (acc, current, index) { |
|||
acc[current.name] = params[index]; |
|||
return acc; |
|||
}, {}); |
|||
}; |
|||
|
|||
/// Should be called to evaluate single expression
|
|||
/// Is internally using javascript's 'eval' method
|
|||
/// Should be checked if it is safe
|
|||
var evaluateExpression = function (expression) { |
|||
|
|||
var self = this; |
|||
|
|||
//var storage = getContractProperties(address, abi);
|
|||
//var methods = getContractMethods(address, abi);
|
|||
|
|||
var method = getMethodWithName(globals.abi, globals.method); |
|||
if (method) { |
|||
var input = getMethodInputParams(method, globals.params); |
|||
copyToContext(input, self); |
|||
} |
|||
|
|||
// TODO: test if it is safe
|
|||
var evaluatedExpression = ""; |
|||
|
|||
// match everything in `` quotes
|
|||
var pattern = /\`(?:\\.|[^`\\])*\`/gim |
|||
var match; |
|||
var lastIndex = 0; |
|||
while ((match = pattern.exec(expression)) !== null) { |
|||
var startIndex = pattern.lastIndex - match[0].length; |
|||
|
|||
var toEval = match[0].slice(1, match[0].length - 1); |
|||
|
|||
evaluatedExpression += expression.slice(lastIndex, startIndex); |
|||
evaluatedExpression += eval(toEval).toString(); |
|||
|
|||
lastIndex = pattern.lastIndex; |
|||
} |
|||
|
|||
evaluatedExpression += expression.slice(lastIndex); |
|||
|
|||
return evaluatedExpression; |
|||
}; |
|||
|
@ -1,5 +1,5 @@ |
|||
<RCC> |
|||
<qresource prefix="/natspec"> |
|||
<file>natspec.js</file> |
|||
<file alias="natspec.js">natspecjs/dist/natspec.min.js</file> |
|||
</qresource> |
|||
</RCC> |
|||
|
@ -0,0 +1,3 @@ |
|||
# VIM stuff |
|||
*.swp |
|||
node_modules/ |
@ -0,0 +1,8 @@ |
|||
language: node_js |
|||
node_js: |
|||
- "0.11" |
|||
- "0.10" |
|||
before_script: |
|||
- npm install |
|||
after_script: |
|||
- npm run-script test-coveralls |
@ -0,0 +1,47 @@ |
|||
# natspec.js |
|||
Javascript Library used to evaluate natspec expressions |
|||
|
|||
[![Build Status][travis-image]][travis-url] [![Coverage Status][coveralls-image]][coveralls-url] |
|||
|
|||
[travis-image]: https://travis-ci.org/ethereum/natspec.js.svg |
|||
[travis-url]: https://travis-ci.org/ethereum/natspec.js |
|||
[coveralls-image]: https://coveralls.io/repos/ethereum/natspec.js/badge.svg?branch=master |
|||
[coveralls-url]: https://coveralls.io/r/ethereum/natspec.js?branch=master |
|||
|
|||
## Usage |
|||
|
|||
It exposes global object `natspec` with method `evaluateExpression`. |
|||
|
|||
```javascript |
|||
var natspec = require('natspec'); |
|||
|
|||
var natspecExpression = "Will multiply `a` by 7 and return `a * 7`."; |
|||
var call = { |
|||
method: 'multiply', |
|||
abi: abi, |
|||
transaction: transaction |
|||
}; |
|||
|
|||
var evaluatedExpression = natspec.evaluateExpression(natspecExpression, call); |
|||
console.log(evaluatedExpression); // "Will multiply 4 by 7 and return 28." |
|||
``` |
|||
|
|||
More examples are available [here](https://github.com/ethereum/natspec.js/blob/master/test/test.js). |
|||
|
|||
## Building |
|||
|
|||
```bash |
|||
npm run-script build |
|||
``` |
|||
|
|||
## Testing (mocha) |
|||
|
|||
```bash |
|||
npm test |
|||
``` |
|||
|
|||
## Wiki |
|||
|
|||
* [Ethereum Natural Specification Format](https://github.com/ethereum/wiki/wiki/Ethereum-Natural-Specification-Format) |
|||
* [Natspec Example](https://github.com/ethereum/wiki/wiki/Natspec-Example) |
|||
|
File diff suppressed because it is too large
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,13 @@ |
|||
<!doctype> |
|||
<html> |
|||
|
|||
<head> |
|||
<script type="text/javascript" src="../dist/natspec.js"></script> |
|||
<script type="text/javascript"> |
|||
var natspec = require('natspec'); |
|||
</script> |
|||
</head> |
|||
<body> |
|||
</body> |
|||
</html> |
|||
|
@ -0,0 +1,186 @@ |
|||
/* |
|||
This file is part of natspec.js. |
|||
|
|||
natspec.js is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU Lesser General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
natspec.js is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU Lesser General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU Lesser General Public License |
|||
along with natspec.js. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file natspec.js |
|||
* @authors: |
|||
* Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
var abi = require('./node_modules/ethereum.js/lib/abi.js'); |
|||
|
|||
/** |
|||
* This object should be used to evaluate natspec expression |
|||
* It has one method evaluateExpression which shoul be used |
|||
*/ |
|||
var natspec = (function () { |
|||
/** |
|||
* Helper method |
|||
* Should be called to copy values from object to global context |
|||
* |
|||
* @method copyToContext |
|||
* @param {Object} object from which we want to copy properties |
|||
* @param {Object} object to which we copy |
|||
*/ |
|||
var copyToContext = function (obj, context) { |
|||
Object.keys(obj).forEach(function (key) { |
|||
context[key] = obj[key]; |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Should be used to generate codes, which will be evaluated |
|||
* |
|||
* @method generateCode |
|||
* @param {Object} object from which code will be generated |
|||
* @return {String} javascript code which is used to initalized variables |
|||
*/ |
|||
var generateCode = function (obj) { |
|||
return Object.keys(obj).reduce(function (acc, key) { |
|||
return acc + "var " + key + " = context['" + key + "'];\n"; |
|||
}, ""); |
|||
}; |
|||
|
|||
/** |
|||
* Helper method |
|||
* Should be called to get method with given name from the abi |
|||
* |
|||
* @method getMethodWithName |
|||
* @param {Array} contract's abi |
|||
* @param {String} name of the method that we are looking for |
|||
* @return {Object} abi for method with name |
|||
*/ |
|||
var getMethodWithName = function(abi, name) { |
|||
return abi.filter(function (method) { |
|||
return method.name === name; |
|||
})[0]; |
|||
}; |
|||
|
|||
/** |
|||
* Should be used to get all contract method input variables |
|||
* |
|||
* @method getMethodInputParams |
|||
* @param {Object} abi for certain method |
|||
* @param {Object} transaction object |
|||
* @return {Object} object with all contract's method input variables |
|||
*/ |
|||
var getMethodInputParams = function (method, transaction) { |
|||
// do it with output formatter (cause we have to decode)
|
|||
var params = abi.formatOutput(method.inputs, '0x' + transaction.params[0].data.slice(10)); |
|||
|
|||
return method.inputs.reduce(function (acc, current, index) { |
|||
acc[current.name] = params[index]; |
|||
return acc; |
|||
}, {}); |
|||
}; |
|||
|
|||
/** |
|||
* Should be called when we want to evaluate natspec expression |
|||
* Replaces all natspec 'subexpressions' with evaluated value |
|||
* |
|||
* @method mapExpressionToEvaluate |
|||
* @param {String} expression to evaluate |
|||
* @param {Function} callback which is called to evaluate te expression |
|||
* @return {String} evaluated expression |
|||
*/ |
|||
var mapExpressionsToEvaluate = function (expression, cb) { |
|||
var evaluatedExpression = ""; |
|||
|
|||
// match everything in `` quotes
|
|||
var pattern = /\`(?:\\.|[^`\\])*\`/gim |
|||
var match; |
|||
var lastIndex = 0; |
|||
try { |
|||
while ((match = pattern.exec(expression)) !== null) { |
|||
var startIndex = pattern.lastIndex - match[0].length; |
|||
var toEval = match[0].slice(1, match[0].length - 1); |
|||
evaluatedExpression += expression.slice(lastIndex, startIndex); |
|||
var evaluatedPart = cb(toEval); |
|||
evaluatedExpression += evaluatedPart; |
|||
lastIndex = pattern.lastIndex; |
|||
} |
|||
|
|||
evaluatedExpression += expression.slice(lastIndex); |
|||
} |
|||
catch (err) { |
|||
throw new Error("Natspec evaluation failed, wrong input params"); |
|||
} |
|||
|
|||
return evaluatedExpression; |
|||
}; |
|||
|
|||
/** |
|||
* Should be called to evaluate single expression |
|||
* Is internally using javascript's 'eval' method |
|||
* |
|||
* @method evaluateExpression |
|||
* @param {String} expression which should be evaluated |
|||
* @param {Object} [call] object containing contract abi, transaction, called method |
|||
* @return {String} evaluated expression |
|||
* @throws exception if method is not found or we are trying to evaluate input params that does not exists |
|||
*/ |
|||
var evaluateExpression = function (expression, call) { |
|||
//var self = this;
|
|||
var context = {}; |
|||
|
|||
if (!!call) { |
|||
try { |
|||
var method = getMethodWithName(call.abi, call.method); |
|||
var params = getMethodInputParams(method, call.transaction); |
|||
copyToContext(params, context); |
|||
} |
|||
catch (err) { |
|||
throw new Error("Natspec evaluation failed, method does not exist"); |
|||
} |
|||
} |
|||
|
|||
var code = generateCode(context); |
|||
|
|||
var evaluatedExpression = mapExpressionsToEvaluate(expression, function (toEval) { |
|||
var fn = new Function("context", code + "return " + toEval + ";"); |
|||
return fn(context).toString(); |
|||
}); |
|||
|
|||
return evaluatedExpression; |
|||
}; |
|||
|
|||
/** |
|||
* Safe version of evaluateExpression |
|||
* Instead of throwing an exception it returns it as a string |
|||
* |
|||
* @method evaluateExpressionSafe |
|||
* @param {String} expression which should be evaluated |
|||
* @param {Object} [call] object containing contract abi, transaction, called method |
|||
* @return {String} evaluated expression |
|||
*/ |
|||
var evaluateExpressionSafe = function (expression, call) { |
|||
try { |
|||
return evaluateExpression(expression, call); |
|||
} |
|||
catch (err) { |
|||
return err.message; |
|||
} |
|||
}; |
|||
|
|||
return { |
|||
evaluateExpression: evaluateExpression, |
|||
evaluateExpressionSafe: evaluateExpressionSafe |
|||
}; |
|||
|
|||
})(); |
|||
|
|||
module.exports = natspec; |
|||
|
@ -0,0 +1,24 @@ |
|||
{ |
|||
"name": "natspec.js", |
|||
"version": "0.0.1", |
|||
"description": "Javascript Library used to evaluate natspec expressions", |
|||
"main": "natspec.js", |
|||
"scripts": { |
|||
"build": "cd dist && browserify -r ../natspec.js:natspec -i crypto -o natspec.js && uglifyjs natspec.js --source-map natspec.js.map -c -m -o natspec.min.js", |
|||
"test": "mocha", |
|||
"test-coveralls": "istanbul cover _mocha -- -R spec && cat coverage/lcov.info | coveralls --verbose" |
|||
}, |
|||
"author": "", |
|||
"dependencies": { |
|||
"ethereum.js": "ethereum/ethereum.js#master" |
|||
}, |
|||
"devDependencies": { |
|||
"browserify": "^9.0.3", |
|||
"chai": "^2.1.0", |
|||
"coveralls": "^2.11.2", |
|||
"istanbul": "^0.3.6", |
|||
"mocha": "^2.1.0", |
|||
"uglify-js": "^2.4.16" |
|||
}, |
|||
"license": "LGPL-3.0" |
|||
} |
@ -0,0 +1,149 @@ |
|||
var chai = require('chai'); |
|||
var natspec = require('../natspec.js'); |
|||
var assert = chai.assert; |
|||
|
|||
describe('natspec', function () { |
|||
it('should evaluate simple expression', function () { |
|||
// given
|
|||
var expression = "`x = 1` + `y = 2` will be equal `x + y`"; |
|||
|
|||
// when
|
|||
var result = natspec.evaluateExpression(expression); |
|||
var result2 = natspec.evaluateExpressionSafe(expression); |
|||
|
|||
// then
|
|||
assert.equal(result, "1 + 2 will be equal 3"); |
|||
assert.equal(result2, "1 + 2 will be equal 3"); |
|||
}); |
|||
|
|||
it('should evalute expression using input params', function () { |
|||
//given
|
|||
var expression = "Will multiply `a` by 7 and return `a * 7`."; |
|||
var method = 'multiply'; |
|||
var abi = [{ |
|||
"name": "multiply", |
|||
"constant": false, |
|||
"type": "function", |
|||
"inputs": [{ |
|||
"name": "a", |
|||
"type": "uint256" |
|||
}], |
|||
"outputs": [{ |
|||
"name": "d", |
|||
"type": "uint256" |
|||
}] |
|||
}]; |
|||
|
|||
var transaction = { |
|||
"jsonrpc": "2.0", |
|||
"method": "eth_call", |
|||
"params": [{ |
|||
"to": "0x8521742d3f456bd237e312d6e30724960f72517a", |
|||
"data": "0xc6888fa1000000000000000000000000000000000000000000000000000000000000007a" |
|||
}], |
|||
"id": 6 |
|||
}; |
|||
|
|||
var call = { |
|||
method: method, |
|||
abi: abi, |
|||
transaction: transaction |
|||
}; |
|||
|
|||
// when
|
|||
var result = natspec.evaluateExpression(expression, call); |
|||
var result2 = natspec.evaluateExpressionSafe(expression, call); |
|||
|
|||
// then
|
|||
assert.equal(result, "Will multiply 122 by 7 and return 854."); |
|||
assert.equal(result2, "Will multiply 122 by 7 and return 854."); |
|||
}); |
|||
|
|||
it('should evalute expression using input params', function () { |
|||
//given
|
|||
var expression = "Will multiply `a` by 7 and return `a * 7`."; |
|||
var method = 'multiply'; |
|||
var abi = [{ |
|||
"name": "multiply", |
|||
"constant": false, |
|||
"type": "function", |
|||
"inputs": [{ |
|||
"name": "b", |
|||
"type": "uint256" |
|||
}], |
|||
"outputs": [{ |
|||
"name": "d", |
|||
"type": "uint256" |
|||
}] |
|||
}]; |
|||
|
|||
var transaction = { |
|||
"jsonrpc": "2.0", |
|||
"method": "eth_call", |
|||
"params": [{ |
|||
"to": "0x8521742d3f456bd237e312d6e30724960f72517a", |
|||
"data": "0xc6888fa1000000000000000000000000000000000000000000000000000000000000007a" |
|||
}], |
|||
"id": 6 |
|||
}; |
|||
|
|||
var call = { |
|||
method: method, |
|||
abi: abi, |
|||
transaction: transaction |
|||
}; |
|||
|
|||
// when
|
|||
var exceptionThrow = function () { natspec.evaluateExpression(expression, call);} |
|||
var result = natspec.evaluateExpressionSafe(expression, call); |
|||
|
|||
// then
|
|||
assert.equal(result, "Natspec evaluation failed, wrong input params"); |
|||
assert.throws(exceptionThrow, "Natspec evaluation failed, wrong input params"); |
|||
}); |
|||
|
|||
it('should evalute expression using input params', function () { |
|||
//given
|
|||
var expression = "Will multiply `a` by 7 and return `a * 7`."; |
|||
var method = 'multiply2'; |
|||
var abi = [{ |
|||
"name": "multiply", |
|||
"constant": false, |
|||
"type": "function", |
|||
"inputs": [{ |
|||
"name": "a", |
|||
"type": "uint256" |
|||
}], |
|||
"outputs": [{ |
|||
"name": "d", |
|||
"type": "uint256" |
|||
}] |
|||
}]; |
|||
|
|||
var transaction = { |
|||
"jsonrpc": "2.0", |
|||
"method": "eth_call", |
|||
"params": [{ |
|||
"to": "0x8521742d3f456bd237e312d6e30724960f72517a", |
|||
"data": "0xc6888fa1000000000000000000000000000000000000000000000000000000000000007a" |
|||
}], |
|||
"id": 6 |
|||
}; |
|||
|
|||
var call = { |
|||
method: method, |
|||
abi: abi, |
|||
transaction: transaction |
|||
}; |
|||
|
|||
// when
|
|||
var exceptionThrow = function () { natspec.evaluateExpression(expression, call);} |
|||
var result = natspec.evaluateExpressionSafe(expression, call); |
|||
|
|||
// then
|
|||
assert.equal(result, "Natspec evaluation failed, method does not exist"); |
|||
assert.throws(exceptionThrow, "Natspec evaluation failed, method does not exist"); |
|||
}); |
|||
}); |
|||
|
|||
|
@ -0,0 +1,209 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file RLPXFrameIO.cpp
|
|||
* @author Alex Leverington <nessence@gmail.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include "Host.h" |
|||
#include "Session.h" |
|||
#include "Peer.h" |
|||
#include "RLPxHandshake.h" |
|||
#include "RLPxFrameIO.h" |
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::p2p; |
|||
using namespace CryptoPP; |
|||
|
|||
RLPXFrameIO::RLPXFrameIO(RLPXHandshake const& _init): m_socket(_init.m_socket) |
|||
{ |
|||
// we need:
|
|||
// originated?
|
|||
// Secret == output of ecdhe agreement
|
|||
// authCipher
|
|||
// ackCipher
|
|||
|
|||
bytes keyMaterialBytes(64); |
|||
bytesRef keyMaterial(&keyMaterialBytes); |
|||
|
|||
// shared-secret = sha3(ecdhe-shared-secret || sha3(nonce || initiator-nonce))
|
|||
Secret ephemeralShared; |
|||
_init.m_ecdhe.agree(_init.m_remoteEphemeral, ephemeralShared); |
|||
ephemeralShared.ref().copyTo(keyMaterial.cropped(0, h256::size)); |
|||
h512 nonceMaterial; |
|||
h256 const& leftNonce = _init.m_originated ? _init.m_remoteNonce : _init.m_nonce; |
|||
h256 const& rightNonce = _init.m_originated ? _init.m_nonce : _init.m_remoteNonce; |
|||
leftNonce.ref().copyTo(nonceMaterial.ref().cropped(0, h256::size)); |
|||
rightNonce.ref().copyTo(nonceMaterial.ref().cropped(h256::size, h256::size)); |
|||
auto outRef(keyMaterial.cropped(h256::size, h256::size)); |
|||
sha3(nonceMaterial.ref(), outRef); // output h(nonces)
|
|||
|
|||
sha3(keyMaterial, outRef); // output shared-secret
|
|||
// token: sha3(outRef, bytesRef(&token)); -> m_host (to be saved to disk)
|
|||
|
|||
// aes-secret = sha3(ecdhe-shared-secret || shared-secret)
|
|||
sha3(keyMaterial, outRef); // output aes-secret
|
|||
m_frameEncKey.resize(h256::size); |
|||
memcpy(m_frameEncKey.data(), outRef.data(), h256::size); |
|||
m_frameDecKey.resize(h256::size); |
|||
memcpy(m_frameDecKey.data(), outRef.data(), h256::size); |
|||
h128 iv; |
|||
m_frameEnc.SetKeyWithIV(m_frameEncKey, h256::size, iv.data()); |
|||
m_frameDec.SetKeyWithIV(m_frameDecKey, h256::size, iv.data()); |
|||
|
|||
// mac-secret = sha3(ecdhe-shared-secret || aes-secret)
|
|||
sha3(keyMaterial, outRef); // output mac-secret
|
|||
m_macEncKey.resize(h256::size); |
|||
memcpy(m_macEncKey.data(), outRef.data(), h256::size); |
|||
m_macEnc.SetKey(m_macEncKey, h256::size); |
|||
|
|||
// Initiator egress-mac: sha3(mac-secret^recipient-nonce || auth-sent-init)
|
|||
// ingress-mac: sha3(mac-secret^initiator-nonce || auth-recvd-ack)
|
|||
// Recipient egress-mac: sha3(mac-secret^initiator-nonce || auth-sent-ack)
|
|||
// ingress-mac: sha3(mac-secret^recipient-nonce || auth-recvd-init)
|
|||
|
|||
(*(h256*)outRef.data() ^ _init.m_remoteNonce).ref().copyTo(keyMaterial); |
|||
bytes const& egressCipher = _init.m_originated ? _init.m_authCipher : _init.m_ackCipher; |
|||
keyMaterialBytes.resize(h256::size + egressCipher.size()); |
|||
keyMaterial.retarget(keyMaterialBytes.data(), keyMaterialBytes.size()); |
|||
bytesConstRef(&egressCipher).copyTo(keyMaterial.cropped(h256::size, egressCipher.size())); |
|||
m_egressMac.Update(keyMaterial.data(), keyMaterial.size()); |
|||
|
|||
// recover mac-secret by re-xoring remoteNonce
|
|||
(*(h256*)keyMaterial.data() ^ _init.m_remoteNonce ^ _init.m_nonce).ref().copyTo(keyMaterial); |
|||
bytes const& ingressCipher = _init.m_originated ? _init.m_ackCipher : _init.m_authCipher; |
|||
keyMaterialBytes.resize(h256::size + ingressCipher.size()); |
|||
keyMaterial.retarget(keyMaterialBytes.data(), keyMaterialBytes.size()); |
|||
bytesConstRef(&ingressCipher).copyTo(keyMaterial.cropped(h256::size, ingressCipher.size())); |
|||
m_ingressMac.Update(keyMaterial.data(), keyMaterial.size()); |
|||
} |
|||
|
|||
void RLPXFrameIO::writeSingleFramePacket(bytesConstRef _packet, bytes& o_bytes) |
|||
{ |
|||
// _packet = type || rlpList()
|
|||
|
|||
RLPStream header; |
|||
uint32_t len = (uint32_t)_packet.size(); |
|||
header.appendRaw(bytes({byte((len >> 16) & 0xff), byte((len >> 8) & 0xff), byte(len & 0xff)})); |
|||
// zeroHeader: []byte{0xC2, 0x80, 0x80}. Should be rlpList(protocolType,seqId,totalPacketSize).
|
|||
header.appendRaw(bytes({0xc2,0x80,0x80})); |
|||
|
|||
// TODO: SECURITY check that header is <= 16 bytes
|
|||
|
|||
bytes headerWithMac(h256::size); |
|||
bytesConstRef(&header.out()).copyTo(bytesRef(&headerWithMac)); |
|||
m_frameEnc.ProcessData(headerWithMac.data(), headerWithMac.data(), 16); |
|||
updateEgressMACWithHeader(bytesConstRef(&headerWithMac).cropped(0, 16)); |
|||
egressDigest().ref().copyTo(bytesRef(&headerWithMac).cropped(h128::size,h128::size)); |
|||
|
|||
auto padding = (16 - (_packet.size() % 16)) % 16; |
|||
o_bytes.swap(headerWithMac); |
|||
o_bytes.resize(32 + _packet.size() + padding + h128::size); |
|||
bytesRef packetRef(o_bytes.data() + 32, _packet.size()); |
|||
m_frameEnc.ProcessData(packetRef.data(), _packet.data(), _packet.size()); |
|||
bytesRef paddingRef(o_bytes.data() + 32 + _packet.size(), padding); |
|||
if (padding) |
|||
m_frameEnc.ProcessData(paddingRef.data(), paddingRef.data(), padding); |
|||
bytesRef packetWithPaddingRef(o_bytes.data() + 32, _packet.size() + padding); |
|||
updateEgressMACWithFrame(packetWithPaddingRef); |
|||
bytesRef macRef(o_bytes.data() + 32 + _packet.size() + padding, h128::size); |
|||
egressDigest().ref().copyTo(macRef); |
|||
} |
|||
|
|||
bool RLPXFrameIO::authAndDecryptHeader(bytesRef io) |
|||
{ |
|||
asserts(io.size() == h256::size); |
|||
updateIngressMACWithHeader(io); |
|||
bytesConstRef macRef = io.cropped(h128::size, h128::size); |
|||
h128 expected = ingressDigest(); |
|||
if (*(h128*)macRef.data() != expected) |
|||
return false; |
|||
m_frameDec.ProcessData(io.data(), io.data(), h128::size); |
|||
return true; |
|||
} |
|||
|
|||
bool RLPXFrameIO::authAndDecryptFrame(bytesRef io) |
|||
{ |
|||
bytesRef cipherText(io.cropped(0, io.size() - h128::size)); |
|||
updateIngressMACWithFrame(cipherText); |
|||
bytesConstRef frameMac(io.data() + io.size() - h128::size, h128::size); |
|||
if (*(h128*)frameMac.data() != ingressDigest()) |
|||
return false; |
|||
m_frameDec.ProcessData(io.data(), io.data(), io.size() - h128::size); |
|||
return true; |
|||
} |
|||
|
|||
h128 RLPXFrameIO::egressDigest() |
|||
{ |
|||
SHA3_256 h(m_egressMac); |
|||
h128 digest; |
|||
h.TruncatedFinal(digest.data(), h128::size); |
|||
return move(digest); |
|||
} |
|||
|
|||
h128 RLPXFrameIO::ingressDigest() |
|||
{ |
|||
SHA3_256 h(m_ingressMac); |
|||
h128 digest; |
|||
h.TruncatedFinal(digest.data(), h128::size); |
|||
return move(digest); |
|||
} |
|||
|
|||
void RLPXFrameIO::updateEgressMACWithHeader(bytesConstRef _headerCipher) |
|||
{ |
|||
updateMAC(m_egressMac, _headerCipher.cropped(0, 16)); |
|||
} |
|||
|
|||
void RLPXFrameIO::updateEgressMACWithFrame(bytesConstRef _cipher) |
|||
{ |
|||
m_egressMac.Update(_cipher.data(), _cipher.size()); |
|||
updateMAC(m_egressMac); |
|||
} |
|||
|
|||
void RLPXFrameIO::updateIngressMACWithHeader(bytesConstRef _headerCipher) |
|||
{ |
|||
updateMAC(m_ingressMac, _headerCipher.cropped(0, 16)); |
|||
} |
|||
|
|||
void RLPXFrameIO::updateIngressMACWithFrame(bytesConstRef _cipher) |
|||
{ |
|||
m_ingressMac.Update(_cipher.data(), _cipher.size()); |
|||
updateMAC(m_ingressMac); |
|||
} |
|||
|
|||
void RLPXFrameIO::updateMAC(SHA3_256& _mac, bytesConstRef _seed) |
|||
{ |
|||
if (_seed.size() && _seed.size() != h128::size) |
|||
asserts(false); |
|||
|
|||
SHA3_256 prevDigest(_mac); |
|||
h128 encDigest(h128::size); |
|||
prevDigest.TruncatedFinal(encDigest.data(), h128::size); |
|||
h128 prevDigestOut = encDigest; |
|||
|
|||
{ |
|||
Guard l(x_macEnc); |
|||
m_macEnc.ProcessData(encDigest.data(), encDigest.data(), 16); |
|||
} |
|||
if (_seed.size()) |
|||
encDigest ^= *(h128*)_seed.data(); |
|||
else |
|||
encDigest ^= *(h128*)prevDigestOut.data(); |
|||
|
|||
// update mac for final digest
|
|||
_mac.Update(encDigest.data(), h128::size); |
|||
} |
@ -0,0 +1,133 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file RLPXFrameIO.h
|
|||
* @author Alex Leverington <nessence@gmail.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
|
|||
#pragma once |
|||
|
|||
#include <memory> |
|||
#include <libdevcrypto/Common.h> |
|||
#include <libdevcrypto/ECDHE.h> |
|||
#include <libdevcrypto/CryptoPP.h> |
|||
#include <libdevcore/Guards.h> |
|||
#include "Common.h" |
|||
namespace ba = boost::asio; |
|||
namespace bi = boost::asio::ip; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace p2p |
|||
{ |
|||
|
|||
class RLPXHandshake; |
|||
|
|||
/**
|
|||
* @brief Encoder/decoder transport for RLPx connections established by RLPXHandshake. |
|||
* Managed (via shared_ptr) socket for use by RLPXHandshake and RLPXFrameIO. |
|||
* |
|||
* Thread Safety |
|||
* Distinct Objects: Safe. |
|||
* Shared objects: Unsafe. |
|||
* * an instance method must not be called concurrently |
|||
* * a writeSingleFramePacket can be called concurrent to authAndDecryptHeader OR authAndDecryptFrame |
|||
*/ |
|||
class RLPXSocket: public std::enable_shared_from_this<RLPXSocket> |
|||
{ |
|||
public: |
|||
RLPXSocket(bi::tcp::socket* _socket): m_socket(std::move(*_socket)) {} |
|||
~RLPXSocket() { close(); } |
|||
|
|||
bool isConnected() const { return m_socket.is_open(); } |
|||
void close() { try { boost::system::error_code ec; m_socket.shutdown(bi::tcp::socket::shutdown_both, ec); if (m_socket.is_open()) m_socket.close(); } catch (...){} } |
|||
bi::tcp::endpoint remoteEndpoint() { try { return m_socket.remote_endpoint(); } catch (...){ return bi::tcp::endpoint(); } } |
|||
bi::tcp::socket& ref() { return m_socket; } |
|||
|
|||
protected: |
|||
bi::tcp::socket m_socket; |
|||
}; |
|||
|
|||
/**
|
|||
* @brief Encoder/decoder transport for RLPx connections established by RLPXHandshake. |
|||
* |
|||
* Thread Safety |
|||
* Distinct Objects: Safe. |
|||
* Shared objects: Unsafe. |
|||
*/ |
|||
class RLPXFrameIO |
|||
{ |
|||
friend class Session; |
|||
public: |
|||
/// Constructor.
|
|||
/// Requires instance of RLPXHandshake which has completed first two phases of handshake.
|
|||
RLPXFrameIO(RLPXHandshake const& _init); |
|||
~RLPXFrameIO() {} |
|||
|
|||
/// Encrypt _packet as RLPx frame.
|
|||
void writeSingleFramePacket(bytesConstRef _packet, bytes& o_bytes); |
|||
|
|||
/// Authenticate and decrypt header in-place.
|
|||
bool authAndDecryptHeader(bytesRef io_cipherWithMac); |
|||
|
|||
/// Authenticate and decrypt frame in-place.
|
|||
bool authAndDecryptFrame(bytesRef io_cipherWithMac); |
|||
|
|||
/// Return first 16 bytes of current digest from egress mac.
|
|||
h128 egressDigest(); |
|||
|
|||
/// Return first 16 bytes of current digest from ingress mac.
|
|||
h128 ingressDigest(); |
|||
|
|||
protected: |
|||
/// Update state of egress MAC with frame header.
|
|||
void updateEgressMACWithHeader(bytesConstRef _headerCipher); |
|||
|
|||
/// Update state of egress MAC with frame.
|
|||
void updateEgressMACWithFrame(bytesConstRef _cipher); |
|||
|
|||
/// Update state of ingress MAC with frame header.
|
|||
void updateIngressMACWithHeader(bytesConstRef _headerCipher); |
|||
|
|||
/// Update state of ingress MAC with frame.
|
|||
void updateIngressMACWithFrame(bytesConstRef _cipher); |
|||
|
|||
bi::tcp::socket& socket() { return m_socket->ref(); } |
|||
|
|||
private: |
|||
/// Update state of _mac.
|
|||
void updateMAC(CryptoPP::SHA3_256& _mac, bytesConstRef _seed = bytesConstRef()); |
|||
|
|||
CryptoPP::SecByteBlock m_frameEncKey; ///< Key for m_frameEnc
|
|||
CryptoPP::CTR_Mode<CryptoPP::AES>::Encryption m_frameEnc; ///< Encoder for egress plaintext.
|
|||
|
|||
CryptoPP::SecByteBlock m_frameDecKey; ///< Key for m_frameDec
|
|||
CryptoPP::CTR_Mode<CryptoPP::AES>::Encryption m_frameDec; ///< Decoder for egress plaintext.
|
|||
|
|||
CryptoPP::SecByteBlock m_macEncKey; /// Key for m_macEnd
|
|||
CryptoPP::ECB_Mode<CryptoPP::AES>::Encryption m_macEnc; /// One-way coder used by updateMAC for ingress and egress MAC updates.
|
|||
Mutex x_macEnc; /// Mutex
|
|||
|
|||
CryptoPP::SHA3_256 m_egressMac; ///< State of MAC for egress ciphertext.
|
|||
CryptoPP::SHA3_256 m_ingressMac; ///< State of MAC for ingress ciphertext.
|
|||
|
|||
std::shared_ptr<RLPXSocket> m_socket; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -0,0 +1,266 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file RLPXHandshake.cpp
|
|||
* @author Alex Leverington <nessence@gmail.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include "Host.h" |
|||
#include "Session.h" |
|||
#include "Peer.h" |
|||
#include "RLPxHandshake.h" |
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::p2p; |
|||
using namespace CryptoPP; |
|||
|
|||
void RLPXHandshake::writeAuth() |
|||
{ |
|||
clog(NetConnect) << "p2p.connect.egress sending auth to " << m_socket->remoteEndpoint(); |
|||
m_auth.resize(Signature::size + h256::size + Public::size + h256::size + 1); |
|||
bytesRef sig(&m_auth[0], Signature::size); |
|||
bytesRef hepubk(&m_auth[Signature::size], h256::size); |
|||
bytesRef pubk(&m_auth[Signature::size + h256::size], Public::size); |
|||
bytesRef nonce(&m_auth[Signature::size + h256::size + Public::size], h256::size); |
|||
|
|||
// E(remote-pubk, S(ecdhe-random, ecdh-shared-secret^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x0)
|
|||
Secret staticShared; |
|||
crypto::ecdh::agree(m_host->m_alias.sec(), m_remote, staticShared); |
|||
sign(m_ecdhe.seckey(), staticShared ^ m_nonce).ref().copyTo(sig); |
|||
sha3(m_ecdhe.pubkey().ref(), hepubk); |
|||
m_host->m_alias.pub().ref().copyTo(pubk); |
|||
m_nonce.ref().copyTo(nonce); |
|||
m_auth[m_auth.size() - 1] = 0x0; |
|||
encryptECIES(m_remote, &m_auth, m_authCipher); |
|||
|
|||
auto self(shared_from_this()); |
|||
ba::async_write(m_socket->ref(), ba::buffer(m_authCipher), [this, self](boost::system::error_code ec, std::size_t) |
|||
{ |
|||
transition(ec); |
|||
}); |
|||
} |
|||
|
|||
void RLPXHandshake::writeAck() |
|||
{ |
|||
clog(NetConnect) << "p2p.connect.ingress sending ack to " << m_socket->remoteEndpoint(); |
|||
m_ack.resize(Public::size + h256::size + 1); |
|||
bytesRef epubk(&m_ack[0], Public::size); |
|||
bytesRef nonce(&m_ack[Public::size], h256::size); |
|||
m_ecdhe.pubkey().ref().copyTo(epubk); |
|||
m_nonce.ref().copyTo(nonce); |
|||
m_ack[m_ack.size() - 1] = 0x0; |
|||
encryptECIES(m_remote, &m_ack, m_ackCipher); |
|||
|
|||
auto self(shared_from_this()); |
|||
ba::async_write(m_socket->ref(), ba::buffer(m_ackCipher), [this, self](boost::system::error_code ec, std::size_t) |
|||
{ |
|||
transition(ec); |
|||
}); |
|||
} |
|||
|
|||
void RLPXHandshake::readAuth() |
|||
{ |
|||
clog(NetConnect) << "p2p.connect.ingress recving auth from " << m_socket->remoteEndpoint(); |
|||
m_authCipher.resize(307); |
|||
auto self(shared_from_this()); |
|||
ba::async_read(m_socket->ref(), ba::buffer(m_authCipher, 307), [this, self](boost::system::error_code ec, std::size_t) |
|||
{ |
|||
if (ec) |
|||
transition(ec); |
|||
else if (decryptECIES(m_host->m_alias.sec(), bytesConstRef(&m_authCipher), m_auth)) |
|||
{ |
|||
bytesConstRef sig(&m_auth[0], Signature::size); |
|||
bytesConstRef hepubk(&m_auth[Signature::size], h256::size); |
|||
bytesConstRef pubk(&m_auth[Signature::size + h256::size], Public::size); |
|||
bytesConstRef nonce(&m_auth[Signature::size + h256::size + Public::size], h256::size); |
|||
pubk.copyTo(m_remote.ref()); |
|||
nonce.copyTo(m_remoteNonce.ref()); |
|||
|
|||
Secret sharedSecret; |
|||
crypto::ecdh::agree(m_host->m_alias.sec(), m_remote, sharedSecret); |
|||
m_remoteEphemeral = recover(*(Signature*)sig.data(), sharedSecret ^ m_remoteNonce); |
|||
assert(sha3(m_remoteEphemeral) == *(h256*)hepubk.data()); |
|||
transition(); |
|||
} |
|||
else |
|||
{ |
|||
clog(NetWarn) << "p2p.connect.egress recving auth decrypt failed for" << m_socket->remoteEndpoint(); |
|||
m_nextState = Error; |
|||
transition(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
void RLPXHandshake::readAck() |
|||
{ |
|||
clog(NetConnect) << "p2p.connect.egress recving ack from " << m_socket->remoteEndpoint(); |
|||
m_ackCipher.resize(210); |
|||
auto self(shared_from_this()); |
|||
ba::async_read(m_socket->ref(), ba::buffer(m_ackCipher, 210), [this, self](boost::system::error_code ec, std::size_t) |
|||
{ |
|||
if (ec) |
|||
transition(ec); |
|||
else if (decryptECIES(m_host->m_alias.sec(), bytesConstRef(&m_ackCipher), m_ack)) |
|||
{ |
|||
bytesConstRef(&m_ack).cropped(0, Public::size).copyTo(m_remoteEphemeral.ref()); |
|||
bytesConstRef(&m_ack).cropped(Public::size, h256::size).copyTo(m_remoteNonce.ref()); |
|||
transition(); |
|||
} |
|||
else |
|||
{ |
|||
clog(NetWarn) << "p2p.connect.egress recving ack decrypt failed for " << m_socket->remoteEndpoint(); |
|||
m_nextState = Error; |
|||
transition(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
void RLPXHandshake::error() |
|||
{ |
|||
clog(NetConnect) << "Disconnecting " << m_socket->remoteEndpoint() << " (Handshake Failed)"; |
|||
m_socket->close(); |
|||
if (m_io != nullptr) |
|||
delete m_io; |
|||
} |
|||
|
|||
void RLPXHandshake::transition(boost::system::error_code _ech) |
|||
{ |
|||
if (_ech || m_nextState == Error) |
|||
return error(); |
|||
|
|||
auto self(shared_from_this()); |
|||
if (m_nextState == New) |
|||
{ |
|||
m_nextState = AckAuth; |
|||
if (m_originated) |
|||
writeAuth(); |
|||
else |
|||
readAuth(); |
|||
} |
|||
else if (m_nextState == AckAuth) |
|||
{ |
|||
m_nextState = WriteHello; |
|||
if (m_originated) |
|||
readAck(); |
|||
else |
|||
writeAck(); |
|||
} |
|||
else if (m_nextState == WriteHello) |
|||
{ |
|||
m_nextState = ReadHello; |
|||
|
|||
if (m_originated) |
|||
clog(NetConnect) << "p2p.connect.egress sending capabilities handshake"; |
|||
else |
|||
clog(NetConnect) << "p2p.connect.ingress sending capabilities handshake"; |
|||
|
|||
/// This pointer will be freed if there is an error otherwise
|
|||
/// it will be passed to Host which will take ownership.
|
|||
m_io = new RLPXFrameIO(*this); |
|||
|
|||
// old packet format
|
|||
// 5 arguments, HelloPacket
|
|||
RLPStream s; |
|||
s.append((unsigned)0).appendList(5) |
|||
<< m_host->protocolVersion() |
|||
<< m_host->m_clientVersion |
|||
<< m_host->caps() |
|||
<< m_host->listenPort() |
|||
<< m_host->id(); |
|||
bytes packet; |
|||
s.swapOut(packet); |
|||
m_io->writeSingleFramePacket(&packet, m_handshakeOutBuffer); |
|||
ba::async_write(m_socket->ref(), ba::buffer(m_handshakeOutBuffer), [this, self](boost::system::error_code ec, std::size_t) |
|||
{ |
|||
transition(ec); |
|||
}); |
|||
} |
|||
else if (m_nextState == ReadHello) |
|||
{ |
|||
// Authenticate and decrypt initial hello frame with initial RLPXFrameIO
|
|||
// and request m_host to start session.
|
|||
m_nextState = StartSession; |
|||
|
|||
// read frame header
|
|||
m_handshakeInBuffer.resize(h256::size); |
|||
ba::async_read(m_socket->ref(), boost::asio::buffer(m_handshakeInBuffer, h256::size), [this, self](boost::system::error_code ec, std::size_t) |
|||
{ |
|||
if (ec) |
|||
transition(ec); |
|||
else |
|||
{ |
|||
/// authenticate and decrypt header
|
|||
if (!m_io->authAndDecryptHeader(bytesRef(m_handshakeInBuffer.data(), h256::size))) |
|||
{ |
|||
m_nextState = Error; |
|||
transition(); |
|||
return; |
|||
} |
|||
|
|||
clog(NetNote) << (m_originated ? "p2p.connect.egress" : "p2p.connect.ingress") << "recvd hello header"; |
|||
|
|||
/// check frame size
|
|||
bytes& header = m_handshakeInBuffer; |
|||
uint32_t frameSize = (uint32_t)(header[2]) | (uint32_t)(header[1])<<8 | (uint32_t)(header[0])<<16; |
|||
if (frameSize > 1024) |
|||
{ |
|||
// all future frames: 16777216
|
|||
clog(NetWarn) << (m_originated ? "p2p.connect.egress" : "p2p.connect.ingress") << "hello frame is too large" << frameSize; |
|||
m_nextState = Error; |
|||
transition(); |
|||
return; |
|||
} |
|||
|
|||
/// rlp of header has protocol-type, sequence-id[, total-packet-size]
|
|||
bytes headerRLP(header.size() - 3 - h128::size); |
|||
bytesConstRef(&header).cropped(3).copyTo(&headerRLP); |
|||
|
|||
/// read padded frame and mac
|
|||
m_handshakeInBuffer.resize(frameSize + ((16 - (frameSize % 16)) % 16) + h128::size); |
|||
ba::async_read(m_socket->ref(), boost::asio::buffer(m_handshakeInBuffer, m_handshakeInBuffer.size()), [this, self, headerRLP](boost::system::error_code ec, std::size_t) |
|||
{ |
|||
if (ec) |
|||
transition(ec); |
|||
else |
|||
{ |
|||
bytesRef frame(&m_handshakeInBuffer); |
|||
if (!m_io->authAndDecryptFrame(frame)) |
|||
{ |
|||
clog(NetWarn) << (m_originated ? "p2p.connect.egress" : "p2p.connect.ingress") << "hello frame: decrypt failed"; |
|||
m_nextState = Error; |
|||
transition(); |
|||
return; |
|||
} |
|||
|
|||
PacketType packetType = (PacketType)(frame[0] == 0x80 ? 0x0 : frame[0]); |
|||
if (packetType != 0) |
|||
{ |
|||
clog(NetWarn) << (m_originated ? "p2p.connect.egress" : "p2p.connect.ingress") << "hello frame: invalid packet type"; |
|||
m_nextState = Error; |
|||
transition(); |
|||
return; |
|||
} |
|||
|
|||
clog(NetNote) << (m_originated ? "p2p.connect.egress" : "p2p.connect.ingress") << "hello frame: success. starting session."; |
|||
RLP rlp(frame.cropped(1)); |
|||
m_host->startPeerSession(m_remote, rlp, m_io, m_socket->remoteEndpoint()); |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
} |
@ -0,0 +1,126 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file RLPXHandshake.h
|
|||
* @author Alex Leverington <nessence@gmail.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
|
|||
#pragma once |
|||
|
|||
#include <memory> |
|||
#include <libdevcrypto/Common.h> |
|||
#include <libdevcrypto/ECDHE.h> |
|||
#include "RLPxFrameIO.h" |
|||
#include "Common.h" |
|||
namespace ba = boost::asio; |
|||
namespace bi = boost::asio::ip; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace p2p |
|||
{ |
|||
|
|||
/**
|
|||
* @brief Setup inbound or outbound connection for communication over RLPXFrameIO. |
|||
* RLPx Spec: https://github.com/ethereum/devp2p/blob/master/rlpx.md#encrypted-handshake
|
|||
* |
|||
* @todo Implement StartSession transition via lambda which is passed to constructor. |
|||
* |
|||
* Thread Safety |
|||
* Distinct Objects: Safe. |
|||
* Shared objects: Unsafe. |
|||
*/ |
|||
class RLPXHandshake: public std::enable_shared_from_this<RLPXHandshake> |
|||
{ |
|||
friend class RLPXFrameIO; |
|||
|
|||
/// Sequential states of handshake
|
|||
enum State |
|||
{ |
|||
Error = -1, |
|||
New, |
|||
AckAuth, |
|||
WriteHello, |
|||
ReadHello, |
|||
StartSession |
|||
}; |
|||
|
|||
public: |
|||
/// Setup incoming connection.
|
|||
RLPXHandshake(Host* _host, std::shared_ptr<RLPXSocket> const& _socket): m_host(_host), m_originated(false), m_socket(_socket) { crypto::Nonce::get().ref().copyTo(m_nonce.ref()); } |
|||
|
|||
/// Setup outbound connection.
|
|||
RLPXHandshake(Host* _host, std::shared_ptr<RLPXSocket> const& _socket, NodeId _remote): m_host(_host), m_remote(_remote), m_originated(true), m_socket(_socket) { crypto::Nonce::get().ref().copyTo(m_nonce.ref()); } |
|||
|
|||
~RLPXHandshake() {} |
|||
|
|||
/// Start handshake.
|
|||
void start() { transition(); } |
|||
|
|||
void cancel() { m_nextState = Error; } |
|||
|
|||
protected: |
|||
/// Write Auth message to socket and transitions to AckAuth.
|
|||
void writeAuth(); |
|||
|
|||
/// Reads Auth message from socket and transitions to AckAuth.
|
|||
void readAuth(); |
|||
|
|||
/// Write Ack message to socket and transitions to WriteHello.
|
|||
void writeAck(); |
|||
|
|||
/// Reads Auth message from socket and transitions to WriteHello.
|
|||
void readAck(); |
|||
|
|||
/// Closes connection and ends transitions.
|
|||
void error(); |
|||
|
|||
/// Performs transition for m_nextState.
|
|||
void transition(boost::system::error_code _ech = boost::system::error_code()); |
|||
|
|||
State m_nextState = New; ///< Current or expected state of transition.
|
|||
|
|||
Host* m_host; ///< Host which provides m_alias, protocolVersion(), m_clientVersion, caps(), and TCP listenPort().
|
|||
|
|||
/// Node id of remote host for socket.
|
|||
NodeId m_remote; ///< Public address of remote host.
|
|||
bool m_originated = false; ///< True if connection is outbound.
|
|||
|
|||
/// Buffers for encoded and decoded handshake phases
|
|||
bytes m_auth; ///< Plaintext of egress or ingress Auth message.
|
|||
bytes m_authCipher; ///< Ciphertext of egress or ingress Auth message.
|
|||
bytes m_ack; ///< Plaintext of egress or ingress Ack message.
|
|||
bytes m_ackCipher; ///< Ciphertext of egress or ingress Ack message.
|
|||
bytes m_handshakeOutBuffer; ///< Frame buffer for egress Hello packet.
|
|||
bytes m_handshakeInBuffer; ///< Frame buffer for ingress Hello packet.
|
|||
|
|||
crypto::ECDHE m_ecdhe; ///< Ephemeral ECDH secret and agreement.
|
|||
h256 m_nonce; ///< Nonce generated by this host for handshake.
|
|||
|
|||
Public m_remoteEphemeral; ///< Remote ephemeral public key.
|
|||
h256 m_remoteNonce; ///< Nonce generated by remote host for handshake.
|
|||
|
|||
/// Used to read and write RLPx encrypted frames for last step of handshake authentication.
|
|||
/// Passed onto Host which will take ownership.
|
|||
RLPXFrameIO* m_io = nullptr; |
|||
|
|||
std::shared_ptr<RLPXSocket> m_socket; ///< Socket.
|
|||
}; |
|||
|
|||
} |
|||
} |
@ -0,0 +1,156 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Yann <yann@ethdev.com> |
|||
* @date 2015 |
|||
* Proxy used to filter a QML TableView. |
|||
*/ |
|||
|
|||
|
|||
#include "SortFilterProxyModel.h" |
|||
#include <QtDebug> |
|||
#include <QtQml> |
|||
|
|||
using namespace dev::mix; |
|||
|
|||
SortFilterProxyModel::SortFilterProxyModel(QObject* _parent) : QSortFilterProxyModel(_parent) |
|||
{ |
|||
connect(this, &SortFilterProxyModel::rowsInserted, this, &SortFilterProxyModel::countChanged); |
|||
connect(this, &SortFilterProxyModel::rowsRemoved, this, &SortFilterProxyModel::countChanged); |
|||
} |
|||
|
|||
int SortFilterProxyModel::count() const |
|||
{ |
|||
return rowCount(); |
|||
} |
|||
|
|||
QObject* SortFilterProxyModel::source() const |
|||
{ |
|||
return sourceModel(); |
|||
} |
|||
|
|||
void SortFilterProxyModel::setSource(QObject* _source) |
|||
{ |
|||
setSourceModel(qobject_cast<QAbstractItemModel*>(_source)); |
|||
} |
|||
|
|||
QByteArray SortFilterProxyModel::sortRole() const |
|||
{ |
|||
return roleNames().value(QSortFilterProxyModel::sortRole()); |
|||
} |
|||
|
|||
void SortFilterProxyModel::setSortRole(QByteArray const& _role) |
|||
{ |
|||
QSortFilterProxyModel::setSortRole(roleKey(_role)); |
|||
} |
|||
|
|||
void SortFilterProxyModel::setSortOrder(Qt::SortOrder _order) |
|||
{ |
|||
QSortFilterProxyModel::sort(0, _order); |
|||
} |
|||
|
|||
QString SortFilterProxyModel::filterString() const |
|||
{ |
|||
return filterRegExp().pattern(); |
|||
} |
|||
|
|||
void SortFilterProxyModel::setFilterString(QString const& _filter) |
|||
{ |
|||
setFilterRegExp(QRegExp(_filter, filterCaseSensitivity(), static_cast<QRegExp::PatternSyntax>(filterSyntax()))); |
|||
} |
|||
|
|||
SortFilterProxyModel::FilterSyntax SortFilterProxyModel::filterSyntax() const |
|||
{ |
|||
return static_cast<FilterSyntax>(filterRegExp().patternSyntax()); |
|||
} |
|||
|
|||
void SortFilterProxyModel::setFilterSyntax(SortFilterProxyModel::FilterSyntax _syntax) |
|||
{ |
|||
setFilterRegExp(QRegExp(filterString(), filterCaseSensitivity(), static_cast<QRegExp::PatternSyntax>(_syntax))); |
|||
} |
|||
|
|||
QJSValue SortFilterProxyModel::get(int _idx) const |
|||
{ |
|||
QJSEngine *engine = qmlEngine(this); |
|||
QJSValue value = engine->newObject(); |
|||
if (_idx >= 0 && _idx < count()) |
|||
{ |
|||
QHash<int, QByteArray> roles = roleNames(); |
|||
QHashIterator<int, QByteArray> it(roles); |
|||
while (it.hasNext()) |
|||
{ |
|||
it.next(); |
|||
value.setProperty(QString::fromUtf8(it.value()), data(index(_idx, 0), it.key()).toString()); |
|||
} |
|||
} |
|||
return value; |
|||
} |
|||
|
|||
int SortFilterProxyModel::roleKey(QByteArray const& _role) const |
|||
{ |
|||
QHash<int, QByteArray> roles = roleNames(); |
|||
QHashIterator<int, QByteArray> it(roles); |
|||
while (it.hasNext()) |
|||
{ |
|||
it.next(); |
|||
if (it.value() == _role) |
|||
return it.key(); |
|||
} |
|||
return -1; |
|||
} |
|||
|
|||
QHash<int, QByteArray> SortFilterProxyModel::roleNames() const |
|||
{ |
|||
if (QAbstractItemModel* source = sourceModel()) |
|||
return source->roleNames(); |
|||
return QHash<int, QByteArray>(); |
|||
} |
|||
|
|||
bool SortFilterProxyModel::filterAcceptsRow(int _sourceRow, QModelIndex const& _sourceParent) const |
|||
{ |
|||
QAbstractItemModel* model = sourceModel(); |
|||
QModelIndex sourceIndex = model->index(_sourceRow, 0, _sourceParent); |
|||
if (!sourceIndex.isValid()) |
|||
return true; |
|||
|
|||
QString keyType = model->data(sourceIndex, roleKey(type.toUtf8())).toString(); |
|||
QString keyContent = model->data(sourceIndex, roleKey(content.toUtf8())).toString(); |
|||
return keyType.contains(m_filterType) && keyContent.contains(m_filterContent); |
|||
} |
|||
|
|||
void SortFilterProxyModel::setFilterType(QString const& _type) |
|||
{ |
|||
m_filterType = QRegExp(_type, filterCaseSensitivity(), static_cast<QRegExp::PatternSyntax>(filterSyntax())); |
|||
setFilterRegExp(_type); |
|||
} |
|||
|
|||
QString SortFilterProxyModel::filterType() const |
|||
{ |
|||
return m_filterType.pattern(); |
|||
} |
|||
|
|||
void SortFilterProxyModel::setFilterContent(QString const& _content) |
|||
{ |
|||
m_filterContent = QRegExp(_content, filterCaseSensitivity(), static_cast<QRegExp::PatternSyntax>(filterSyntax())); |
|||
setFilterRegExp(_content); |
|||
} |
|||
|
|||
QString SortFilterProxyModel::filterContent() const |
|||
{ |
|||
return m_filterContent.pattern(); |
|||
} |
|||
|
@ -0,0 +1,97 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Yann <yann@ethdev.com> |
|||
* @date 2015 |
|||
* Proxy used to filter a QML TableView. |
|||
*/ |
|||
|
|||
|
|||
#pragma once |
|||
|
|||
#include <QtCore/qsortfilterproxymodel.h> |
|||
#include <QtQml/qjsvalue.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace mix |
|||
{ |
|||
|
|||
class SortFilterProxyModel: public QSortFilterProxyModel |
|||
{ |
|||
Q_OBJECT |
|||
Q_PROPERTY(int count READ count NOTIFY countChanged) |
|||
Q_PROPERTY(QObject* source READ source WRITE setSource) |
|||
|
|||
Q_PROPERTY(QByteArray sortRole READ sortRole WRITE setSortRole) |
|||
Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder) |
|||
|
|||
Q_PROPERTY(QString filterContent READ filterContent WRITE setFilterContent) |
|||
Q_PROPERTY(QString filterType READ filterType WRITE setFilterType) |
|||
Q_PROPERTY(QString filterString READ filterString WRITE setFilterString) |
|||
Q_PROPERTY(FilterSyntax filterSyntax READ filterSyntax WRITE setFilterSyntax) |
|||
|
|||
Q_ENUMS(FilterSyntax) |
|||
|
|||
public: |
|||
explicit SortFilterProxyModel(QObject* _parent = 0); |
|||
|
|||
QObject* source() const; |
|||
void setSource(QObject* _source); |
|||
|
|||
QByteArray sortRole() const; |
|||
void setSortRole(QByteArray const& _role); |
|||
|
|||
void setSortOrder(Qt::SortOrder _order); |
|||
|
|||
QString filterContent() const; |
|||
void setFilterContent(QString const& _content); |
|||
QString filterType() const; |
|||
void setFilterType(QString const& _type); |
|||
|
|||
QString filterString() const; |
|||
void setFilterString(QString const& _filter); |
|||
|
|||
enum FilterSyntax { |
|||
RegExp, |
|||
Wildcard, |
|||
FixedString |
|||
}; |
|||
|
|||
FilterSyntax filterSyntax() const; |
|||
void setFilterSyntax(FilterSyntax _syntax); |
|||
|
|||
int count() const; |
|||
Q_INVOKABLE QJSValue get(int _index) const; |
|||
|
|||
signals: |
|||
void countChanged(); |
|||
|
|||
protected: |
|||
int roleKey(QByteArray const& _role) const; |
|||
QHash<int, QByteArray> roleNames() const; |
|||
bool filterAcceptsRow(int _sourceRow, QModelIndex const& _sourceParent) const; |
|||
|
|||
private: |
|||
QRegExp m_filterType; |
|||
QRegExp m_filterContent; |
|||
const QString type = "type"; |
|||
const QString content = "content"; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -0,0 +1,302 @@ |
|||
import QtQuick 2.0 |
|||
import QtQuick.Layouts 1.0 |
|||
import QtQuick.Controls 1.1 |
|||
import QtQuick.Controls.Styles 1.3 |
|||
import org.ethereum.qml.SortFilterProxyModel 1.0 |
|||
import "." |
|||
|
|||
Rectangle |
|||
{ |
|||
function push(_level, _type, _content) |
|||
{ |
|||
_content = _content.replace(/\n/g, " ") |
|||
logsModel.insert(0, { "type": _type, "date": Qt.formatDateTime(new Date(), "hh:mm:ss dd.MM.yyyy"), "content": _content, "level": _level }); |
|||
} |
|||
|
|||
anchors.fill: parent |
|||
radius: 5 |
|||
color: LogsPaneStyle.generic.layout.backgroundColor |
|||
border.color: LogsPaneStyle.generic.layout.borderColor |
|||
border.width: LogsPaneStyle.generic.layout.borderWidth |
|||
ColumnLayout { |
|||
z: 2 |
|||
height: parent.height |
|||
width: parent.width |
|||
spacing: 0 |
|||
Row |
|||
{ |
|||
id: rowAction |
|||
Layout.preferredHeight: LogsPaneStyle.generic.layout.headerHeight |
|||
height: LogsPaneStyle.generic.layout.headerHeight |
|||
anchors.leftMargin: LogsPaneStyle.generic.layout.leftMargin |
|||
anchors.left: parent.left |
|||
spacing: LogsPaneStyle.generic.layout.headerButtonSpacing |
|||
Button |
|||
{ |
|||
height: LogsPaneStyle.generic.layout.headerButtonHeight |
|||
anchors.verticalCenter: parent.verticalCenter |
|||
action: clearAction |
|||
iconSource: "qrc:/qml/img/broom.png" |
|||
} |
|||
|
|||
Action { |
|||
id: clearAction |
|||
enabled: logsModel.count > 0 |
|||
tooltip: qsTr("Clear") |
|||
onTriggered: { |
|||
logsModel.clear() |
|||
} |
|||
} |
|||
|
|||
Button |
|||
{ |
|||
height: LogsPaneStyle.generic.layout.headerButtonHeight |
|||
anchors.verticalCenter: parent.verticalCenter |
|||
action: copytoClipBoardAction |
|||
iconSource: "qrc:/qml/img/copy.png" |
|||
} |
|||
|
|||
Action { |
|||
id: copytoClipBoardAction |
|||
enabled: logsModel.count > 0 |
|||
tooltip: qsTr("Copy to Clipboard") |
|||
onTriggered: { |
|||
var content = ""; |
|||
for (var k = 0; k < logsModel.count; k++) |
|||
{ |
|||
var log = logsModel.get(k); |
|||
content += log.type + "\t" + log.level + "\t" + log.date + "\t" + log.content + "\n"; |
|||
} |
|||
appContext.toClipboard(content); |
|||
} |
|||
} |
|||
|
|||
Rectangle { |
|||
anchors.verticalCenter: parent.verticalCenter |
|||
width: 1; |
|||
height: parent.height - 10 |
|||
color : "#808080" |
|||
} |
|||
|
|||
ToolButton { |
|||
id: javascriptButton |
|||
checkable: true |
|||
height: LogsPaneStyle.generic.layout.headerButtonHeight |
|||
anchors.verticalCenter: parent.verticalCenter |
|||
checked: true |
|||
onCheckedChanged: { |
|||
proxyModel.toogleFilter("javascript") |
|||
} |
|||
tooltip: qsTr("JavaScript") |
|||
style: |
|||
ButtonStyle { |
|||
label: |
|||
Item { |
|||
DefaultLabel { |
|||
font.family: LogsPaneStyle.generic.layout.logLabelFont |
|||
font.pointSize: Style.absoluteSize(-3) |
|||
color: LogsPaneStyle.generic.layout.logLabelColor |
|||
anchors.centerIn: parent |
|||
text: qsTr("JavaScript") |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
ToolButton { |
|||
id: runButton |
|||
checkable: true |
|||
height: LogsPaneStyle.generic.layout.headerButtonHeight |
|||
anchors.verticalCenter: parent.verticalCenter |
|||
checked: true |
|||
onCheckedChanged: { |
|||
proxyModel.toogleFilter("run") |
|||
} |
|||
tooltip: qsTr("Run") |
|||
style: |
|||
ButtonStyle { |
|||
label: |
|||
Item { |
|||
DefaultLabel { |
|||
font.family: LogsPaneStyle.generic.layout.logLabelFont |
|||
font.pointSize: Style.absoluteSize(-3) |
|||
color: LogsPaneStyle.generic.layout.logLabelColor |
|||
anchors.centerIn: parent |
|||
text: qsTr("Run") |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
ToolButton { |
|||
id: stateButton |
|||
checkable: true |
|||
height: LogsPaneStyle.generic.layout.headerButtonHeight |
|||
anchors.verticalCenter: parent.verticalCenter |
|||
checked: true |
|||
onCheckedChanged: { |
|||
proxyModel.toogleFilter("state") |
|||
} |
|||
tooltip: qsTr("State") |
|||
style: |
|||
ButtonStyle { |
|||
label: |
|||
Item { |
|||
DefaultLabel { |
|||
font.family: LogsPaneStyle.generic.layout.logLabelFont |
|||
font.pointSize: Style.absoluteSize(-3) |
|||
color: "#5391d8" |
|||
anchors.centerIn: parent |
|||
text: qsTr("State") |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
ToolButton { |
|||
id: compilationButton |
|||
checkable: true |
|||
height: LogsPaneStyle.generic.layout.headerButtonHeight |
|||
anchors.verticalCenter: parent.verticalCenter |
|||
checked: false |
|||
onCheckedChanged: { |
|||
proxyModel.toogleFilter("compilation") |
|||
} |
|||
tooltip: qsTr("Compilation") |
|||
style: |
|||
ButtonStyle { |
|||
label: |
|||
Item { |
|||
DefaultLabel { |
|||
font.family: LogsPaneStyle.generic.layout.logLabelFont |
|||
font.pointSize: Style.absoluteSize(-3) |
|||
color: "#5391d8" |
|||
anchors.centerIn: parent |
|||
text: qsTr("Compilation") |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
DefaultTextField |
|||
{ |
|||
id: searchBox |
|||
height: LogsPaneStyle.generic.layout.headerButtonHeight |
|||
anchors.verticalCenter: parent.verticalCenter |
|||
width: LogsPaneStyle.generic.layout.headerInputWidth |
|||
font.family: LogsPaneStyle.generic.layout.logLabelFont |
|||
font.pointSize: Style.absoluteSize(-3) |
|||
font.italic: true |
|||
onTextChanged: { |
|||
proxyModel.search(text); |
|||
} |
|||
} |
|||
} |
|||
|
|||
ListModel { |
|||
id: logsModel |
|||
} |
|||
|
|||
TableView { |
|||
id: logsTable |
|||
clip: true |
|||
Layout.fillWidth: true |
|||
Layout.preferredHeight: parent.height - rowAction.height |
|||
headerVisible : false |
|||
onDoubleClicked: |
|||
{ |
|||
var log = logsModel.get((logsTable.currentRow)); |
|||
if (log) |
|||
appContext.toClipboard(log.type + "\t" + log.level + "\t" + log.date + "\t" + log.content); |
|||
} |
|||
|
|||
model: SortFilterProxyModel { |
|||
id: proxyModel |
|||
source: logsModel |
|||
property var roles: ["-", "javascript", "run", "state"] |
|||
|
|||
Component.onCompleted: { |
|||
filterType = regEx(proxyModel.roles); |
|||
} |
|||
|
|||
function search(_value) |
|||
{ |
|||
filterContent = _value; |
|||
} |
|||
|
|||
function toogleFilter(_value) |
|||
{ |
|||
var count = roles.length; |
|||
for (var i in roles) |
|||
{ |
|||
if (roles[i] === _value) |
|||
{ |
|||
roles.splice(i, 1); |
|||
break; |
|||
} |
|||
} |
|||
if (count === roles.length) |
|||
roles.push(_value); |
|||
|
|||
filterType = regEx(proxyModel.roles); |
|||
} |
|||
|
|||
function regEx(_value) |
|||
{ |
|||
return "(?:" + roles.join('|') + ")"; |
|||
} |
|||
|
|||
filterType: "(?:javascript|run|state)" |
|||
filterContent: "" |
|||
filterSyntax: SortFilterProxyModel.RegExp |
|||
filterCaseSensitivity: Qt.CaseInsensitive |
|||
} |
|||
TableViewColumn |
|||
{ |
|||
role: "date" |
|||
title: qsTr("date") |
|||
width: LogsPaneStyle.generic.layout.dateWidth |
|||
delegate: itemDelegate |
|||
} |
|||
TableViewColumn |
|||
{ |
|||
role: "type" |
|||
title: qsTr("type") |
|||
width: LogsPaneStyle.generic.layout.typeWidth |
|||
delegate: itemDelegate |
|||
} |
|||
TableViewColumn |
|||
{ |
|||
role: "content" |
|||
title: qsTr("content") |
|||
width: LogsPaneStyle.generic.layout.contentWidth |
|||
delegate: itemDelegate |
|||
} |
|||
|
|||
rowDelegate: Item { |
|||
Rectangle { |
|||
width: logsTable.width - 4 |
|||
height: 17 |
|||
color: styleData.alternate ? "transparent" : LogsPaneStyle.generic.layout.logAlternateColor |
|||
} |
|||
} |
|||
} |
|||
|
|||
Component { |
|||
id: itemDelegate |
|||
DefaultLabel { |
|||
text: styleData.value; |
|||
font.family: LogsPaneStyle.generic.layout.logLabelFont |
|||
font.pointSize: Style.absoluteSize(-1) |
|||
color: { |
|||
if (proxyModel.get(styleData.row).level === "error") |
|||
return "red"; |
|||
else if (proxyModel.get(styleData.row).level === "warning") |
|||
return "orange"; |
|||
else |
|||
return "#808080"; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,29 @@ |
|||
pragma Singleton |
|||
import QtQuick 2.0 |
|||
|
|||
QtObject { |
|||
|
|||
function absoluteSize(rel) |
|||
{ |
|||
return systemPointSize + rel; |
|||
} |
|||
|
|||
property QtObject generic: QtObject { |
|||
property QtObject layout: QtObject { |
|||
property string backgroundColor: "#f7f7f7" |
|||
property string borderColor: "#5391d8" |
|||
property int borderWidth: 1 |
|||
property int headerHeight: 35 |
|||
property int headerButtonSpacing: 5 |
|||
property int leftMargin: 10 |
|||
property int headerButtonHeight: 30 |
|||
property string logLabelColor: "#5391d8" |
|||
property string logLabelFont: "sans serif" |
|||
property int headerInputWidth: 200 |
|||
property int dateWidth: 150 |
|||
property int typeWidth: 80 |
|||
property int contentWidth: 700 |
|||
property string logAlternateColor: "#f0f0f0" |
|||
} |
|||
} |
|||
} |
After Width: | Height: | Size: 501 B |
After Width: | Height: | Size: 342 B |
@ -0,0 +1,32 @@ |
|||
# usage |
|||
# ./pullsubtree [repository branch] [repository2 branch2] |
|||
# |
|||
# example |
|||
# ./pullSubtree evmjit master |
|||
# ./pullSubtree ethereumjs develop |
|||
# ./pullSubtree evmjit master ethereumjs master |
|||
|
|||
evmjit_repo="https://github.com/ethereum/evmjit" |
|||
evmjit_location="evmjit" |
|||
|
|||
ethereumjs_repo="https://github.com/ethereum/ethereum.js" |
|||
ethereumjs_location="libjsqrc/ethereumjs" |
|||
|
|||
natspecjs_repo="https://github.com/ethereum/natspec.js" |
|||
natspecjs_location="libnatspec/natspecjs" |
|||
|
|||
while [ "$1" != "" ]; do |
|||
case $1 in |
|||
evmjit | ethereumjs | natspecjs ) |
|||
REPO="${1}_repo" |
|||
REPO=${!REPO} |
|||
LOCATION="${1}_location" |
|||
LOCATION=${!LOCATION} |
|||
shift |
|||
BRANCH=$1 |
|||
git subtree pull --prefix=${LOCATION} ${REPO} ${BRANCH} --squash |
|||
;; |
|||
esac |
|||
shift |
|||
done |
|||
|
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue