yann300
10 years ago
42 changed files with 1551 additions and 192 deletions
@ -0,0 +1,90 @@ |
|||
/*
|
|||
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 RLPXFrameReader.cpp
|
|||
* @author Alex Leverington <nessence@gmail.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include "RLPXFrameReader.h" |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::p2p; |
|||
|
|||
std::vector<RLPXPacket> RLPXFrameReader::demux(RLPXFrameCoder& _coder, RLPXFrameInfo const& _info, bytesRef _frame) |
|||
{ |
|||
if (!_coder.authAndDecryptFrame(_frame)) |
|||
BOOST_THROW_EXCEPTION(RLPXFrameDecryptFailed()); |
|||
|
|||
std::vector<RLPXPacket> ret; |
|||
if (_frame.empty()) |
|||
// drop: bad frame (empty)
|
|||
return ret; |
|||
if (_info.multiFrame && _info.totalLength && _frame.size() > _info.totalLength) |
|||
// drop: bad frame (too large)
|
|||
return ret; |
|||
|
|||
// trim mac
|
|||
bytesConstRef buffer = _frame.cropped(0, _frame.size() - (h128::size + _info.padding)); |
|||
// continue populating multiframe packets
|
|||
if (_info.multiFrame && m_incomplete.count(_info.sequenceId)) |
|||
{ |
|||
uint32_t& remaining = m_incomplete.at(_info.sequenceId).second; |
|||
if (!_info.totalLength && buffer.size() > 0 && buffer.size() <= remaining) |
|||
{ |
|||
remaining -= buffer.size(); |
|||
|
|||
RLPXPacket& p = m_incomplete.at(_info.sequenceId).first; |
|||
if(p.append(buffer) && !remaining) |
|||
ret.push_back(std::move(p)); |
|||
if (!remaining) |
|||
m_incomplete.erase(_info.sequenceId); |
|||
|
|||
if (!ret.empty() && remaining) |
|||
BOOST_THROW_EXCEPTION(RLPXInvalidPacket()); |
|||
else if (ret.empty() && !remaining) |
|||
BOOST_THROW_EXCEPTION(RLPXInvalidPacket()); |
|||
|
|||
return ret; |
|||
} |
|||
else |
|||
m_incomplete.erase(_info.sequenceId); |
|||
} |
|||
|
|||
while (!buffer.empty()) |
|||
{ |
|||
auto type = nextRLP(buffer); |
|||
if (type.empty()) |
|||
break; |
|||
buffer = buffer.cropped(type.size()); |
|||
// consume entire buffer if packet has sequence
|
|||
auto packet = _info.multiFrame ? buffer : nextRLP(buffer); |
|||
buffer = buffer.cropped(packet.size()); |
|||
RLPXPacket p(m_protocolType, type); |
|||
if (!packet.empty()) |
|||
p.append(packet); |
|||
|
|||
uint32_t remaining = _info.totalLength - type.size() - packet.size(); |
|||
if (p.isValid()) |
|||
ret.push_back(std::move(p)); |
|||
else if (_info.multiFrame && remaining) |
|||
m_incomplete.insert(std::make_pair(_info.sequenceId, std::make_pair(std::move(p), remaining))); |
|||
else |
|||
BOOST_THROW_EXCEPTION(RLPXInvalidPacket()); |
|||
} |
|||
return ret; |
|||
} |
@ -0,0 +1,55 @@ |
|||
/*
|
|||
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 RLPXFrameReader.h
|
|||
* @author Alex Leverington <nessence@gmail.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
|
|||
#pragma once |
|||
|
|||
#include <libdevcore/Guards.h> |
|||
#include "RLPXFrameCoder.h" |
|||
#include "RLPXPacket.h" |
|||
namespace ba = boost::asio; |
|||
namespace bi = boost::asio::ip; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace p2p |
|||
{ |
|||
|
|||
/**
|
|||
* RLPFrameReader |
|||
* Reads and assembles RLPX frame byte buffers into RLPX packets. Additionally |
|||
* buffers incomplete packets which are pieced into multiple frames (has sequence). |
|||
*/ |
|||
class RLPXFrameReader |
|||
{ |
|||
public: |
|||
RLPXFrameReader(uint16_t _protocolType): m_protocolType(_protocolType) {} |
|||
|
|||
/// Processes a single frame returning complete packets.
|
|||
std::vector<RLPXPacket> demux(RLPXFrameCoder& _coder, RLPXFrameInfo const& _info, bytesRef _frame); |
|||
|
|||
protected: |
|||
uint16_t m_protocolType; |
|||
std::map<uint16_t, std::pair<RLPXPacket, uint32_t>> m_incomplete; ///< Sequence: Incomplete packet and bytes remaining.
|
|||
}; |
|||
|
|||
} |
|||
} |
@ -0,0 +1,161 @@ |
|||
/*
|
|||
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 RLPXFrameWriter.cpp
|
|||
* @author Alex Leverington <nessence@gmail.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include "RLPXFrameWriter.h" |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::p2p; |
|||
|
|||
const uint16_t RLPXFrameWriter::EmptyFrameLength = h128::size * 3; // header + headerMAC + frameMAC
|
|||
const uint16_t RLPXFrameWriter::MinFrameDequeLength = h128::size * 4; // header + headerMAC + padded-block + frameMAC
|
|||
|
|||
void RLPXFrameWriter::enque(RLPXPacket&& _p, PacketPriority _priority) |
|||
{ |
|||
if (!_p.isValid()) |
|||
return; |
|||
WriterState& qs = _priority ? m_q.first : m_q.second; |
|||
DEV_GUARDED(qs.x) |
|||
qs.q.push_back(move(_p)); |
|||
} |
|||
|
|||
void RLPXFrameWriter::enque(uint8_t _packetType, RLPStream& _payload, PacketPriority _priority) |
|||
{ |
|||
enque(RLPXPacket(m_protocolType, (RLPStream() << _packetType), _payload), _priority); |
|||
} |
|||
|
|||
size_t RLPXFrameWriter::mux(RLPXFrameCoder& _coder, unsigned _size, vector<bytes>& o_toWrite) |
|||
{ |
|||
static const size_t c_blockSize = h128::size; |
|||
static const size_t c_overhead = c_blockSize * 3; // header + headerMac + frameMAC
|
|||
if (_size < c_overhead + c_blockSize) |
|||
return 0; |
|||
|
|||
size_t ret = 0; |
|||
size_t frameLen = _size / 16 * 16; |
|||
bytes payload(0); |
|||
bool swapQueues = false; |
|||
while (frameLen >= c_overhead + c_blockSize) |
|||
{ |
|||
bool highPending; |
|||
bool lowPending; |
|||
DEV_GUARDED(m_q.first.x) |
|||
highPending = !!m_q.first.q.size(); |
|||
DEV_GUARDED(m_q.second.x) |
|||
lowPending = !!m_q.second.q.size(); |
|||
|
|||
if (!highPending && !lowPending) |
|||
return ret; |
|||
|
|||
// first run when !swapQueues, high > low, otherwise low > high
|
|||
bool high = highPending && !swapQueues ? true : !lowPending; |
|||
WriterState &qs = high ? m_q.first : m_q.second; |
|||
size_t frameAllot = (!swapQueues && highPending && lowPending ? frameLen / 2 - (c_overhead + c_blockSize) > 0 ? frameLen / 2 : frameLen : frameLen) - c_overhead; |
|||
size_t offset = 0; |
|||
size_t length = 0; |
|||
while (frameAllot >= c_blockSize) |
|||
{ |
|||
// Invariants:
|
|||
// !qs.writing && payload.empty() initial entry
|
|||
// !qs.writing && !payload.empty() continuation (multiple packets per frame)
|
|||
// qs.writing && payload.empty() initial entry, continuation (multiple frames per packet)
|
|||
// qs.writing && !payload.empty() INVALID
|
|||
|
|||
// write packet-type
|
|||
if (qs.writing == nullptr) |
|||
{ |
|||
{ |
|||
Guard l(qs.x); |
|||
if (!qs.q.empty()) |
|||
qs.writing = &qs.q[0]; |
|||
else |
|||
break; |
|||
} |
|||
|
|||
// break here if we can't write-out packet-type
|
|||
// or payload is packed and next packet won't fit (implicit)
|
|||
qs.multiFrame = qs.writing->size() > frameAllot; |
|||
assert(qs.writing->type().size()); |
|||
if (qs.writing->type().size() > frameAllot || (qs.multiFrame && !payload.empty())) |
|||
{ |
|||
qs.writing = nullptr; |
|||
qs.remaining = 0; |
|||
qs.multiFrame = false; |
|||
break; |
|||
} |
|||
else if (qs.multiFrame) |
|||
qs.sequence = ++m_sequenceId; |
|||
|
|||
frameAllot -= qs.writing->type().size(); |
|||
payload += qs.writing->type(); |
|||
|
|||
qs.remaining = qs.writing->data().size(); |
|||
} |
|||
|
|||
// write payload w/remaining allotment
|
|||
assert(qs.multiFrame || (!qs.multiFrame && frameAllot >= qs.remaining)); |
|||
if (frameAllot && qs.remaining) |
|||
{ |
|||
offset = qs.writing->data().size() - qs.remaining; |
|||
length = qs.remaining <= frameAllot ? qs.remaining : frameAllot; |
|||
bytes portion = bytesConstRef(&qs.writing->data()).cropped(offset, length).toBytes(); |
|||
qs.remaining -= length; |
|||
frameAllot -= portion.size(); |
|||
payload += portion; |
|||
} |
|||
|
|||
assert((!qs.remaining && (offset > 0 || !qs.multiFrame)) || (qs.remaining && qs.multiFrame)); |
|||
if (!qs.remaining) |
|||
{ |
|||
qs.writing = nullptr; |
|||
DEV_GUARDED(qs.x) |
|||
qs.q.pop_front(); |
|||
ret++; |
|||
} |
|||
// qs.writing is left alone for first frame of multi-frame packet
|
|||
if (qs.multiFrame) |
|||
break; |
|||
} |
|||
|
|||
if (!payload.empty()) |
|||
{ |
|||
if (qs.multiFrame) |
|||
if (offset == 0) |
|||
// 1st frame of segmented packet writes total-size of packet
|
|||
_coder.writeFrame(m_protocolType, qs.sequence, qs.writing->size(), &payload, payload); |
|||
else |
|||
_coder.writeFrame(m_protocolType, qs.sequence, &payload, payload); |
|||
else |
|||
_coder.writeFrame(m_protocolType, &payload, payload); |
|||
assert(frameLen >= payload.size()); |
|||
frameLen -= payload.size(); |
|||
o_toWrite.push_back(payload); |
|||
payload.resize(0); |
|||
|
|||
if (!qs.remaining && qs.multiFrame) |
|||
qs.multiFrame = false; |
|||
} |
|||
else if (swapQueues) |
|||
break; |
|||
swapQueues = true; |
|||
} |
|||
return ret; |
|||
} |
@ -0,0 +1,88 @@ |
|||
/*
|
|||
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 RLPXperimental.h
|
|||
* @author Alex Leverington <nessence@gmail.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
|
|||
#pragma once |
|||
|
|||
#include <libdevcore/Guards.h> |
|||
#include "RLPXFrameCoder.h" |
|||
#include "RLPXPacket.h" |
|||
namespace ba = boost::asio; |
|||
namespace bi = boost::asio::ip; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace p2p |
|||
{ |
|||
|
|||
/**
|
|||
* @brief Multiplex packets into encrypted RLPX frames. |
|||
* @todo throw when enqueued packet is invalid |
|||
* @todo use RLPXFrameInfo |
|||
*/ |
|||
class RLPXFrameWriter |
|||
{ |
|||
/**
|
|||
* @brief Queue and state for Writer |
|||
* Properties are used independently; |
|||
* only valid packets should be added to q |
|||
* @todo implement as class |
|||
*/ |
|||
struct WriterState |
|||
{ |
|||
std::deque<RLPXPacket> q; |
|||
mutable Mutex x; |
|||
|
|||
RLPXPacket* writing = nullptr; |
|||
size_t remaining = 0; |
|||
bool multiFrame = false; |
|||
uint16_t sequence = -1; |
|||
}; |
|||
|
|||
public: |
|||
enum PacketPriority { PriorityLow = 0, PriorityHigh }; |
|||
static const uint16_t EmptyFrameLength; |
|||
static const uint16_t MinFrameDequeLength; |
|||
|
|||
RLPXFrameWriter(uint16_t _protocolType): m_protocolType(_protocolType) {} |
|||
RLPXFrameWriter(RLPXFrameWriter const& _s): m_protocolType(_s.m_protocolType) {} |
|||
|
|||
/// Returns total number of queued packets. Thread-safe.
|
|||
size_t size() const { size_t l; size_t h; DEV_GUARDED(m_q.first.x) h = m_q.first.q.size(); DEV_GUARDED(m_q.second.x) l = m_q.second.q.size(); return l + h; } |
|||
|
|||
/// Moves @_payload output to queue, to be muxed into frames by mux() when network buffer is ready for writing. Thread-safe.
|
|||
void enque(uint8_t _packetType, RLPStream& _payload, PacketPriority _priority = PriorityLow); |
|||
|
|||
/// Returns number of packets framed and outputs frames to o_bytes. Not thread-safe.
|
|||
size_t mux(RLPXFrameCoder& _coder, unsigned _size, std::vector<bytes>& o_toWrite); |
|||
|
|||
protected: |
|||
/// Moves @_p to queue, to be muxed into frames by mux() when network buffer is ready for writing. Thread-safe.
|
|||
void enque(RLPXPacket&& _p, PacketPriority _priority = PriorityLow); |
|||
|
|||
private: |
|||
uint16_t const m_protocolType; // Protocol Type
|
|||
std::pair<WriterState, WriterState> m_q; // High, Low frame queues
|
|||
uint16_t m_sequenceId = 0; // Sequence ID
|
|||
}; |
|||
|
|||
} |
|||
} |
@ -0,0 +1,69 @@ |
|||
/*
|
|||
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 RLPXPacket.h
|
|||
* @author Alex Leverington <nessence@gmail.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <algorithm> |
|||
#include "Common.h" |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace p2p |
|||
{ |
|||
|
|||
struct RLPXInvalidPacket: virtual dev::Exception {}; |
|||
|
|||
static bytesConstRef nextRLP(bytesConstRef _b) { try { RLP r(_b, RLP::AllowNonCanon); return _b.cropped(0, std::min((size_t)r.actualSize(), _b.size())); } catch(...) {} return bytesConstRef(); } |
|||
|
|||
/**
|
|||
* RLPX Packet |
|||
*/ |
|||
class RLPXPacket |
|||
{ |
|||
public: |
|||
/// Construct packet. RLPStream data is invalidated.
|
|||
RLPXPacket(uint8_t _capId, RLPStream& _type, RLPStream& _data): m_cap(_capId), m_type(_type.out()), m_data(_data.out()) {} |
|||
|
|||
/// Construct packet from single bytestream. RLPStream data is invalidated.
|
|||
RLPXPacket(unsigned _capId, bytesConstRef _in): m_cap(_capId), m_type(nextRLP(_in).toBytes()) { if (_in.size() > m_type.size()) { m_data.resize(_in.size() - m_type.size()); _in.cropped(m_type.size()).copyTo(&m_data); } } |
|||
|
|||
RLPXPacket(RLPXPacket const& _p) = delete; |
|||
RLPXPacket(RLPXPacket&& _p): m_cap(_p.m_cap), m_type(_p.m_type), m_data(_p.m_data) {} |
|||
|
|||
bytes const& type() const { return m_type; } |
|||
|
|||
bytes const& data() const { return m_data; } |
|||
|
|||
size_t size() const { try { return RLP(m_type).actualSize() + RLP(m_data, RLP::LaissezFaire).actualSize(); } catch(...) { return 0; } } |
|||
|
|||
/// Appends byte data and returns if packet is valid.
|
|||
bool append(bytesConstRef _in) { auto offset = m_data.size(); m_data.resize(offset + _in.size()); _in.copyTo(bytesRef(&m_data).cropped(offset)); return isValid(); } |
|||
|
|||
virtual bool isValid() const noexcept { try { return !(m_type.empty() && m_data.empty()) && RLP(m_type).actualSize() == m_type.size() && RLP(m_data).actualSize() == m_data.size(); } catch (...) {} return false; } |
|||
|
|||
protected: |
|||
uint8_t m_cap; |
|||
bytes m_type; |
|||
bytes m_data; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -0,0 +1,112 @@ |
|||
/*
|
|||
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 RLPXSocketIO.cpp
|
|||
* @author Alex Leverington <nessence@gmail.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include "RLPXSocketIO.h" |
|||
|
|||
#include <algorithm> |
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::p2p; |
|||
|
|||
uint32_t const RLPXSocketIO::MinFrameSize = h128::size * 3; // header + block + mac
|
|||
uint32_t const RLPXSocketIO::MaxPacketSize = 1 << 24; |
|||
uint16_t const RLPXSocketIO::DefaultInitialCapacity = 8 << 8; |
|||
|
|||
RLPXSocketIO::RLPXSocketIO(unsigned _protCount, RLPXFrameCoder& _coder, bi::tcp::socket& _socket, bool _flowControl, size_t _initialCapacity): |
|||
m_flowControl(_flowControl), |
|||
m_coder(_coder), |
|||
m_socket(_socket), |
|||
m_writers(move(writers(_protCount))), |
|||
m_egressCapacity(m_flowControl ? _initialCapacity : MaxPacketSize * m_writers.size()) |
|||
{} |
|||
|
|||
vector<RLPXFrameWriter> RLPXSocketIO::writers(unsigned _capacity) |
|||
{ |
|||
vector<RLPXFrameWriter> ret; |
|||
for (unsigned i = 0; i < _capacity; i++) |
|||
ret.push_back(RLPXFrameWriter(i)); |
|||
return ret; |
|||
} |
|||
|
|||
void RLPXSocketIO::send(unsigned _protocolType, unsigned _type, RLPStream& _payload) |
|||
{ |
|||
if (!m_socket.is_open()) |
|||
return; // TCPSocketNotOpen
|
|||
m_writers.at(_protocolType).enque(_type, _payload); |
|||
bool wasEmtpy = false; |
|||
DEV_GUARDED(x_queued) |
|||
wasEmtpy = (++m_queued == 1); |
|||
if (wasEmtpy) |
|||
doWrite(); |
|||
} |
|||
|
|||
void RLPXSocketIO::doWrite() |
|||
{ |
|||
m_toSend.clear(); |
|||
|
|||
size_t capacity; |
|||
DEV_GUARDED(x_queued) |
|||
capacity = min(m_egressCapacity, MaxPacketSize); |
|||
|
|||
size_t active = 0; |
|||
for (auto const& w: m_writers) |
|||
if (w.size()) |
|||
active += 1; |
|||
size_t dequed = 0; |
|||
size_t protFrameSize = capacity / active; |
|||
if (protFrameSize >= MinFrameSize) |
|||
for (auto& w: m_writers) |
|||
dequed += w.mux(m_coder, protFrameSize, m_toSend); |
|||
|
|||
if (dequed) |
|||
write(dequed); |
|||
else |
|||
deferWrite(); |
|||
} |
|||
|
|||
void RLPXSocketIO::deferWrite() |
|||
{ |
|||
auto self(shared_from_this()); |
|||
m_congestion.reset(new ba::deadline_timer(m_socket.get_io_service())); |
|||
m_congestion->expires_from_now(boost::posix_time::milliseconds(50)); |
|||
m_congestion->async_wait([=](boost::system::error_code const& _ec) { m_congestion.reset(); if (!_ec) doWrite(); }); |
|||
} |
|||
|
|||
void RLPXSocketIO::write(size_t _dequed) |
|||
{ |
|||
auto self(shared_from_this()); |
|||
ba::async_write(m_socket, ba::buffer(m_toSend), [this, self, _dequed](boost::system::error_code ec, size_t written) |
|||
{ |
|||
if (ec) |
|||
return; // TCPSocketWriteError
|
|||
|
|||
bool reschedule = false; |
|||
DEV_GUARDED(x_queued) |
|||
{ |
|||
if (m_flowControl) |
|||
m_egressCapacity -= written; |
|||
m_queued -= _dequed; |
|||
reschedule = m_queued > 0; |
|||
} |
|||
if (reschedule) |
|||
doWrite(); |
|||
}); |
|||
} |
@ -0,0 +1,71 @@ |
|||
/*
|
|||
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 RLPXSocketIO.h
|
|||
* @author Alex Leverington <nessence@gmail.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include "RLPXFrameWriter.h" |
|||
namespace ba = boost::asio; |
|||
namespace bi = boost::asio::ip; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace p2p |
|||
{ |
|||
|
|||
class RLPXSocketIO: public std::enable_shared_from_this<RLPXSocketIO> |
|||
{ |
|||
public: |
|||
static uint32_t const MinFrameSize; |
|||
static uint32_t const MaxPacketSize; |
|||
static uint16_t const DefaultInitialCapacity; |
|||
|
|||
RLPXSocketIO(unsigned _protCount, RLPXFrameCoder& _coder, bi::tcp::socket& _socket, bool _flowControl = true, size_t _initialCapacity = DefaultInitialCapacity); |
|||
|
|||
void send(unsigned _protocolType, unsigned _type, RLPStream& _payload); |
|||
|
|||
void doWrite(); |
|||
|
|||
bool congested() const { return !!m_congestion; } |
|||
|
|||
private: |
|||
static std::vector<RLPXFrameWriter> writers(unsigned _capacity); |
|||
|
|||
void deferWrite(); |
|||
|
|||
void write(size_t _dequed); |
|||
|
|||
bool const m_flowControl; ///< True if flow control is enabled.
|
|||
|
|||
RLPXFrameCoder& m_coder; ///< Encoder/decoder of frame payloads.
|
|||
bi::tcp::socket& m_socket; |
|||
|
|||
std::vector<bytes> m_toSend; ///< Reusable byte buffer for pending socket writes.
|
|||
|
|||
std::vector<RLPXFrameWriter> m_writers; ///< Write queues for each protocol. TODO: map to bytes (of capability)
|
|||
std::unique_ptr<ba::deadline_timer> m_congestion; ///< Scheduled when writes are deferred due to congestion.
|
|||
|
|||
Mutex x_queued; |
|||
unsigned m_queued = 0; ///< Track total queued packets to ensure single write loop
|
|||
uint32_t m_egressCapacity; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -0,0 +1,69 @@ |
|||
/*
|
|||
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 WhisperDB.cpp
|
|||
* @author Vladislav Gluhovsky <vlad@ethdev.com> |
|||
* @date July 2015 |
|||
*/ |
|||
|
|||
#include "WhisperDB.h" |
|||
#include <boost/filesystem.hpp> |
|||
#include <libdevcore/FileSystem.h> |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::shh; |
|||
|
|||
WhisperDB::WhisperDB() |
|||
{ |
|||
string path = dev::getDataDir("shh"); |
|||
boost::filesystem::create_directories(path); |
|||
leveldb::Options op; |
|||
op.create_if_missing = true; |
|||
op.max_open_files = 256; |
|||
leveldb::DB* p = nullptr; |
|||
leveldb::Status status = leveldb::DB::Open(op, path + "/messages", &p); |
|||
m_db.reset(p); |
|||
if (!status.ok()) |
|||
BOOST_THROW_EXCEPTION(FailedToOpenLevelDB(status.ToString())); |
|||
} |
|||
|
|||
string WhisperDB::lookup(dev::h256 const& _key) const |
|||
{ |
|||
string ret; |
|||
leveldb::Slice slice((char const*)_key.data(), _key.size); |
|||
leveldb::Status status = m_db->Get(m_readOptions, slice, &ret); |
|||
if (!status.ok() && !status.IsNotFound()) |
|||
BOOST_THROW_EXCEPTION(FailedLookupInLevelDB(status.ToString())); |
|||
|
|||
return ret; |
|||
} |
|||
|
|||
void WhisperDB::insert(dev::h256 const& _key, string const& _value) |
|||
{ |
|||
leveldb::Slice slice((char const*)_key.data(), _key.size); |
|||
leveldb::Status status = m_db->Put(m_writeOptions, slice, _value); |
|||
if (!status.ok()) |
|||
BOOST_THROW_EXCEPTION(FailedInsertInLevelDB(status.ToString())); |
|||
} |
|||
|
|||
void WhisperDB::kill(dev::h256 const& _key) |
|||
{ |
|||
leveldb::Slice slice((char const*)_key.data(), _key.size); |
|||
leveldb::Status status = m_db->Delete(m_writeOptions, slice); |
|||
if (!status.ok()) |
|||
BOOST_THROW_EXCEPTION(FailedDeleteInLevelDB(status.ToString())); |
|||
} |
@ -0,0 +1,55 @@ |
|||
/*
|
|||
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 WhisperDB.h
|
|||
* @author Vladislav Gluhovsky <vlad@ethdev.com> |
|||
* @date July 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <libdevcore/db.h> |
|||
#include <libdevcore/FixedHash.h> |
|||
#include "Common.h" |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace shh |
|||
{ |
|||
|
|||
struct FailedToOpenLevelDB: virtual Exception { FailedToOpenLevelDB(std::string const& _message): Exception(_message) {} }; |
|||
struct FailedInsertInLevelDB: virtual Exception { FailedInsertInLevelDB(std::string const& _message): Exception(_message) {} }; |
|||
struct FailedLookupInLevelDB: virtual Exception { FailedLookupInLevelDB(std::string const& _message): Exception(_message) {} }; |
|||
struct FailedDeleteInLevelDB: virtual Exception { FailedDeleteInLevelDB(std::string const& _message): Exception(_message) {} }; |
|||
|
|||
class WhisperDB |
|||
{ |
|||
public: |
|||
WhisperDB(); |
|||
~WhisperDB() {} |
|||
|
|||
std::string lookup(dev::h256 const& _key) const; |
|||
void insert(dev::h256 const& _key, std::string const& _value); |
|||
void kill(dev::h256 const& _key); |
|||
|
|||
private: |
|||
leveldb::ReadOptions m_readOptions; |
|||
leveldb::WriteOptions m_writeOptions; |
|||
std::unique_ptr<leveldb::DB> m_db; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -0,0 +1,122 @@ |
|||
/*
|
|||
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 whisperMessage.cpp
|
|||
* @author Vladislav Gluhovsky <vlad@ethdev.com> |
|||
* @date July 2015 |
|||
*/ |
|||
|
|||
#include <thread> |
|||
#include <boost/test/unit_test.hpp> |
|||
#include <libwhisper/WhisperDB.h> |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::shh; |
|||
|
|||
BOOST_AUTO_TEST_SUITE(whisperDB) |
|||
|
|||
BOOST_AUTO_TEST_CASE(basic) |
|||
{ |
|||
VerbosityHolder setTemporaryLevel(10); |
|||
cnote << "Testing Whisper DB..."; |
|||
|
|||
string s; |
|||
string const text1 = "lorem"; |
|||
string const text2 = "ipsum"; |
|||
h256 h1(0xBEEF); |
|||
h256 h2(0xC0FFEE); |
|||
WhisperDB db; |
|||
|
|||
db.kill(h1); |
|||
db.kill(h2); |
|||
|
|||
s = db.lookup(h1); |
|||
BOOST_REQUIRE(s.empty()); |
|||
|
|||
db.insert(h1, text2); |
|||
s = db.lookup(h2); |
|||
BOOST_REQUIRE(s.empty()); |
|||
s = db.lookup(h1); |
|||
BOOST_REQUIRE(!s.compare(text2)); |
|||
|
|||
db.insert(h1, text1); |
|||
s = db.lookup(h2); |
|||
BOOST_REQUIRE(s.empty()); |
|||
s = db.lookup(h1); |
|||
BOOST_REQUIRE(!s.compare(text1)); |
|||
|
|||
db.insert(h2, text2); |
|||
s = db.lookup(h2); |
|||
BOOST_REQUIRE(!s.compare(text2)); |
|||
s = db.lookup(h1); |
|||
BOOST_REQUIRE(!s.compare(text1)); |
|||
|
|||
db.kill(h1); |
|||
db.kill(h2); |
|||
|
|||
s = db.lookup(h2); |
|||
BOOST_REQUIRE(s.empty()); |
|||
s = db.lookup(h1); |
|||
BOOST_REQUIRE(s.empty()); |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_CASE(persistence) |
|||
{ |
|||
VerbosityHolder setTemporaryLevel(10); |
|||
cnote << "Testing persistence of Whisper DB..."; |
|||
|
|||
string s; |
|||
string const text1 = "sator"; |
|||
string const text2 = "arepo"; |
|||
h256 const h1(0x12345678); |
|||
h256 const h2(0xBADD00DE); |
|||
|
|||
{ |
|||
WhisperDB db; |
|||
db.kill(h1); |
|||
db.kill(h2); |
|||
s = db.lookup(h1); |
|||
BOOST_REQUIRE(s.empty()); |
|||
db.insert(h1, text2); |
|||
s = db.lookup(h2); |
|||
BOOST_REQUIRE(s.empty()); |
|||
s = db.lookup(h1); |
|||
BOOST_REQUIRE(!s.compare(text2)); |
|||
} |
|||
|
|||
this_thread::sleep_for(chrono::milliseconds(20)); |
|||
|
|||
{ |
|||
WhisperDB db; |
|||
db.insert(h1, text1); |
|||
db.insert(h2, text2); |
|||
} |
|||
|
|||
this_thread::sleep_for(chrono::milliseconds(20)); |
|||
|
|||
{ |
|||
WhisperDB db; |
|||
s = db.lookup(h2); |
|||
BOOST_REQUIRE(!s.compare(text2)); |
|||
s = db.lookup(h1); |
|||
BOOST_REQUIRE(!s.compare(text1)); |
|||
db.kill(h1); |
|||
db.kill(h2); |
|||
} |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_SUITE_END() |
Loading…
Reference in new issue