OverviewFilesCommitsIssuesDocsReleasesPeers

hyperswarm-peer / test / test_overlay_late_attach.cpp

91 lines · cpp ref:
// A peer that meshes BEFORE its overlay attaches must still get a channel.
//
// Build: ./build_tests.sh && ./test_overlay_late_attach
//
// WHY THIS EXISTS. Layer 13's third peer joins the topic and only then opens and
// attaches its overlay — which is the ordinary case in the game, where the swarm
// is up long before anyone asks for an asset. The already-attached peers open
// their overlay channel towards it the moment the connection forms, the newcomer
// has no handler for the protocol yet, and protomux rejects. Nothing re-opened
// afterwards: the newcomer's own Open arrived at a peer whose OverlayTransport
// still held a pointer to the channel that had just been rejected, so its pair()
// callback — the one mechanism built for exactly this race — declined to act:
//
//     if (!channel_) open_channel();   // channel_ non-null, though the channel is closed
//
// The JS reference has no such state to go stale: hyper-overlay's peer entry is
// keyed by mux and DELETED in onclose (lib/protocol.js), so the pair callback's
// re-entry into attach(mux) always creates a fresh channel. The port kept the
// entry and the pointer.
//
// Two muxes over a loopback, no sockets, no threads, no DHT — the ordering is the
// whole bug, so it is provoked at the layer the ordering lives in. A real
// Protocol is not needed and is deliberately absent: what is asserted here is
// that a channel exists and that the owner is told about it. That the bytes then
// move is layer 13's claim, not this one's.

#include "overlay_transport.h"

#include <cstdio>
#include <string>
#include <vector>

using hyperswarm::OverlayTransport;

static int tests_run = 0;
static int tests_passed = 0;

static void check(const char *name, bool ok) {
	tests_run++;
	if (ok) {
		tests_passed++;
		printf("  PASS  %s\n", name);
	} else {
		printf("  FAIL  %s\n", name);
	}
}

int main() {
	printf("=== Overlay late attach ===\n");

	Protomux a, b;
	a.set_write_callback([&b](const uint8_t *d, size_t n) { b.recv(d, n); return true; });
	b.set_write_callback([&a](const uint8_t *d, size_t n) { a.recv(d, n); return true; });

	// ---- A is meshed with its overlay already attached. B is not. ----
	// This is the holder in layer 13's Test C, and B is the third peer, which has
	// joined the swarm but has not opened its overlay yet.
	bool a_joined_pool = false;
	OverlayTransport ta(&a, nullptr, "A",
			[&a_joined_pool](OverlayTransport *) { a_joined_pool = true; });

	// Read from the mux, because the transport no longer answers it: a channel WAS
	// created (the slot exists) and is now closed (the slot is empty), which is the
	// reject, and has_channel() reporting false for it is the fix itself.
	check("A opened an overlay channel on attach", a.local_channels().size() == 1);
	check("B has no overlay yet, so A's channel is rejected",
			a.local_channels()[0] == nullptr && !ta.ready());
	check("A forgot the rejected channel", !ta.has_channel());
	check("A did not join the pool on a rejected channel", !a_joined_pool);

	// ---- B attaches. THE LINE UNDER TEST IS EVERYTHING BELOW THIS ONE. ----
	bool b_joined_pool = false;
	OverlayTransport tb(&b, nullptr, "B",
			[&b_joined_pool](OverlayTransport *) { b_joined_pool = true; });

	check("the late overlay's channel opens", tb.ready());
	check("the late overlay joins its own pool", b_joined_pool);
	check("the peer that rejected it re-opens its side", ta.ready());
	check("and that peer rejoins its pool, so a fetch can reach it", a_joined_pool);

	// A channel both sides call open on is a channel both sides can write on. send()
	// refuses while `opened` is false, which is how a re-paired channel that never
	// actually opened would still look fine above.
	const std::vector<uint8_t> frame(8, 0x7f);
	check("A can send on the re-opened channel", ta.send(hyperoverlay::MsgId::SyncState, frame));
	check("B can send on it too", tb.send(hyperoverlay::MsgId::SyncState, frame));

	printf("\n=== %d/%d passed ===\n", tests_passed, tests_run);
	return tests_passed == tests_run ? 0 : 1;
}