/* 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 #include #include #include #include #include #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; }