Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cbind/cbind.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ task examples, "Build and run the C bindings examples":
cborObjs.add obj
let cborObjsStr = cborObjs.join(" ")

for example in ["echo", "gossipsub", "kad"]:
for example in ["echo", "gossipsub", "kad", "relay", "peerstore", "metrics"]:
let outBin = "../build/" & example
exec "gcc -std=c11 -O2 -I c_bindings -I " & vendor & " examples/" & example & ".c " &
cborObjsStr & " " & lib & " -pthread -Wl,-rpath,'$ORIGIN' -o " & outBin
Expand Down
2 changes: 1 addition & 1 deletion cbind/examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ set_property(TARGET tinycbor PROPERTY POSITION_INDEPENDENT_CODE ON)

find_package(Threads REQUIRED)

foreach(example echo gossipsub kad)
foreach(example echo gossipsub kad relay peerstore metrics)
add_executable(${example} "${example}.c")
target_include_directories(${example} PRIVATE "${GEN_DIR}")
target_link_libraries(${example} PRIVATE "${LIB_FILE}" tinycbor Threads::Threads)
Expand Down
6 changes: 6 additions & 0 deletions cbind/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ The bindings are a header-only C API over a C ABI. Every generated method is asy
- `echo.c` — two TCP nodes; the server mounts a custom `/cbind/echo/1.0.0` protocol and echoes the bytes it reads, the client dials it and verifies the round-trip. The incoming-stream handler only hands the stream id to `main`, which serves it inline, since calling back into the library from the dispatch thread would deadlock.
- `gossipsub.c` — two TCP nodes join a shared topic and exchange one message, delivered to the subscriber through its pub/sub-message listener.
- `kad.c` — two TCP nodes form a Kademlia DHT (a server, and a client that lists the server as its bootstrap node), then round-trip a value (`kad_put_value` / `kad_get_value`) and a provider record (`create_cid`, `kad_start_providing` / `kad_get_providers`) over the wire.
- `relay.c` — three TCP nodes: a relay, a destination and a source. The destination reserves a slot on the relay (`circuit_relay_reserve`); the source reaches it over a `/p2p-circuit` address (`dial_circuit_relay`) and sends a message the destination reads off its relayed stream.
- `peerstore.c` — drives one node's peerstore directly from a second node's PeerInfo: `peerstore_add_peer`, `peerstore_get_peers`, `peerstore_get_peer_info`, then `peerstore_delete_peer`. No dialing required.
- `metrics.c` — a running node dumps the process-wide Prometheus registry as JSON via `collect_metrics`.

## Build

