Skip to content

Commit c45f199

Browse files
authored
Merge branch 'master' into chore/cbind/ffi/07-service-disco
2 parents d806b8e + bfdaaa1 commit c45f199

10 files changed

Lines changed: 639 additions & 16 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
name: Mobile Android
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
pull_request:
8+
merge_group:
9+
workflow_dispatch:
10+
11+
concurrency:
12+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
13+
cancel-in-progress: true
14+
15+
jobs:
16+
cbind-ffi-android:
17+
timeout-minutes: 60
18+
runs-on: ubuntu-22.04
19+
strategy:
20+
fail-fast: false
21+
matrix:
22+
include:
23+
- abi: arm64-v8a
24+
attr: cbind-ffi-android-arm64-v8a
25+
machine: AArch64
26+
- abi: x86_64
27+
attr: cbind-ffi-android-x86_64
28+
machine: Advanced Micro Devices X86-64
29+
30+
defaults:
31+
run:
32+
shell: bash
33+
34+
name: "Android ${{ matrix.abi }} FFI check build"
35+
steps:
36+
- name: Checkout
37+
uses: actions/checkout@v6
38+
with:
39+
submodules: true
40+
41+
- name: Install Nix
42+
uses: cachix/install-nix-action@v31
43+
44+
- name: Build Android C ABI package
45+
run: |
46+
nix --extra-experimental-features "nix-command flakes" \
47+
build ".#${{ matrix.attr }}" \
48+
-o "result-${{ matrix.abi }}"
49+
50+
- name: Verify Android artifacts
51+
run: |
52+
out="result-${{ matrix.abi }}"
53+
54+
test -f "$out/lib/liblibp2p.so"
55+
test -f "$out/lib/liblibp2p.a"
56+
test -f "$out/lib/libc++_shared.so"
57+
test -f "$out/include/libp2p.h"
58+
test -f "$out/include/nim_ffi_cbor.h"
59+
test -f "$out/include/nim_ffi_prelude.h"
60+
test -f "$out/include/tinycbor/cbor.h"
61+
test -f "$out/include/cddl_bindings/libp2p.cddl"
62+
test -x "$out/bin/libp2p_ffi_android_check"
63+
64+
readelf -h "$out/lib/liblibp2p.so" | tee liblibp2p.readelf
65+
readelf -h "$out/bin/libp2p_ffi_android_check" | tee check.readelf
66+
67+
grep -q "Machine:[[:space:]]*${{ matrix.machine }}" liblibp2p.readelf
68+
grep -q "Machine:[[:space:]]*${{ matrix.machine }}" check.readelf
69+
70+
file "$out/lib/liblibp2p.so"
71+
file "$out/bin/libp2p_ffi_android_check"

