From 8d34e9757ceb3e09d68a5a1198c7351cb4e9676d Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 13 Jan 2016 22:21:25 +0100 Subject: [PATCH] libp2p: implement EIP-8 RLPx handshake encoding --- libp2p/RLPxHandshake.cpp | 100 ++++++++++++++---- libp2p/RLPxHandshake.h | 20 +++- test/libp2p/eip-8.cpp | 223 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 315 insertions(+), 28 deletions(-) diff --git a/libp2p/RLPxHandshake.cpp b/libp2p/RLPxHandshake.cpp index 4c553110b..4e7ec9891 100644 --- a/libp2p/RLPxHandshake.cpp +++ b/libp2p/RLPxHandshake.cpp @@ -72,9 +72,19 @@ void RLPXHandshake::writeAck() }); } +void RLPXHandshake::setAuthValues(Signature _sig, Public _remotePubk, h256 _remoteNonce, uint64_t _remoteVersion) +{ + _remotePubk.ref().copyTo(m_remote.ref()); + _remoteNonce.ref().copyTo(m_remoteNonce.ref()); + m_remoteVersion = _remoteVersion; + Secret sharedSecret; + crypto::ecdh::agree(m_host->m_alias.sec(), _remotePubk, sharedSecret); + m_remoteEphemeral = recover(_sig, sharedSecret.makeInsecure() ^ _remoteNonce); +} + void RLPXHandshake::readAuth() { - clog(NetP2PConnect) << "p2p.connect.ingress recving auth from " << m_socket->remoteEndpoint(); + clog(NetP2PConnect) << "p2p.connect.ingress receiving 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) @@ -83,25 +93,43 @@ void RLPXHandshake::readAuth() 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.makeInsecure() ^ m_remoteNonce); + bytesConstRef data(&m_auth); + auto sig = Signature(data.cropped(0, Signature::size)); + auto pubk = Public(data.cropped(Signature::size + h256::size, Public::size)); + auto nonce = h256(data.cropped(Signature::size + h256::size + Public::size, h256::size)); + setAuthValues(sig, pubk, nonce, 4); + transition(); + } + else + readAuthEIP8(); + }); +} - if (sha3(m_remoteEphemeral) != *(h256*)hepubk.data()) - clog(NetP2PConnect) << "p2p.connect.ingress auth failed (invalid: hash mismatch) for" << m_socket->remoteEndpoint(); - +void RLPXHandshake::readAuthEIP8() +{ + clog(NetP2PConnect) << "p2p.connect.ingress receiving EIP-8 auth from " << m_socket->remoteEndpoint(); + auto length = fromBigEndian(bytesConstRef(&m_authCipher).cropped(0, 2)); + m_authCipher.resize(length + 2); + auto rembuf = ba::buffer(ba::buffer(m_authCipher) + 307); + auto self = shared_from_this(); + ba::async_read(m_socket->ref(), rembuf, [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).cropped(2), m_auth)) + { + RLP rlp(m_auth, RLP::ThrowOnFail | RLP::FailIfTooSmall); + setAuthValues( + rlp[0].toHash(), + rlp[1].toHash(), + rlp[2].toHash(), + rlp[3].toInt() + ); transition(); } else { - clog(NetP2PConnect) << "p2p.connect.ingress recving auth decrypt failed for" << m_socket->remoteEndpoint(); + clog(NetP2PConnect) << "p2p.connect.ingress auth decrypt failed for" << m_socket->remoteEndpoint(); m_nextState = Error; transition(); } @@ -110,7 +138,7 @@ void RLPXHandshake::readAuth() void RLPXHandshake::readAck() { - clog(NetP2PConnect) << "p2p.connect.egress recving ack from " << m_socket->remoteEndpoint(); + clog(NetP2PConnect) << "p2p.connect.egress receiving 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) @@ -121,29 +149,59 @@ void RLPXHandshake::readAck() { bytesConstRef(&m_ack).cropped(0, Public::size).copyTo(m_remoteEphemeral.ref()); bytesConstRef(&m_ack).cropped(Public::size, h256::size).copyTo(m_remoteNonce.ref()); + m_remoteVersion = 4; + transition(); + } + else + readAckEIP8(); + }); +} + +void RLPXHandshake::readAckEIP8() +{ + clog(NetP2PConnect) << "p2p.connect.egress receiving EIP-8 ack from " << m_socket->remoteEndpoint(); + auto length = fromBigEndian(bytesConstRef(&m_ackCipher).cropped(0, 2)); + m_ackCipher.resize(length + 2); + auto rembuf = ba::buffer(ba::buffer(m_ackCipher) + 210); + auto self = shared_from_this(); + ba::async_read(m_socket->ref(), rembuf, [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).cropped(2), m_ack)) + { + RLP rlp(m_ack, RLP::ThrowOnFail | RLP::FailIfTooSmall); + m_remoteEphemeral = rlp[0].toHash(); + m_remoteNonce = rlp[1].toHash(); + m_remoteVersion = rlp[2].toInt(); transition(); } else { - clog(NetP2PConnect) << "p2p.connect.egress recving ack decrypt failed for " << m_socket->remoteEndpoint(); + clog(NetP2PConnect) << "p2p.connect.egress ack decrypt failed for " << m_socket->remoteEndpoint(); m_nextState = Error; transition(); } }); } -void RLPXHandshake::error() +void RLPXHandshake::cancel() { + m_cancel = true; m_idleTimer.cancel(); - + m_socket->close(); + m_io.reset(); +} + +void RLPXHandshake::error() +{ auto connected = m_socket->isConnected(); if (connected && !m_socket->remoteEndpoint().address().is_unspecified()) clog(NetP2PConnect) << "Disconnecting " << m_socket->remoteEndpoint() << " (Handshake Failed)"; else clog(NetP2PConnect) << "Handshake Failed (Connection reset by peer)"; - m_socket->close(); - m_io.reset(); + cancel(); } void RLPXHandshake::transition(boost::system::error_code _ech) diff --git a/libp2p/RLPxHandshake.h b/libp2p/RLPxHandshake.h index 426bce234..650b31220 100644 --- a/libp2p/RLPxHandshake.h +++ b/libp2p/RLPxHandshake.h @@ -62,9 +62,9 @@ class RLPXHandshake: public std::enable_shared_from_this /// Start handshake. void start() { transition(); } - /// Cancels handshake preventing - void cancel() { m_cancel = true; m_socket->close(); } - + /// Aborts the handshake. + void cancel(); + protected: /// Sequential states of handshake enum State @@ -79,21 +79,30 @@ class RLPXHandshake: public std::enable_shared_from_this /// Write Auth message to socket and transitions to AckAuth. void writeAuth(); - + /// Reads Auth message from socket and transitions to AckAuth. void readAuth(); + + /// Continues reading Auth message in EIP-8 format and transitions to AckAuthEIP8. + void readAuthEIP8(); + + /// Derives ephemeral secret from signature and sets members after Auth has been decrypted. + void setAuthValues(Signature sig, Public remotePubk, h256 remoteNonce, uint64_t remoteVersion); /// Write Ack message to socket and transitions to WriteHello. void writeAck(); /// Reads Auth message from socket and transitions to WriteHello. void readAck(); + + /// Continues reading Ack message in EIP-8 format and transitions to WriteHello. + void readAckEIP8(); /// Closes connection and ends transitions. void error(); /// Performs transition for m_nextState. - void transition(boost::system::error_code _ech = boost::system::error_code()); + virtual void transition(boost::system::error_code _ech = boost::system::error_code()); /// Timeout for remote to respond to transition events. Enforced by m_idleTimer and refreshed by transition(). boost::posix_time::milliseconds const c_timeout = boost::posix_time::milliseconds(1800); @@ -120,6 +129,7 @@ class RLPXHandshake: public std::enable_shared_from_this Public m_remoteEphemeral; ///< Remote ephemeral public key. h256 m_remoteNonce; ///< Nonce generated by remote host for handshake. + uint64_t m_remoteVersion; /// Used to read and write RLPx encrypted frames for last step of handshake authentication. /// Passed onto Host which will take ownership. diff --git a/test/libp2p/eip-8.cpp b/test/libp2p/eip-8.cpp index f9e75fe89..6a13757f7 100644 --- a/test/libp2p/eip-8.cpp +++ b/test/libp2p/eip-8.cpp @@ -22,14 +22,17 @@ #include #include +#include +#include +#include using namespace std; using namespace dev; using namespace dev::p2p; -BOOST_AUTO_TEST_SUITE(discovery) +BOOST_AUTO_TEST_SUITE(eip8) -BOOST_AUTO_TEST_CASE(test_discovery_eip8) +BOOST_AUTO_TEST_CASE(test_discovery_packets) { bi::udp::endpoint ep; bytes packet; @@ -114,4 +117,220 @@ BOOST_AUTO_TEST_CASE(test_discovery_eip8) BOOST_CHECK_EQUAL(neighbours.ts, 1136239445); } +class TestHandshake: public RLPXHandshake +{ +public: + using RLPXHandshake::RLPXHandshake; + + /// Creates a handshake attached to a Host with the given alias, + /// then runs it through the key establishment, supplying packet + /// as the input on the socket. + /// + /// If remoteID is supplied, the handshake runs in initiator mode. + static shared_ptr runWithInput(Secret _hostAlias, bytes _packet, NodeID _remoteID = NodeID()); + + /// transition is overridden to stop after key establishment. + virtual void transition(boost::system::error_code _ec); + + /// Reports whether we made it through the key establishment without error. + bool completedKeyEstablishment() { return m_nextState == ReadHello; } + + /// Checks whether Auth-related members match the values in the EIP-8 test vectors. + void checkAuthValuesEIP8(uint64_t _expectedRemoteVersion); + + /// Checks whether Ack-related members match the values in the EIP-8 test vectors. + void checkAckValuesEIP8(uint64_t _expectedRemoteVersion); +}; + +// This test checks that pre-EIP-8 'plain' format is still accepted. +BOOST_AUTO_TEST_CASE(test_handshake_plain_auth) +{ + auto keyB = Secret("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"); + auto auth = fromHex( + "048ca79ad18e4b0659fab4853fe5bc58eb83992980f4c9cc147d2aa31532efd29a3d3dc6a3d89eaf" + "913150cfc777ce0ce4af2758bf4810235f6e6ceccfee1acc6b22c005e9e3a49d6448610a58e98744" + "ba3ac0399e82692d67c1f58849050b3024e21a52c9d3b01d871ff5f210817912773e610443a9ef14" + "2e91cdba0bd77b5fdf0769b05671fc35f83d83e4d3b0b000c6b2a1b1bba89e0fc51bf4e460df3105" + "c444f14be226458940d6061c296350937ffd5e3acaceeaaefd3c6f74be8e23e0f45163cc7ebd7622" + "0f0128410fd05250273156d548a414444ae2f7dea4dfca2d43c057adb701a715bf59f6fb66b2d1d2" + "0f2c703f851cbf5ac47396d9ca65b6260bd141ac4d53e2de585a73d1750780db4c9ee4cd4d225173" + "a4592ee77e2bd94d0be3691f3b406f9bba9b591fc63facc016bfa8" + ); + auto handshake = TestHandshake::runWithInput(keyB, auth); + BOOST_REQUIRE(handshake->completedKeyEstablishment()); + handshake->checkAuthValuesEIP8(4); +} + +BOOST_AUTO_TEST_CASE(test_handshake_eip8_auth1) +{ + auto keyB = Secret("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"); + auto auth = fromHex( + "017f04fb0b7e0b750bc6bc3a8687ddf46ced3f2c1d6f24d6c8f9780522846fda9e0eadad42403cce" + "59fe71376cee69e8f70b98c5b64ab85c50c35b0064f7b0d22fd02482641b7f9d19ea28459f9078fa" + "48b765916c67d75a80a5782b96eaba7a08720f1f31c287da5775410539dd4062d6584c8d1a084524" + "7fff79d0049e36c505f6f661b6ce2a038b7ad355a14673df4cf8c52e419ef2242764942135009133" + "e4f6d92502800412c37d467ec803b2f0a39040f787365619ee478c0ba0352db6e3fa321c66454e23" + "24776bc91ee6e7319af0c5b834a309b3d42d21ac39e36cd12e6562d5c5d43927f3be7e99679002b0" + "988b93afbfd7bb8f8a869bf40d0f2441aff91171fd50d32fbb42e52194c44eddb923a71c6c404c92" + "823209416fdb0431d514bf35f278523f96247d52886d1938399ed875066f719d5d6991bcc4f9c5d0" + "802a524da1b28c4fd3a751185b0d2301035489c6a4ba3177c5b1ffe144537eb4a273f2dcb7f11644" + "7e9ef9bb72548dc89334037e8150f7667f2a2769c81245781d" + ); + auto handshake = TestHandshake::runWithInput(keyB, auth); + BOOST_REQUIRE(handshake->completedKeyEstablishment()); + handshake->checkAuthValuesEIP8(4); +} + +BOOST_AUTO_TEST_CASE(test_handshake_eip8_auth2) +{ + auto keyB = Secret("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"); + auto auth = fromHex( + "019d04b1633f42b0051e14ba334ae669d5f779d8f5f68952654e47e3bf643be4cf5b27e43995ae16" + "2bb239f5248b8826b05048471085fa974b6b10e9600099d284c63964b3ac78b90b09a56e89b89441" + "2236577664932009511c1807311d3d6e4332703f6ed98a54b52f538328124e3f74be9a908ce9dcd6" + "a39b7d739e649ccd4e3d71a77f257590ce7e48032c4d7c58d82bca05f0a653f62a146b41635d9d8f" + "0fc12ae898c058e6d0108e6c5a30255010fb784e50b73d5028d9efe72ead48a38e81610f062365b4" + "eb2d413f0a83ed173cbdc3a26652d07e2425e801331050f564eb964ca45bfbaca8ffeed5e4a2e3b5" + "1c4ccf17bf58d66ab87a8ee35c8201cc6cb03f878e3c4625caec3ca9b74482c8b04a263c09ba743c" + "c789a13bf5af6c0cb18515d0741e9a433d37838da86067f6069df56b282dba63ddfdada72d8ef3c4" + "dd2041b01deb463b9044e2a40a9c3ea79426229d0f3d901587fd706d578d7be87b7e8fbefce38b69" + "2cce17ea20017f512eb76b51a148dc21bb546fc8c8629e2d68c1b80e70b6082a5923911dd064c966" + "80a71c8f25997068b709f1cbd399a1" + ); + auto handshake = TestHandshake::runWithInput(keyB, auth); + BOOST_REQUIRE(handshake->completedKeyEstablishment()); + handshake->checkAuthValuesEIP8(56); +} + +BOOST_AUTO_TEST_CASE(test_handshake_eip8_ack_plain) +{ + auto keyA = Secret("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee"); + auto ack = fromHex( + "049f8abcfa9c0dc65b982e98af921bc0ba6e4243169348a236abe9df5f93aa69d99cadddaa387662" + "b0ff2c08e9006d5a11a278b1b3331e5aaabf0a32f01281b6f4ede0e09a2d5f585b26513cb794d963" + "5a57563921c04a9090b4f14ee42be1a5461049af4ea7a7f49bf4c97a352d39c8d02ee4acc416388c" + "1c66cec761d2bc1c72da6ba143477f049c9d2dde846c252c111b904f630ac98e51609b3b1f58168d" + "dca6505b7196532e5f85b259a20c45e1979491683fee108e9660edbf38f3add489ae73e3dda2c71b" + "d1497113d5c755e942d1" + ); + auto initiatorPubk = NodeID("fda1cff674c90c9a197539fe3dfb53086ace64f83ed7c6eabec741f7f381cc803e52ab2cd55d5569bce4347107a310dfd5f88a010cd2ffd1005ca406f1842877"); + auto handshake = TestHandshake::runWithInput(keyA, ack, initiatorPubk); + BOOST_REQUIRE(handshake->completedKeyEstablishment()); + handshake->checkAckValuesEIP8(4); +} + +BOOST_AUTO_TEST_CASE(test_handshake_eip8_ack1) +{ + auto keyA = Secret("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee"); + auto ack = fromHex( + "019d04f43c232e6061efaa5fadb223e879eddce664706ff1764c6fe76dd482a9599a67dc5ae9a8fb" + "28eca063148ec619b227e0f70f676931d9a6ad1cd63c14bb611c13e9ff79957a93e13e1e509c2f12" + "1a3d69734fad4d1e6b5ff10d2d7d9b49d5beb5aff00f46ef4f20229bf9fb576203f418324e8724ba" + "b4190c99fe7912061a2c4cc1eb12221465a51ba753244eeeaf3fc789c3fc45cfc4674407f6b13f21" + "38a00b7a920f5c9bac8a6a01fc0b72ed3a59033c6fe855199b0ecf5f6abaea4ccc9da41a955c6f6e" + "8fcff6e7792033f7c0905e06ee1a47cb0cf88e00fdef70c1e217125f98cb3ef2c8f66cb8e940ed81" + "f7334c5a574c56ec3ed099cd98b5a2ba871ccbc819320b556709d199b382620d8b71f84234fe33b7" + "c95957bbbd4c1390a281502b8bf2ddf2e92631340b5aaeafb963fc62c96b2e98e93987bc197b2cfe" + "c08cdfe1aeb1da45fca5676e89725e897382e27ab2ae35de3e4733eaa9e76d9ca518ba074a9adf64" + "a28736a111137d346ca7743a5e0f4e7ab763d4331536ba8ef48163dc1dd5acede3a4b9b22ee31d3d" + "ac41e1f6cbfc78523449b3792b6523" + ); + auto initiatorPubk = NodeID("fda1cff674c90c9a197539fe3dfb53086ace64f83ed7c6eabec741f7f381cc803e52ab2cd55d5569bce4347107a310dfd5f88a010cd2ffd1005ca406f1842877"); + auto handshake = TestHandshake::runWithInput(keyA, ack, initiatorPubk); + BOOST_REQUIRE(handshake->completedKeyEstablishment()); + handshake->checkAckValuesEIP8(4); +} + +BOOST_AUTO_TEST_CASE(test_handshake_eip8_ack2) +{ + auto keyA = Secret("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee"); + auto ack = fromHex( + "021f04b8893a7c7a1d0cebc91ea85c40914b2d7115e577fa5cb5f140e2ef24d5fc296bd827e473ee" + "ddd8a23e98ac94d03519dcbf21155cc557bfdce8200af2d106cdfee31c97fe8362b354ed4e8ebff4" + "7c14eeeed1594e5ceb30e160214a7da9d5f4e48d746de32fd33995bbbd1d08cfc834f37ec111f049" + "0c4ac8e383c0b451a4aabffc08b0802a151329338849cede4669b8a117d55aba5aecf8d2c9c085e5" + "4f4bdf92c72e21e834bf62b6f161417cd1112319fcbe0f24e3a85ef5596e8e1acfbcc37de4db3b53" + "34351963feee5a41f40969e564406b2557934d89a6660191cea0c01bb2dcf6511cc0437ed75e0071" + "bf1ac12e0f848269aa617c72bc572e4d3604868f73d54d32e5c34908f28af6b1d6366f6ae0de8ea5" + "bec99fee0a8a26b2dad2fc5527f95bd2e3040ecc7575a8486b857c2f2b2d6b50cf263db786ba590f" + "31c59e02f86c9d501239ea21dbbbcd9f8a454d0431cb186a566086d02dfa599e21c8bd5caa578945" + "f32336e5e819e2c74ca529c764d668891057516e2fb9beb88fca439e569a373c330b1f4229012c1b" + "e8afb7e7821f6fa9714fb59907e81e33a054d062080c6770afa84348f26f200f47812f9e0d550553" + "c28fe35dbea39562968c86b9e85e7337cd758c1991a17b8890f5e7d305ac897829f4229cf616fdf4" + "36ebb1faf39300f6390c878d2ea46d4cf3498deb850825dab93cd9f53dd15fd7539d448a29c46196" + "3c38ba40e270057bfda5df3e1f1e0cb567f93860b203f5e559" + ); + auto initiatorPubk = NodeID("fda1cff674c90c9a197539fe3dfb53086ace64f83ed7c6eabec741f7f381cc803e52ab2cd55d5569bce4347107a310dfd5f88a010cd2ffd1005ca406f1842877"); + auto handshake = TestHandshake::runWithInput(keyA, ack, initiatorPubk); + BOOST_REQUIRE(handshake->completedKeyEstablishment()); + handshake->checkAckValuesEIP8(57); +} + +void TestHandshake::checkAuthValuesEIP8(uint64_t _expectedRemoteVersion) +{ + BOOST_CHECK_EQUAL(m_remote, Public("fda1cff674c90c9a197539fe3dfb53086ace64f83ed7c6eabec741f7f381cc803e52ab2cd55d5569bce4347107a310dfd5f88a010cd2ffd1005ca406f1842877")); + BOOST_CHECK_EQUAL(m_remoteNonce, h256("7e968bba13b6c50e2c4cd7f241cc0d64d1ac25c7f5952df231ac6a2bda8ee5d6")); + BOOST_CHECK_EQUAL(m_remoteEphemeral, Public("654d1044b69c577a44e5f01a1209523adb4026e70c62d1c13a067acabc09d2667a49821a0ad4b634554d330a15a58fe61f8a8e0544b310c6de7b0c8da7528a8d")); + BOOST_CHECK_EQUAL(m_remoteVersion, _expectedRemoteVersion); +} + +void TestHandshake::checkAckValuesEIP8(uint64_t _expectedRemoteVersion) +{ + BOOST_CHECK_EQUAL(m_remoteNonce, h256("559aead08264d5795d3909718cdd05abd49572e84fe55590eef31a88a08fdffd")); + BOOST_CHECK_EQUAL(m_remoteEphemeral, Public("b6d82fa3409da933dbf9cb0140c5dde89f4e64aec88d476af648880f4a10e1e49fe35ef3e69e93dd300b4797765a747c6384a6ecf5db9c2690398607a86181e4")); + BOOST_CHECK_EQUAL(m_remoteVersion, _expectedRemoteVersion); +} + +#define throwErrorCode(what, error) \ + { if (error) BOOST_THROW_EXCEPTION(Exception(what + _ec.message())); } + +shared_ptr TestHandshake::runWithInput(Secret _hostAlias, bytes _packet, NodeID _remoteID) +{ + // Spawn a listener which sends the packet to any client. + ba::io_service io; + bi::tcp::acceptor acceptor(io); + bi::tcp::endpoint endpoint(bi::address::from_string("127.0.0.1"), 0); + acceptor.open(endpoint.protocol()); + acceptor.bind(endpoint); + acceptor.listen(); + auto server = std::make_shared(io); + acceptor.async_accept(server->ref(), [_packet,server](boost::system::error_code const& _ec) + { + throwErrorCode("accept error: ", _ec); + ba::async_write(server->ref(), ba::buffer(_packet), [](const boost::system::error_code& _ec, std::size_t) + { + throwErrorCode("write error: ", _ec); + }); + }); + + // Spawn a client to execute the handshake. + auto host = make_shared("peer name", KeyPair(_hostAlias)); + auto client = make_shared(io); + shared_ptr handshake; + if (_remoteID == NodeID()) + handshake.reset(new TestHandshake(host.get(), client)); + else + handshake.reset(new TestHandshake(host.get(), client, _remoteID)); + + client->ref().async_connect(acceptor.local_endpoint(), [handshake](boost::system::error_code const& _ec) + { + throwErrorCode("connect error: ", _ec); + handshake->start(); + }); + + // Run all I/O to completion and return the finished handshake. + io.run(); + return handshake; +} + +void TestHandshake::transition(boost::system::error_code _ec) +{ + if (!_ec && m_nextState == ReadHello) + { + cancel(); + return; + } + RLPXHandshake::transition(_ec); +} + BOOST_AUTO_TEST_SUITE_END()