From a16b33031c7bcf2eabf1e0c3571000234b7740df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Smr=C5=BE?= Date: Sun, 24 Jan 2021 22:46:48 +0100 Subject: Attach service --- src/pairing.cpp | 212 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 src/pairing.cpp (limited to 'src/pairing.cpp') diff --git a/src/pairing.cpp b/src/pairing.cpp new file mode 100644 index 0000000..9e161df --- /dev/null +++ b/src/pairing.cpp @@ -0,0 +1,212 @@ +#include + +#include "service.h" + +#include + +#include + +#include +#include +#include +#include + +using namespace erebos; + +using std::lock_guard; +using std::runtime_error; + +void PairingServiceBase::onRequestInit(RequestInitHook hook) +{ + lock_guard lock(stateLock); + requestInitHook = hook; +} + +void PairingServiceBase::onResponse(ConfirmHook hook) +{ + lock_guard lock(stateLock); + responseHook = hook; +} + +void PairingServiceBase::onRequest(ConfirmHook hook) +{ + lock_guard lock(stateLock); + requestHook = hook; +} + +void PairingServiceBase::onRequestNonceFailed(RequestNonceFailedHook hook) +{ + lock_guard lock(stateLock); + requestNonceFailedHook = hook; +} + +void PairingServiceBase::handle(Context & ctx) +{ + auto rec = ctx.ref()->asRecord(); + if (!rec) + return; + + auto pid = ctx.peer().identity(); + if (!pid) + throw runtime_error("Pairing request for peer without known identity"); + + lock_guard lock(stateLock); + auto & state = peerStates.try_emplace(ctx.peer(), State()).first->second; + + if (auto request = rec->item("request").asBinary()) { + if (state.phase != StatePhase::NoPairing) + return; + + if (requestInitHook) + requestInitHook(ctx.peer()); + + state.phase = StatePhase::PeerRequest; + state.peerCheck = *request; + state.nonce.resize(32); + RAND_bytes(state.nonce.data(), state.nonce.size()); + + ctx.peer().send(uuid(), Object(Record({ + { "response", state.nonce }, + }))); + } + + else if (auto response = rec->item("response").asBinary()) { + if (state.phase != StatePhase::OurRequest) { + fprintf(stderr, "Unexpected pairing response.\n"); // TODO + return; + } + + if (responseHook) { + string confirm = confirmationNumber(nonceDigest( + ctx.peer().server().identity(), *pid, + state.nonce, *response)); + std::thread(&PairingServiceBase::waitForConfirmation, + this, ctx.peer(), confirm).detach(); + } + + state.phase = StatePhase::OurRequestConfirm; + + ctx.peer().send(uuid(), Object(Record({ + { "reqnonce", state.nonce }, + }))); + } + + else if (auto reqnonce = rec->item("reqnonce").asBinary()) { + auto check = nonceDigest( + *pid, ctx.peer().server().identity(), + *reqnonce, vector()); + if (check != state.peerCheck) { + if (requestNonceFailedHook) + requestNonceFailedHook(ctx.peer()); + state.phase = StatePhase::PairingFailed; + return; + } + + if (requestHook) { + string confirm = confirmationNumber(nonceDigest( + *pid, ctx.peer().server().identity(), + *reqnonce, state.nonce)); + std::thread(&PairingServiceBase::waitForConfirmation, + this, ctx.peer(), confirm).detach(); + } + + state.phase = StatePhase::PeerRequestConfirm; + } + + else { + if (state.phase == StatePhase::OurRequestReady) { + handlePairingResult(ctx); + state.phase = StatePhase::PairingDone; + } else { + result = ctx.ref(); + } + } +} + +void PairingServiceBase::requestPairing(UUID serviceId, const Peer & peer) +{ + auto pid = peer.identity(); + if (!pid) + throw runtime_error("Pairing request for peer without known identity"); + + lock_guard lock(stateLock); + auto & state = peerStates.try_emplace(peer, State()).first->second; + + state.phase = StatePhase::OurRequest; + state.nonce.resize(32); + RAND_bytes(state.nonce.data(), state.nonce.size()); + + vector items; + items.emplace_back("request", nonceDigest( + peer.server().identity(), *pid, + state.nonce, vector())); + + peer.send(serviceId, Object(Record(std::move(items)))); +} + +vector PairingServiceBase::nonceDigest(const Identity & id1, const Identity & id2, + const vector & nonce1, const vector & nonce2) +{ + vector items; + items.emplace_back("id", id1.ref().value()); + items.emplace_back("id", id2.ref().value()); + items.emplace_back("nonce", nonce1); + items.emplace_back("nonce", nonce2); + + const auto arr = Digest::of(Object(Record(std::move(items)))).arr(); + vector ret(arr.size()); + std::copy_n(arr.begin(), arr.size(), ret.begin()); + return ret; +} + +string PairingServiceBase::confirmationNumber(const vector & digest) +{ + uint32_t confirm; + memcpy(&confirm, digest.data(), sizeof(confirm)); + string ret(7, '\0'); + snprintf(ret.data(), ret.size(), "%06d", ntohl(confirm) % 1000000); + return ret; +} + +void PairingServiceBase::waitForConfirmation(Peer peer, string confirm) +{ + ConfirmHook hook; + { + lock_guard lock(stateLock); + auto & state = peerStates.try_emplace(peer, State()).first->second; + if (state.phase == StatePhase::OurRequestConfirm) + hook = responseHook; + if (state.phase == StatePhase::PeerRequestConfirm) + hook = requestHook; + } + + bool ok = hook(peer, confirm).get(); + + lock_guard lock(stateLock); + auto & state = peerStates.try_emplace(peer, State()).first->second; + + if (ok) { + if (state.phase == StatePhase::OurRequestConfirm) { + if (result) { + peer.server().localHead().update([&] (const Stored & local) { + Service::Context ctx(new Service::Context::Priv { + .ref = *result, + .peer = peer, + .local = local, + }); + + handlePairingResult(ctx); + return ctx.local(); + }); + state.phase = StatePhase::PairingDone; + } else { + state.phase = StatePhase::OurRequestReady; + } + } else if (state.phase == StatePhase::PeerRequestConfirm) { + peer.send(uuid(), handlePairingCompleteRef(peer)); + state.phase = StatePhase::PairingDone; + } + } else { + state.phase = StatePhase::PairingFailed; + } +} -- cgit v1.2.3