Files
utun2/tools/chatgui-android/jni_bridge/android_ble.c
T
evgeny ba1c4cf160 Add cross-platform BLE transport and Bluetooth settings
Integrate shared C transport with desktop and Android frontends, add radio probes and detailed Windows diagnostics, and fix GUI build dependencies and early configuration startup errors.
2026-10-29 10:51:19 +01:00

273 lines
14 KiB
C

/* Android backend общего BLE C API. JNI события копируются в ограниченный mailbox;
* eventfd будит единственный uasync owner. Mutex защищает active/очередь/fd от teardown.
* Java commands: 0 stop, 1 start, 2 advertise, 3 connect, 4 close, 5 send, 6 incoming,
* 7 status UI, 8 advertising UI. Events: 1 ready, 2 opened, 3 received, 4 sent,
* 5 closed, 6 broadcast, 7 advertising, 8 failed. token < 0 — входящий Java link.
* Flags настроек: 1 enabled, 2 advertise, 4 incoming; 8 echo/16 dial — instrumentation.
* JVM class reference живёт до завершения процесса. Все transport handles — только worker. */
#include <jni.h>
#include <pthread.h>
#include <string.h>
#include <sys/eventfd.h>
#include <unistd.h>
#include <errno.h>
#include "ble/ble_backend.h"
#include "ble/ble_identity.h"
#include "u_async.h"
#include "mem.h"
#include "debug_config.h"
#include "instance_lite.h"
#include "utun_instance.h"
#include "config_parser.h"
#include "android_ble.h"
#define CAT DEBUG_CATEGORY_DISCOVERY
#define EVENTS 256
struct event { jlong token; int type, arg, len; char text[256]; uint8_t data[512]; };
struct android_link { ble_link_id id; jlong token; };
struct ble_backend {
struct UASYNC* ua;
struct ble_transport* owner;
void* io;
int wake_fd;
jlong generation;
struct android_link links[BLE_LINKS_MAX];
struct event events[EVENTS];
unsigned head, count;
int overflow;
};
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static struct ble_backend* active;
static jlong generation;
static JavaVM* vm;
static jclass radio_class;
static jmethodID command_method;
static struct ble_transport* transport; /* Только worker. */
static int policy;
static int incoming_policy;
static int radio_ready;
static uint8_t identity[24];
static int identity_len;
static int command(int op, jlong gen, jlong token, const char* text, const uint8_t* bytes, size_t len, int arg) {
JNIEnv* env = NULL; int attached = 0, ok = 0;
if (!vm || !radio_class) { DEBUG_ERROR(CAT, "BLE JNI is not installed"); return -1; }
if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
if ((*vm)->AttachCurrentThread(vm, &env, NULL) != JNI_OK) { DEBUG_ERROR(CAT, "BLE JNI attach failed"); return -1; }
attached = 1;
}
jstring string = (*env)->NewStringUTF(env, text ? text : "");
jbyteArray data = string ? (*env)->NewByteArray(env, (jsize)len) : NULL;
if (string && data) {
if (len) (*env)->SetByteArrayRegion(env, data, 0, (jsize)len, (const jbyte*)bytes);
if (!(*env)->ExceptionCheck(env)) (*env)->CallStaticVoidMethod(env, radio_class, command_method, op, gen, token, string, data, arg);
ok = !(*env)->ExceptionCheck(env);
}
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); DEBUG_ERROR(CAT, "BLE JNI command failed op=%d", op); }
if (string) (*env)->DeleteLocalRef(env, string);
if (data) (*env)->DeleteLocalRef(env, data);
if (attached) (*vm)->DetachCurrentThread(vm);
return ok ? 0 : -1;
}
static struct android_link* by_id(struct ble_backend* b, ble_link_id id) {
for (int i = 0; i < BLE_LINKS_MAX; i++) if (b->links[i].id == id) return &b->links[i];
return NULL;
}
static struct android_link* by_token(struct ble_backend* b, jlong token) {
for (int i = 0; i < BLE_LINKS_MAX; i++) if (b->links[i].id && b->links[i].token == token) return &b->links[i];
return NULL;
}
/* JNI держит lock до записи: detach не может закрыть/reuse fd под отправителем. */
static void wake(struct ble_backend* b) {
uint64_t value = 1; ssize_t n;
do { n = write(b->wake_fd, &value, sizeof(value)); } while (n < 0 && errno == EINTR);
if (n < 0 && errno != EAGAIN) DEBUG_ERROR(CAT, "BLE eventfd write failed: %s", strerror(errno));
}
/* Одна ограниченная порция событий; eventfd будит uasync сразу, без задержки polling-таймера. */
static void drain(int fd, void* arg) {
struct ble_backend* b = arg;
uint64_t value; ssize_t n;
do { n = read(fd, &value, sizeof(value)); } while (n < 0 && errno == EINTR);
if (n < 0 && errno != EAGAIN) {
DEBUG_ERROR(CAT, "BLE eventfd read failed: %s", strerror(errno));
ble_backend_status(b->owner, 0, "eventfd read failed"); return;
}
for (int i = 0; i < EVENTS; i++) {
struct event e; int available, overflow;
pthread_mutex_lock(&lock);
available = b->count != 0; overflow = b->overflow; b->overflow = 0;
if (available) { e = b->events[b->head]; b->head = (b->head + 1) % EVENTS; b->count--; }
pthread_mutex_unlock(&lock);
if (overflow) {
pthread_mutex_lock(&lock); b->head = b->count = 0; pthread_mutex_unlock(&lock);
command(0, b->generation, 0, NULL, NULL, 0, 0);
ble_backend_status(b->owner, 0, "Android event mailbox full"); break;
}
if (!available) break;
struct android_link* l = by_token(b, e.token);
struct ble_endpoint ep; memcpy(ep.id, e.text, sizeof(ep.id));
switch (e.type) {
case 1: ble_backend_status(b->owner, 1, "Android scan/GATT ready"); break;
case 2:
if (!l && e.token < 0) {
l = by_id(b, 0);
ble_link_id id = l ? ble_backend_incoming(b->owner, &ep) : 0;
if (!id) { command(4, b->generation, e.token, NULL, NULL, 0, 0); break; }
l->id = id; l->token = e.token;
}
if (l) ble_backend_opened(b->owner, l->id, e.arg);
break;
case 3: if (l) ble_backend_received(b->owner, l->id, e.data, e.len); break;
case 4: if (l) ble_backend_sent(b->owner, l->id, e.arg == 0); break;
case 5: if (l) ble_backend_closed(b->owner, l->id, e.text); break;
case 6: ble_backend_broadcast(b->owner, &ep, e.data, e.len, e.arg); break;
case 7: ble_backend_advertising(b->owner, e.arg ? BLE_CLOSED : BLE_READY, e.text); break;
case 8: ble_backend_status(b->owner, 0, e.text); break;
default: DEBUG_WARN(CAT, "BLE unknown Android event=%d", e.type); break;
}
}
pthread_mutex_lock(&lock);
if (b->count || b->overflow) wake(b);
pthread_mutex_unlock(&lock);
}
JNIEXPORT void JNICALL Java_com_utun_chat_data_BleRadio_nativeInstall(JNIEnv* env, jclass cls) {
if (radio_class) return; /* Application устанавливает один раз, до запуска worker. */
if ((*env)->GetJavaVM(env, &vm) != JNI_OK) { DEBUG_ERROR(CAT, "BLE GetJavaVM failed"); return; }
command_method = (*env)->GetStaticMethodID(env, cls, "command", "(IJJLjava/lang/String;[BI)V");
if (!command_method) { DEBUG_ERROR(CAT, "BLE command method unavailable"); return; }
radio_class = (*env)->NewGlobalRef(env, cls);
if (!radio_class) DEBUG_ERROR(CAT, "BLE global class allocation failed");
}
JNIEXPORT void JNICALL Java_com_utun_chat_data_BleRadio_nativeEvent(JNIEnv* env, jclass cls, jlong gen,
jlong token, jint type, jint arg, jstring text, jbyteArray bytes) {
(void)cls;
struct event e = {.token = token, .type = type, .arg = arg};
e.len = bytes ? (*env)->GetArrayLength(env, bytes) : 0;
if (e.len > 512) { DEBUG_ERROR(CAT, "BLE JNI oversized event=%d", e.len); return; }
if (e.len) (*env)->GetByteArrayRegion(env, bytes, 0, e.len, (jbyte*)e.data);
const char* s = text ? (*env)->GetStringUTFChars(env, text, NULL) : NULL;
if (s) { snprintf(e.text, sizeof(e.text), "%s", s); (*env)->ReleaseStringUTFChars(env, text, s); }
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); DEBUG_ERROR(CAT, "BLE JNI event copy failed"); return; }
pthread_mutex_lock(&lock);
if (active && active->generation == gen) {
if (active->count == EVENTS) active->overflow = 1;
else { active->events[(active->head + active->count) % EVENTS] = e; active->count++; }
wake(active);
}
pthread_mutex_unlock(&lock);
}
struct ble_backend* ble_backend_create(struct ble_transport* owner, struct UASYNC* ua, const struct ble_config* cfg) {
struct ble_backend* b = u_calloc(1, sizeof(*b));
if (!b) { DEBUG_ERROR(CAT, "BLE Android allocation failed"); return NULL; }
b->owner = owner; b->ua = ua;
b->wake_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
if (b->wake_fd < 0) { DEBUG_ERROR(CAT, "BLE eventfd create: %s", strerror(errno)); u_free(b); return NULL; }
pthread_mutex_lock(&lock);
if (active) { pthread_mutex_unlock(&lock); close(b->wake_fd); u_free(b); DEBUG_ERROR(CAT, "BLE Android already active"); return NULL; }
b->generation = ++generation; active = b;
pthread_mutex_unlock(&lock);
b->io = uasync_add_socket(ua, b->wake_fd, drain, NULL, drain, "android_ble_events", b);
if (!b->io) DEBUG_ERROR(CAT, "BLE eventfd registration failed");
if (!b->io || command(1, b->generation, 0, NULL, NULL, 0, cfg->company_id) < 0) {
ble_backend_destroy(b); return NULL;
}
return b;
}
void ble_backend_destroy(struct ble_backend* b) {
if (!b) return;
pthread_mutex_lock(&lock); if (active == b) active = NULL; pthread_mutex_unlock(&lock);
command(0, b->generation, 0, NULL, NULL, 0, 0);
if (b->io) uasync_remove_socket(b->ua, b->io);
close(b->wake_fd);
u_free(b);
}
int ble_backend_advertise(struct ble_backend* b, const uint8_t* data, size_t len) {
return command(2, b->generation, 0, NULL, data, len, data != NULL);
}
int ble_backend_connect(struct ble_backend* b, ble_link_id id, const struct ble_endpoint* ep) {
struct android_link* l = by_id(b, 0);
if (!l) { DEBUG_WARN(CAT, "BLE Android link limit"); return BLE_ERROR; }
l->id = id; l->token = (jlong)id;
return command(3, b->generation, l->token, ep->id, NULL, 0, 0);
}
void ble_backend_close(struct ble_backend* b, ble_link_id id) {
struct android_link* l = by_id(b, id); if (!l) return;
command(4, b->generation, l->token, NULL, NULL, 0, 0); memset(l, 0, sizeof(*l));
}
int ble_backend_send(struct ble_backend* b, ble_link_id id, const uint8_t* data, size_t len) {
struct android_link* l = by_id(b, id);
if (!l) { DEBUG_ERROR(CAT, "BLE Android send unknown link"); return BLE_ERROR; }
return command(5, b->generation, l->token, NULL, data, len, 0);
}
static void status(void* arg, enum ble_state state, const char* reason) {
(void)arg;
radio_ready = state == BLE_READY;
DEBUG_INFO(CAT, "BLE Android status=%d reason=%s", state, reason);
command(7, 0, 0, reason, NULL, 0, state);
if (state == BLE_READY && (policy & 2)) ble_advertise(transport, identity, identity_len);
}
static void advertising(void* arg, enum ble_state state, const char* reason) {
(void)arg; command(8, 0, 0, reason, NULL, 0, state);
}
static void broadcast(void* arg, const struct ble_endpoint* ep, const uint8_t* bytes, size_t len, int rssi) {
(void)arg;
struct ble_identity peer;
if (ble_identity_decode(&peer, bytes, len) < 0) return;
DEBUG_DEBUG(CAT, "BLE discovered node=%016llx name=%s rssi=%d endpoint=%s",
(unsigned long long)peer.node_id, peer.name, rssi, ep->id);
/* Только явно запрошенный instrumentation test; обычный транспорт не выбирает пиров. */
if ((policy & 16) && peer.node_id == 0x5643424c45544553ULL) {
policy &= ~16;
ble_connect(transport, ep, peer.node_id);
}
}
static int validate(void* arg, ble_link_id link, uint64_t peer) {
(void)arg; DEBUG_INFO(CAT, "BLE validation allow link=%llu node=%016llx", (unsigned long long)link, (unsigned long long)peer);
return 1;
}
static void received(void* arg, ble_link_id link) {
(void)arg;
if (!(policy & 8)) return;
struct ll_entry* e;
while ((e = ble_rx_get(transport, link))) {
int result = ble_tx_put(transport, link, e);
if (result != BLE_OK) {
DEBUG_ERROR(CAT, "BLE test echo failed link=%llu result=%d", (unsigned long long)link, result);
queue_dgram_free(e); queue_entry_free(e);
}
}
}
struct ble_transport* android_ble_transport(void) { return transport; }
void android_ble_detach(void) {
if (transport) { ble_destroy(transport); transport = NULL; }
}
static void configure(void* arg) {
int flags = (int)(intptr_t)arg;
struct UTUN_INSTANCE* inst = instance_lite_get_instance();
if (!inst) { DEBUG_WARN(CAT, "BLE configure cancelled: core stopped"); return; }
if (flags & 8) debug_set_category_level(CAT, DEBUG_LEVEL_DEBUG);
policy = flags; incoming_policy = !!(flags & 4);
if (transport && radio_ready && (flags & 1)) {
ble_set_incoming(transport, incoming_policy);
command(6, 0, 0, NULL, NULL, 0, incoming_policy);
ble_advertise(transport, (flags & 2) ? identity : NULL, (flags & 2) ? identity_len : 0);
return;
}
android_ble_detach();
command(6, 0, 0, NULL, NULL, 0, incoming_policy);
if (!(flags & 1)) return;
struct ble_config cfg = {0}; cfg.node_id = inst->node_id; cfg.company_id = 0xffff;
cfg.callbacks.advertising = advertising;
cfg.callbacks.status = status; cfg.callbacks.broadcast = broadcast; cfg.callbacks.validate = validate; cfg.callbacks.received = received;
identity_len = ble_identity_encode(identity, cfg.node_id, inst->config->global.name);
if (identity_len < 0) return;
transport = ble_create(instance_lite_get_uasync(), &cfg);
if (transport) { ble_set_incoming(transport, incoming_policy); ble_start(transport); }
}
JNIEXPORT jboolean JNICALL Java_com_utun_chat_data_BleRadio_nativeConfigure(JNIEnv* env, jclass cls, jint flags) {
(void)env; (void)cls;
if (flags < 0 || flags > 31) { DEBUG_ERROR(CAT, "BLE invalid settings=%d", flags); return JNI_FALSE; }
struct posted_task* task = u_calloc(1, sizeof(*task));
if (!task) { DEBUG_ERROR(CAT, "BLE settings allocation failed"); return JNI_FALSE; }
task->callback = configure; task->arg = (void*)(intptr_t)flags;
return instance_lite_post_frontend(task, CAT, "BLE") == 0;
}