.pinned

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ httputils;https://github.com/status-im/nim-http-utils@#f142cb2e8bd812dd002a6493b
77
json_serialization;https://github.com/status-im/nim-json-serialization@#a6dcf03e04e179127a5fcb7e495d19a821d56c17
88
metrics;https://github.com/status-im/nim-metrics@#b4b70a88fe1755d281366cbc3f22d7515240d192
99
nimcrypto;https://github.com/cheatfate/nimcrypto@#721fb99ee099b632eb86dfad1f0d96ee87583774
10-
lsquic;https://github.com/vacp2p/nim-lsquic@#186ec5b98747bdeaa98d3460b9e71823cac3b57a
10+
lsquic;https://github.com/vacp2p/nim-lsquic@#079768948ac03d8f2384e03045a090cb88aeb7d9
1111
results;https://github.com/arnetheduck/nim-results@#df8113dda4c2d74d460a8fa98252b0b771bf1f27
1212
secp256k1;https://github.com/status-im/nim-secp256k1@#d8f1288b7c72f00be5fc2c5ea72bf5cae1eafb15
1313
serialization;https://github.com/status-im/nim-serialization@#f80cfd8657f272a2abd063d070b77f2a74f704cd
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
// SPDX-License-Identifier: Apache-2.0 OR MIT
2+
// Copyright (c) Status Research & Development GmbH
3+
4+
#include <errno.h>
5+
#include <pthread.h>
6+
#include <stdbool.h>
7+
#include <stdio.h>
8+
#include <stdlib.h>
9+
#include <string.h>
10+
#include <time.h>
11+
12+
#include "libp2p.h"
13+
14+
enum {
15+
LIBP2P_FFI_MUXER_YAMUX = 1,
16+
LIBP2P_FFI_TRANSPORT_TCP = 1,
17+
CALLBACK_TIMEOUT_SECONDS = 20,
18+
};
19+
20+
typedef struct {
21+
pthread_mutex_t mutex;
22+
pthread_cond_t cond;
23+
int done;
24+
int err_code;
25+
char err_msg[512];
26+
LibP2PCtx *ctx;
27+
bool bool_reply;
28+
} CallbackWait;
29+
30+
static void wait_init(CallbackWait *wait) {
31+
memset(wait, 0, sizeof(*wait));
32+
wait->err_code = NIMFFI_RET_OK;
33+
if (pthread_mutex_init(&wait->mutex, NULL) != 0) {
34+
fprintf(stderr, "pthread_mutex_init failed\n");
35+
exit(2);
36+
}
37+
if (pthread_cond_init(&wait->cond, NULL) != 0) {
38+
fprintf(stderr, "pthread_cond_init failed\n");
39+
exit(2);
40+
}
41+
}
42+
43+
static void wait_destroy(CallbackWait *wait) {
44+
pthread_cond_destroy(&wait->cond);
45+
pthread_mutex_destroy(&wait->mutex);
46+
}
47+
48+
static void record_error(CallbackWait *wait, int err_code,
49+
const char *err_msg) {
50+
wait->err_code = err_code;
51+
if (err_msg != NULL && err_msg[0] != '\0') {
52+
snprintf(wait->err_msg, sizeof(wait->err_msg), "%s", err_msg);
53+
} else if (err_code != NIMFFI_RET_OK) {
54+
snprintf(wait->err_msg, sizeof(wait->err_msg), "FFI returned error code %d",
55+
err_code);
56+
}
57+
}
58+
59+
static void signal_done(CallbackWait *wait) {
60+
wait->done = 1;
61+
pthread_cond_signal(&wait->cond);
62+
}
63+
64+
static void on_created(int err_code, LibP2PCtx *ctx, const char *err_msg,
65+
void *user_data) {
66+
CallbackWait *wait = (CallbackWait *)user_data;
67+
pthread_mutex_lock(&wait->mutex);
68+
record_error(wait, err_code, err_msg);
69+
if (err_code == NIMFFI_RET_OK && ctx == NULL) {
70+
record_error(wait, -1, "create callback returned a null context");
71+
} else {
72+
wait->ctx = ctx;
73+
}
74+
signal_done(wait);
75+
pthread_mutex_unlock(&wait->mutex);
76+
}
77+
78+
static void on_bool_reply(int err_code, const bool *reply, const char *err_msg,
79+
void *user_data) {
80+
CallbackWait *wait = (CallbackWait *)user_data;
81+
pthread_mutex_lock(&wait->mutex);
82+
record_error(wait, err_code, err_msg);
83+
if (err_code == NIMFFI_RET_OK && reply == NULL) {
84+
record_error(wait, -1, "bool callback returned a null reply");
85+
} else if (reply != NULL) {
86+
wait->bool_reply = *reply;
87+
}
88+
signal_done(wait);
89+
pthread_mutex_unlock(&wait->mutex);
90+
}
91+
92+
static int wait_for_callback(CallbackWait *wait, const char *op) {
93+
struct timespec deadline;
94+
if (clock_gettime(CLOCK_REALTIME, &deadline) != 0) {
95+
fprintf(stderr, "%s: clock_gettime failed: %s\n", op, strerror(errno));
96+
return 1;
97+
}
98+
deadline.tv_sec += CALLBACK_TIMEOUT_SECONDS;
99+
100+
pthread_mutex_lock(&wait->mutex);
101+
int rc = 0;
102+
while (!wait->done && rc == 0) {
103+
rc = pthread_cond_timedwait(&wait->cond, &wait->mutex, &deadline);
104+
}
105+
106+
int err_code = wait->err_code;
107+
char err_msg[sizeof(wait->err_msg)];
108+
snprintf(err_msg, sizeof(err_msg), "%s", wait->err_msg);
109+
pthread_mutex_unlock(&wait->mutex);
110+
111+
if (rc == ETIMEDOUT) {
112+
fprintf(stderr, "%s: timed out after %d seconds\n", op,
113+
CALLBACK_TIMEOUT_SECONDS);
114+
return 1;
115+
}
116+
if (rc != 0) {
117+
fprintf(stderr, "%s: pthread_cond_timedwait failed: %s\n", op,
118+
strerror(rc));
119+
return 1;
120+
}
121+
if (err_code != NIMFFI_RET_OK) {
122+
fprintf(stderr, "%s: %s\n", op,
123+
err_msg[0] ? err_msg : "FFI callback failed");
124+
return 1;
125+
}
126+
return 0;
127+
}
128+
129+
static int create_node(LibP2PCtx **out_ctx) {
130+
Libp2pConfig config;
131+
memset(&config, 0, sizeof(config));
132+
133+
NimFfiStr listen_addrs[] = {nimffi_str("/ip4/127.0.0.1/tcp/0")};
134+
config.addrs.data = listen_addrs;
135+
config.addrs.len = 1;
136+
config.dnsResolver = nimffi_str("");
137+
config.transport = LIBP2P_FFI_TRANSPORT_TCP;
138+
config.muxer = LIBP2P_FFI_MUXER_YAMUX;
139+
config.maxConnections = 8;
140+
config.maxIn = 4;
141+
config.maxOut = 4;
142+
config.maxConnsPerPeer = 2;
143+
144+
CallbackWait create_wait;
145+
wait_init(&create_wait);
146+
int submit = libp2p_ctx_create(&config, on_created, &create_wait);
147+
if (submit != 0) {
148+
fprintf(stderr, "create: submit failed\n");
149+
wait_destroy(&create_wait);
150+
return 1;
151+
}
152+
if (wait_for_callback(&create_wait, "create") != 0) {
153+
wait_destroy(&create_wait);
154+
return 1;
155+
}
156+
157+
*out_ctx = create_wait.ctx;
158+
wait_destroy(&create_wait);
159+
return 0;
160+
}
161+
162+
static int call_bool_method(
163+
const char *op, LibP2PCtx *ctx,
164+
int (*fn)(const LibP2PCtx *,
165+
void (*)(int, const bool *, const char *, void *), void *)) {
166+
CallbackWait wait;
167+
wait_init(&wait);
168+
int submit = fn(ctx, on_bool_reply, &wait);
169+
if (submit != 0) {
170+
fprintf(stderr, "%s: submit failed\n", op);
171+
wait_destroy(&wait);
172+
return 1;
173+
}
174+
if (wait_for_callback(&wait, op) != 0) {
175+
wait_destroy(&wait);
176+
return 1;
177+
}
178+
if (!wait.bool_reply) {
179+
fprintf(stderr, "%s: expected true reply\n", op);
180+
wait_destroy(&wait);
181+
return 1;
182+
}
183+
wait_destroy(&wait);
184+
return 0;
185+
}
186+
187+
int main(void) {
188+
setvbuf(stdout, NULL, _IONBF, 0);
189+
setvbuf(stderr, NULL, _IONBF, 0);
190+
191+
LibP2PCtx *ctx = NULL;
192+
if (create_node(&ctx) != 0) {
193+
return 1;
194+
}
195+
196+
int rc = 0;
197+
if (call_bool_method("start", ctx, libp2p_ctx_start) != 0) {
198+
rc = 1;
199+
goto cleanup;
200+
}
201+
if (call_bool_method("stop", ctx, libp2p_ctx_stop) != 0) {
202+
rc = 1;
203+
goto cleanup;
204+
}
205+
206+
cleanup:
207+
libp2p_ctx_destroy(ctx);
208+
return rc;
209+
}

