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> |
<RCC> |
||||
<qresource prefix="/natspec"> |
<qresource prefix="/natspec"> |
||||
<file>natspec.js</file> |
<file alias="natspec.js">natspecjs/dist/natspec.min.js</file> |
||||
</qresource> |
</qresource> |
||||
</RCC> |
</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