// 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;
}