cbind/libp2p_ffi.nim

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ type LibP2P* = ref object
5050
topicHandlers: Table[string, TopicHandler]
5151
customProtocols: Table[string, LPProtocol]
5252
streams: StreamRegistry
53-
stopped: bool
53+
running: bool
5454

5555
declareLibrary("libp2p", LibP2P)
5656

@@ -319,11 +319,7 @@ proc createLibp2pNode(config: Libp2pConfig): Result[LibP2P, string] =
319319
return err("could not create libp2p node: " & e.msg)
320320

321321
let lib = LibP2P(
322-
switch: switch,
323-
rng: rng,
324-
relayClient: relayClientOpt,
325-
streams: StreamRegistry(),
326-
stopped: true,
322+
switch: switch, rng: rng, relayClient: relayClientOpt, streams: StreamRegistry()
327323
)
328324

329325
?mountProtocols(lib, cfg)
@@ -339,19 +335,19 @@ proc libp2pNew*(config: Libp2pConfig): Future[Result[LibP2P, string]] {.ffiCtor.
339335
proc shutdownSwitch(lib: LibP2P) {.async.} =
340336
## Single source of truth for graceful shutdown. Idempotent: safe to call from
341337
## both an explicit `libp2pStop` and `libp2pDestroy`'s teardown.
342-
if lib.stopped:
338+
if not lib.running:
343339
return
344-
lib.stopped = true
345340
await lib.switch.stop()
341+
lib.running = false
346342

347343
proc libp2pStart*(lib: LibP2P): Future[Result[bool, string]] {.ffi.} =
348-
if not lib.stopped:
344+
if lib.running:
349345
return ok(true)
350346
try:
351347
await lib.switch.start()
352348
except LPError as e:
353349
return err(e.msg)
354-
lib.stopped = false
350+
lib.running = true
355351
ok(true)
356352

357353
proc libp2pStop*(lib: LibP2P): Future[Result[bool, string]] {.ffi.} =

0 commit comments

Comments
 (0)