OverviewFilesCommitsIssuesDocsReleasesPeers

hyperswarm-peer / swarmd / test / test_control.cpp

169 lines · cpp ref:
// Gate: a real client, over the real socket, through librpc and the GENERATED
// dispatcher. docs/SWARMD-DESIGN.md §5.
//
// This is the proof that the codegen bet pays off end to end:
//
//   client encodes with swarmd_encode_hello  (generated)
//     -> UDS frame (uint32 LE length + payload)
//       -> rpc_decode_message                (librpc)
//         -> swarmd_hrpc_dispatch            (generated)
//           -> handler
//             -> reply encoded               (generated)
//               -> UDS frame back
//                 -> swarmd_decode_hello_response (generated)
//
// Not one byte of that is parsed by hand, which was the whole argument for not
// hand-rolling the protocol.

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>

#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#include "control.h"

extern "C" {
#include <compact.h>
#include <rpc.h>
}

static int checks = 0, failures = 0;
static void check(bool c, const char *what) {
	checks++;
	if (c) { printf("  PASS: %s\n", what); return; }
	failures++;
	printf("  FAIL: %s\n", what);
}

static int dial(const char *path) {
	int fd = socket(AF_UNIX, SOCK_STREAM, 0);
	struct sockaddr_un a;
	memset(&a, 0, sizeof(a));
	a.sun_family = AF_UNIX;
	strncpy(a.sun_path, path, sizeof(a.sun_path) - 1);
	if (connect(fd, (struct sockaddr *)&a, sizeof(a)) != 0) { close(fd); return -1; }
	return fd;
}

static void write_frame(int fd, const uint8_t *d, size_t n) {
	uint8_t hdr[4] = { (uint8_t)(n & 0xff), (uint8_t)((n >> 8) & 0xff),
					   (uint8_t)((n >> 16) & 0xff), (uint8_t)((n >> 24) & 0xff) };
	(void)!write(fd, hdr, 4);
	(void)!write(fd, d, n);
}

static bool read_frame(int fd, std::vector<uint8_t> &out) {
	uint8_t hdr[4];
	ssize_t r = read(fd, hdr, 4);
	if (r != 4) return false;
	const uint32_t n = (uint32_t)hdr[0] | ((uint32_t)hdr[1] << 8) |
					   ((uint32_t)hdr[2] << 16) | ((uint32_t)hdr[3] << 24);
	out.resize(n);
	size_t got = 0;
	while (got < n) {
		r = read(fd, out.data() + got, n - got);
		if (r <= 0) return false;
		got += (size_t)r;
	}
	return true;
}

int main() {
	char path[128];
	snprintf(path, sizeof(path), "/tmp/swarmd-control-%d.sock", (int)getpid());

	swarmd::UdsServer uds;
	if (!uds.listen(path)) { printf("listen failed\n"); return 1; }

	bool joined = false;
	uint8_t joined_topic[32] = { 0 };

	swarmd::ControlBackend backend;
	backend.status = [](swarmd_status_t *out) {
		out->dht_state = 2;          // up
		out->bootstrapped = true;
		out->peers_connected = 3;
		out->local_clients = 1;
	};
	backend.swarm_join = [&](const uint8_t topic[32], bool, bool) {
		joined = true;
		memcpy(joined_topic, topic, 32);
		return true;
	};

	swarmd::Control control;
	control.attach(&uds, backend);

	int fd = dial(path);
	check(fd >= 0, "client attached");
	uds.poll(50);

	// ---- HELLO, encoded by the generated client encoder ----
	swarmd_hello_request_t hello;
	memset(&hello, 0, sizeof(hello));
	const char *name = "gate";
	hello.name.data = (utf8_t *)name;
	hello.name.len = strlen(name);
	memset(hello.app_identity, 0x7e, 32);
	hello.version = 1;

	uint8_t *req = nullptr;
	size_t req_len = 0;
	check(swarmd_encode_hello(1, &hello, &req, &req_len) >= 0 && req != nullptr,
		  "generated encoder produced a request");
	write_frame(fd, req, req_len);
	free(req);   // caller frees

	for (int i = 0; i < 20; i++) uds.poll(5);
	check(control.hellos() == 1, "the dispatcher routed HELLO to its handler");

	const swarmd::Control::Session *s = control.session(1);
	check(s && s->hello, "the session records that HELLO happened");
	uint8_t want[32];
	memset(want, 0x7e, 32);
	check(s && memcmp(s->app_identity, want, 32) == 0,
		  "the app identity was COPIED OUT of the frame buffer, not aliased");
	check(s && s->name == "gate", "and so was the name");

	// ---- the reply comes back, decoded by the generated decoder ----
	std::vector<uint8_t> frame;
	check(read_frame(fd, frame), "a reply frame came back");
	if (!frame.empty()) {
		compact_state_t st = { 0, frame.size(), frame.data() };
		rpc_message_t msg;
		check(rpc_decode_message(&st, &msg) >= 0, "librpc decoded the reply");
		swarmd_hello_response_t res;
		hrpc_error_t err;
		memset(&res, 0, sizeof(res));
		check(swarmd_decode_hello_response(&msg, &res, &err) >= 0,
			  "the generated decoder read the response");
	}

	// ---- a second command, to prove dispatch is by command id ----
	swarmd_swarm_join_t sj;
	memset(&sj, 0, sizeof(sj));
	memset(sj.topic, 0x11, 32);
	sj.server = true;
	sj.client = true;
	uint8_t *req2 = nullptr;
	size_t req2_len = 0;
	swarmd_encode_swarm_join(2, &sj, &req2, &req2_len);
	write_frame(fd, req2, req2_len);
	free(req2);
	for (int i = 0; i < 20; i++) uds.poll(5);

	uint8_t want_topic[32];
	memset(want_topic, 0x11, 32);
	check(joined && memcmp(joined_topic, want_topic, 32) == 0,
		  "SWARM_JOIN reached the backend with the right topic");

	close(fd);
	uds.close_all();
	printf("\n%d/%d passed\n", checks - failures, checks);
	return failures ? 1 : 0;
}