Expand Down Expand Up @@ -38,6 +41,9 @@ cmake -S . -B build -DTINYCBOR_VENDOR=$(nimble path ffi)/ffi/codegen/templates/c
./build/echo
./build/gossipsub
./build/kad
./build/relay
./build/peerstore
./build/metrics
```

Each program exits `0` on success and prints the values it exchanged over the wire.
75 changes: 75 additions & 0 deletions cbind/examples/metrics.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Metrics: a running node dumps the process-wide Prometheus registry as JSON
// via libp2p_ctx_collect_metrics. The registry is global, so one started node
// is enough to populate it. Calls are made blocking with the helpers in
// common.h.
#include "common.h"

// TransportType / MuxerType ordinals, mirrored from libp2p_ffi/config.nim.
static const int64_t TransportTcp = 1;
static const int64_t MuxerMplex = 0;

// collect_metrics replies with a JSON string (Result[string]).
typedef struct {
atomic_int done;
int err_code;
char err[256];
char json[1 << 16];
size_t len;
} MetricsWaiter;

static void on_metrics(int ec, const NimFfiStr *reply, const char *em,
void *ud) {
MetricsWaiter *w = (MetricsWaiter *)ud;
w->err_code = ec;
if (reply && reply->data) {
w->len =
reply->len < sizeof(w->json) - 1 ? reply->len : sizeof(w->json) - 1;
memcpy(w->json, reply->data, w->len);
w->json[w->len] = '\0';
}
if (em)
snprintf(w->err, sizeof(w->err), "%s", em);
atomic_store(&w->done, 1);
}

int main(void) {
NimFfiStr addr = nimffi_str("/ip4/127.0.0.1/tcp/5041");
Libp2pConfig cfg;
memset(&cfg, 0, sizeof(cfg));
cfg.addrs.data = &addr;
cfg.addrs.len = 1;
cfg.muxer = MuxerMplex;
cfg.transport = TransportTcp;

LibP2PCtx *node = await_create(&cfg, "node");
if (!node)
return 1;

int status = 1;
BoolWaiter bw;
if (!AWAIT_BOOL(bw, libp2p_ctx_start(node, on_bool, &bw), "start"))
goto cleanup;

MetricsWaiter mw;
memset(&mw, 0, sizeof(mw));
libp2p_ctx_collect_metrics(node, on_metrics, &mw);
if (!wait_done(&mw.done) || mw.err_code != 0) {
fprintf(stderr, "collect_metrics: %s\n", mw.err[0] ? mw.err : "unknown");
goto cleanup;
}

// A JSON array of {name,type,help,labels,value,timestamp} objects; print a
// prefix rather than the whole document.
printf("Collected %zu bytes of metrics\n", mw.len);
printf("%.400s%s\n", mw.json, mw.len > 400 ? " ..." : "");

if (mw.json[0] == '[' && strstr(mw.json, "\"name\""))
status = 0;
else
fprintf(stderr, "Error: metrics payload was empty or malformed\n");

cleanup:
AWAIT_BOOL(bw, libp2p_ctx_stop(node, on_bool, &bw), "stop");
libp2p_ctx_destroy(node);
return status;
}
161 changes: 161 additions & 0 deletions cbind/examples/peerstore.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Peerstore: a node's local record of other peers. This drives the store
// directly — no dialing — using a second node's real PeerInfo: add the peer,
// list it, read it back, then delete it. Calls are made blocking via common.h.
#include "common.h"

// TransportType / MuxerType ordinals, mirrored from libp2p_ffi/config.nim.
static const int64_t TransportTcp = 1;
static const int64_t MuxerMplex = 0;

static const char *Proto = "/cbind/peerstore/1.0.0";

// get_peers replies with a PeersResponse of peer-id strings.
#define MAX_PEERS 16
typedef struct {
atomic_int done;
int err_code;
char err[256];
char peerIds[MAX_PEERS][128];
size_t n;
} PeersWaiter;

static void on_peers(int ec, const PeersResponse *reply, const char *em,
void *ud) {
PeersWaiter *w = (PeersWaiter *)ud;
w->err_code = ec;
if (reply) {
w->n = reply->peerIds.len < MAX_PEERS ? reply->peerIds.len : MAX_PEERS;
for (size_t i = 0; i < w->n; i++)
if (reply->peerIds.data[i].data)
snprintf(w->peerIds[i], sizeof(w->peerIds[i]), "%s",
reply->peerIds.data[i].data);
}
if (em)
snprintf(w->err, sizeof(w->err), "%s", em);
atomic_store(&w->done, 1);
}

static bool get_peers(LibP2PCtx *ctx, PeersWaiter *w) {
memset(w, 0, sizeof(*w));
libp2p_ctx_peerstore_get_peers(ctx, on_peers, w);
if (!wait_done(&w->done) || w->err_code != 0) {
fprintf(stderr, "get_peers: %s\n", w->err[0] ? w->err : "unknown");
return false;
}
return true;
}

static bool holds(const PeersWaiter *w, const char *peerId) {
for (size_t i = 0; i < w->n; i++)
if (strcmp(w->peerIds[i], peerId) == 0)
return true;
return false;
}

// get_peer_info replies with the full entry; we only report its counts here.
typedef struct {
atomic_int done;
int err_code;
char err[256];
size_t naddrs, nprotocols;
} EntryWaiter;

static void on_entry(int ec, const PeerStoreEntryResponse *reply,
const char *em, void *ud) {
EntryWaiter *w = (EntryWaiter *)ud;
w->err_code = ec;
if (reply) {
w->naddrs = reply->addrs.len;
w->nprotocols = reply->protocols.len;
}
if (em)
snprintf(w->err, sizeof(w->err), "%s", em);
atomic_store(&w->done, 1);
}

static LibP2PCtx *createNode(const char *addr, const char *label) {
NimFfiStr slot = nimffi_str(addr);
Libp2pConfig cfg;
memset(&cfg, 0, sizeof(cfg));
cfg.addrs.data = &slot;
cfg.addrs.len = 1;
cfg.muxer = MuxerMplex;
cfg.transport = TransportTcp;
return await_create(&cfg, label);
}

int main(void) {
int status = 1;
BoolWaiter bw;

// `local` owns the peerstore we drive; `other` just hands us a real peer id
// and listen addresses to store.
LibP2PCtx *local = createNode("/ip4/127.0.0.1/tcp/5051", "local");
LibP2PCtx *other = createNode("/ip4/127.0.0.1/tcp/5052", "other");
if (!local || !other) {
libp2p_ctx_destroy(local);
libp2p_ctx_destroy(other);
return 1;
}

// Start `other` so its listen addresses are bound and reported by peer_info.
if (!AWAIT_BOOL(bw, libp2p_ctx_start(other, on_bool, &bw), "start other"))
goto cleanup;
PeerInfoWaiter oi;
if (!await_peerinfo(other, &oi, "other peerinfo"))
goto cleanup;
printf("Peer to store: %s\n", oi.peerId);

// add_peer: merge the peer's addresses and a protocol into the store.
NimFfiStr addrs[MAX_ADDRS];
for (size_t i = 0; i < oi.naddrs; i++)
addrs[i] = nimffi_str(oi.addrs[i]);
NimFfiStr proto = nimffi_str(Proto);
AddPeerRequest addReq = {
nimffi_str(oi.peerId), {addrs, oi.naddrs}, {&proto, 1}};
if (!AWAIT_BOOL(bw,
libp2p_ctx_peerstore_add_peer(local, &addReq, on_bool, &bw),
"add_peer"))
goto cleanup;

// get_peers + get_peer_info: the peer is now listed and readable.
PeersWaiter peers;
if (!get_peers(local, &peers))
goto cleanup;
EntryWaiter entry;
memset(&entry, 0, sizeof(entry));
libp2p_ctx_peerstore_get_peer_info(local, nimffi_str(oi.peerId), on_entry,
&entry);
if (!wait_done(&entry.done) || entry.err_code != 0) {
fprintf(stderr, "get_peer_info: %s\n",
entry.err[0] ? entry.err : "unknown");
goto cleanup;
}
printf("Stored: %zu peer(s), entry has %zu address(es) and %zu protocol(s)\n",
peers.n, entry.naddrs, entry.nprotocols);
if (!holds(&peers, oi.peerId) || entry.naddrs == 0 || entry.nprotocols == 0) {
fprintf(stderr, "Error: peer was not stored correctly\n");
goto cleanup;
}

// delete_peer: the store no longer holds it.
if (!AWAIT_BOOL(bw,
libp2p_ctx_peerstore_delete_peer(local, nimffi_str(oi.peerId),
on_bool, &bw),
"delete_peer"))
goto cleanup;
if (!get_peers(local, &peers))
goto cleanup;
if (holds(&peers, oi.peerId)) {
fprintf(stderr, "Error: peer still present after delete_peer\n");
goto cleanup;
}
printf("Deleted %s\n", oi.peerId);
status = 0;

cleanup:
AWAIT_BOOL(bw, libp2p_ctx_stop(other, on_bool, &bw), "stop other");
libp2p_ctx_destroy(other);
libp2p_ctx_destroy(local);
return status;
}
Loading
Loading