skip to content
==> src/cluster_conf.c <==
1/* cluster.rs: keys and topology. */
2#include "mesh/cluster_conf.h"
3#include "mesh/abi/peer_frames.h"
4#include "mesh/parse.h"
5#include "mesh/rust_fmt.h"
6#include "mesh/mt.h"
7
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11
12mcc_tunables mcc_tunables_default(void) { return (mcc_tunables){MESH_PEER_PING_MS, MESH_PEER_DEAD_MS, msw_default()}; }
13
14/* Rust's trim_matches('"'): every leading and trailing quote. */
15static char *unquote(const char *s) {
16    size_t n = strlen(s);
17    while (n && s[0] == '"') s++, n--;
18    while (n && s[n - 1] == '"') n--;
19    return mt_strndup(s, n);
20}
21
22bool mcc_keys(const char *text, ms_map *out, char **err) {
23    *out = (ms_map){0};
24    mt_strs lines = mt_lines(text);
25    bool ok = true;
26    for (size_t i = 0; i < lines.n && ok; i++) {
27        const char *raw = lines.v[i];
28        const char *hash = strchr(raw, '#');
29        char *pre = hash ? mt_strndup(raw, (size_t)(hash - raw)) : mt_strdup(raw);
30        char *line = mt_trim(pre);
31        free(pre);
32        char *k, *v;
33        if (!*line) {
34        } else if (!mt_split_once(line, '=', &k, &v)) {
35            char *q = rf_debug_str(raw);
36            *err = mt_fmt("line %zu: expected key = value, got %s", i + 1, q);
37            free(q);
38            ok = false;
39        } else {
40            char *kt = mt_trim(k), *vt = mt_trim(v);
41            char *vq = unquote(vt);
42            if (!*kt) {
43                *err = mt_fmt("line %zu: empty key", i + 1);
44                ok = false;
45            } else if (!ms_set(out, kt, vq, false)) {
46                *err = mt_fmt("line %zu: %s given twice", i + 1, kt);
47                ok = false;
48            }
49            free(k), free(v), free(kt), free(vt), free(vq);
50        }
51        free(line);
52    }
53    mt_strs_free(&lines);
54    if (!ok) ms_free(out);
55    return ok;
56}
57
58/* `k.peer` must be host:port (a non-empty host, a u16 port after the last ':'). */
59static bool peer_addr(const ms_map *m, uint32_t k, char **out, char **err) {
60    char key[32];
61    snprintf(key, sizeof key, "%u.peer", k);
62    const char *a = ms_get(m, key);
63    if (!a) {
64        *err = mt_fmt("cluster: no `%u.peer = host:port`", k);
65        return false;
66    }
67    const char *colon = strrchr(a, ':');
68    uint16_t port;
69    if (!colon || colon == a || !mp_u16(colon + 1, &port)) {
70        char *q = rf_debug_str(a);
71        *err = mt_fmt("cluster: %u.peer = %s: expected host:port", k, q);
72        free(q);
73        return false;
74    }
75    *out = mt_strdup(a);
76    return true;
77}
78
79static bool millis(const ms_map *m, const char *k, uint64_t d, uint64_t *out, char **err) {
80    const char *v = ms_get(m, k);
81    if (!v) {
82        *out = d;
83        return true;
84    }
85    if (mp_u64(v, out)) return true;
86    char *q = rf_debug_str(v);
87    *err = mt_fmt("cluster: %s = %s: milliseconds", k, q);
88    free(q);
89    return false;
90}
91
92bool mcc_topology_parse(const char *text, uint32_t me, mcc_topology *out, char **err) {
93    ms_map m;
94    if (!mcc_keys(text, &m, err)) return false;
95    char *peers[MESH_MAX_SYSTEMS] = {0};
96    uint32_t n = 0;
97    bool ok = false;
98    const char *sys = ms_get(&m, "systems");
99    if (!sys) {
100        *err = mt_strdup("cluster: no `systems = N`");
101        goto done;
102    }
103    if (!mp_u32(sys, &n)) {
104        *err = mt_strdup("cluster: systems: not a number");
105        goto done;
106    }
107    if (n < 2 || n > MESH_MAX_SYSTEMS) {
108        *err = mt_fmt("cluster: systems = %u: 2..=%u", n, (unsigned)MESH_MAX_SYSTEMS);
109        goto done;
110    }
111    if (me >= n) {
112        *err = mt_fmt("cluster: --system %u, but the cluster has systems 0..%u", me, n - 1);
113        goto done;
114    }
115    for (uint32_t k = 0; k < n; k++)
116        if (!peer_addr(&m, k, &peers[k], err)) goto done;
117    for (uint32_t i = 0; i < n; i++)
118        for (uint32_t j = 0; j < i; j++)
119            if (strcmp(peers[j], peers[i]) == 0) {
120                *err = mt_fmt("cluster: systems %u and %u share the peer address %s", j, i, peers[i]);
121                goto done;
122            }
123    mcc_topology t = {.me = me, .n = n};
124    if (me > 0) {
125        char key[32];
126        snprintf(key, sizeof key, "%u.listen", me);
127        const char *l = ms_get(&m, key);
128        t.listen = mt_strdup(l ? l : peers[me]);
129    }
130    for (uint32_t j = me + 1; j < n; j++) {
131        t.dial_k[t.ndial] = j;
132        t.dial_addr[t.ndial++] = mt_strdup(peers[j]);
133    }
134    if (!millis(&m, "ping_ms", MESH_PEER_PING_MS, &t.tun.ping_ms, err) || !millis(&m, "dead_ms", MESH_PEER_DEAD_MS, &t.tun.dead_ms, err) ||
135        !msw_from_keys(&m, &t.tun.sched, err)) {
136        mcc_topology_free(&t);
137        goto done;
138    }
139    if (t.tun.ping_ms == 0 || t.tun.dead_ms < 3 * t.tun.ping_ms) {
140        *err = mt_fmt("cluster: dead_ms %llu must be at least 3 x ping_ms %llu", (unsigned long long)t.tun.dead_ms, (unsigned long long)t.tun.ping_ms);
141        mcc_topology_free(&t);
142        goto done;
143    }
144    *out = t;
145    ok = true;
146done:
147    for (uint32_t k = 0; k < MESH_MAX_SYSTEMS; k++) free(peers[k]);
148    ms_free(&m);
149    return ok;
150}
151
152mcc_topology mcc_pair_dialer(const char *peer) {
153    mcc_topology t = {.me = 0, .n = 2, .ndial = 1, .tun = mcc_tunables_default()};
154    t.dial_k[0] = 1;
155    t.dial_addr[0] = mt_strdup(peer);
156    return t;
157}
158
159mcc_topology mcc_pair_listener(const char *addr) {
160    return (mcc_topology){.me = 1, .n = 2, .listen = mt_strdup(addr), .tun = mcc_tunables_default()};
161}
162
163bool mcc_topology_eq(const mcc_topology *a, const mcc_topology *b) {
164    if (a->me != b->me || a->n != b->n || !mt_eq(a->listen, b->listen) || a->ndial != b->ndial) return false;
165    for (size_t i = 0; i < a->ndial; i++)
166        if (a->dial_k[i] != b->dial_k[i] || strcmp(a->dial_addr[i], b->dial_addr[i]) != 0) return false;
167    return a->tun.ping_ms == b->tun.ping_ms && a->tun.dead_ms == b->tun.dead_ms && msw_eq(&a->tun.sched, &b->tun.sched);
168}
169
170void mcc_topology_free(mcc_topology *t) {
171    free(t->listen);
172    for (size_t i = 0; i < t->ndial; i++) free(t->dial_addr[i]);
173    *t = (mcc_topology){0};
174}