236 lines
14 KiB
C
236 lines
14 KiB
C
#include "ble_join.h"
|
|
#include "../../src/chat/chat_sync.h"
|
|
#include "../../src/utun_instance.h"
|
|
#include "../../src/transport_layer/secure_channel.h"
|
|
#include "../../src/local_transport/protocol.h"
|
|
#include "../../lib/u_async.h"
|
|
#include "../../lib/mem.h"
|
|
#include "../../lib/debug_config.h"
|
|
#include <openssl/rand.h>
|
|
#include <openssl/crypto.h>
|
|
#include <string.h>
|
|
#define CAT DEBUG_CATEGORY_DISCOVERY
|
|
#define JOIN_QUEUE 4
|
|
#define JOIN_PREFIX 34 /* version, kind, X25519 отправителя */
|
|
#define JOIN_BODY 45 /* domain8, sender8, receiver8, nonce8, echo8, seq4, opcode1 */
|
|
#define JOIN_LIFETIME_US 60000000ULL
|
|
static const uint8_t domain[8] = {'V','C','J','O','I','N',1,0};
|
|
enum { JOIN_HELLO = 1, JOIN_REPLY, JOIN_MESSAGE, JOIN_CANCEL };
|
|
struct join_link {
|
|
ble_link_id id;
|
|
uint64_t peer, local_nonce, peer_nonce, deadline, hello_due;
|
|
sc_context_t crypto;
|
|
uint32_t tx_sequence, rx_sequence;
|
|
int ready, requested, authenticated, notified, held, releasing;
|
|
struct ll_entry* queue[JOIN_QUEUE];
|
|
unsigned head, count;
|
|
};
|
|
struct ble_join {
|
|
struct UTUN_INSTANCE* inst;
|
|
struct ble_transport* radio;
|
|
struct chat_join_carrier provider;
|
|
int (*connect)(void*, uint64_t);
|
|
void (*wake)(void*);
|
|
void* arg;
|
|
struct join_link links[BLE_LINKS_MAX];
|
|
};
|
|
static struct join_link* find_link(struct ble_join* b, uint64_t id, int handle) {
|
|
for (int i = 0; i < BLE_LINKS_MAX; ++i)
|
|
if (id && b->links[i].id && (handle ? b->links[i].local_nonce == id : b->links[i].id == id)) return &b->links[i];
|
|
return NULL;
|
|
}
|
|
/* При сбросе nonce меняется; поздние сообщения не могут возобновить завершённую сессию. */
|
|
static void reset(struct ble_join* b, struct join_link* l, int notify) {
|
|
uint64_t handle = l->local_nonce, peer = l->peer; ble_link_id id = l->id; int ready = l->ready;
|
|
for (unsigned i = 0; i < JOIN_QUEUE; ++i) if (l->queue[i]) {
|
|
queue_dgram_free(l->queue[i]); queue_entry_free(l->queue[i]);
|
|
}
|
|
OPENSSL_cleanse(l, sizeof(*l)); l->id = id; l->peer = peer; l->ready = ready;
|
|
if (notify && handle) chat_sync_join_carrier_closed(b->inst, handle);
|
|
}
|
|
static int start(struct ble_join* b, struct join_link* l, const uint8_t key[32]) {
|
|
if (l->local_nonce) {
|
|
if (!memcmp(l->crypto.peer_public_key, key, 32)) return 0;
|
|
DEBUG_WARN(CAT, "BLE JOIN key changed inside GATT link=%llu", (unsigned long long)l->id); return -1;
|
|
}
|
|
uint64_t nonce;
|
|
if (RAND_bytes((uint8_t*)&nonce, sizeof(nonce)) != 1 || !nonce || find_link(b, nonce, 1) ||
|
|
sc_init_ctx(&l->crypto, &b->inst->my_keys) != SC_OK || sc_set_peer_public_key(&l->crypto, key, SC_PEER_PUBKEY_BIN) != SC_OK) {
|
|
DEBUG_ERROR(CAT, "BLE JOIN nonce/crypto initialization failed link=%llu", (unsigned long long)l->id); return -1;
|
|
}
|
|
l->local_nonce = nonce; l->deadline = get_time_us() + JOIN_LIFETIME_US; l->held = 1;
|
|
DEBUG_INFO(CAT, "BLE JOIN session started link=%llu peer=%016llx handle=%016llx",
|
|
(unsigned long long)l->id, (unsigned long long)l->peer, (unsigned long long)nonce);
|
|
return 0;
|
|
}
|
|
/* Одна bounded очередь шифрованных целых INVITE; фрагментацией владеет ble_transport. */
|
|
static int enqueue(struct ble_join* b, struct join_link* l, int opcode, const uint8_t* data, size_t len) {
|
|
if (l->count == JOIN_QUEUE || len > CHAT_JOIN_WIRE_MAX || !l->local_nonce || l->tx_sequence == UINT32_MAX) {
|
|
DEBUG_WARN(CAT, "BLE JOIN TX limit link=%llu queued=%u bytes=%zu", (unsigned long long)l->id, l->count, len); return -1;
|
|
}
|
|
uint8_t plain[JOIN_BODY + CHAT_JOIN_WIRE_MAX];
|
|
memcpy(plain, domain, sizeof(domain)); local_put64(plain + 8, b->inst->node_id); local_put64(plain + 16, l->peer);
|
|
local_put64(plain + 24, l->local_nonce); local_put64(plain + 32, opcode == JOIN_HELLO ? 0 : l->peer_nonce);
|
|
uint32_t sequence = opcode >= JOIN_MESSAGE ? l->tx_sequence + 1 : 0, wire = htonl(sequence);
|
|
memcpy(plain + 40, &wire, 4); plain[44] = (uint8_t)opcode;
|
|
if (len) memcpy(plain + JOIN_BODY, data, len);
|
|
struct ll_entry* e = ll_alloc_lldgram(JOIN_PREFIX + JOIN_BODY + len + 33);
|
|
if (!e) { DEBUG_ERROR(CAT, "BLE JOIN TX allocation failed"); return -1; }
|
|
e->dgram[0] = 1; e->dgram[1] = 3; memcpy(e->dgram + 2, b->inst->my_keys.public_key, 32);
|
|
size_t encrypted = 0;
|
|
int result = sc_encrypt(&l->crypto, plain, JOIN_BODY + len, e->dgram + JOIN_PREFIX, &encrypted);
|
|
OPENSSL_cleanse(plain, sizeof(plain));
|
|
if (result != SC_OK) {
|
|
queue_dgram_free(e); queue_entry_free(e); DEBUG_ERROR(CAT, "BLE JOIN encryption failed rc=%d", result); return -1;
|
|
}
|
|
e->len = JOIN_PREFIX + encrypted; l->queue[(l->head + l->count++) % JOIN_QUEUE] = e;
|
|
if (opcode >= JOIN_MESSAGE) l->tx_sequence = sequence;
|
|
DEBUG_DEBUG(CAT, "BLE JOIN TX queued link=%llu op=%d seq=%u bytes=%zu queued=%u",
|
|
(unsigned long long)l->id, opcode, sequence, len, l->count);
|
|
b->wake(b->arg); return 0;
|
|
}
|
|
static int carrier_open(void* arg, const uint8_t key[32], uint64_t* handle) {
|
|
struct ble_join* b = arg;
|
|
if (b->inst->utun_started) return 1;
|
|
uint64_t peer = sc_derive_node_id_from_pubkey(key);
|
|
struct join_link* l = NULL;
|
|
for (int i = 0; i < BLE_LINKS_MAX; ++i) if (b->links[i].id && b->links[i].peer == peer) { l = &b->links[i]; break; }
|
|
if (!l) {
|
|
if (b->connect(b->arg, peer)) return 1;
|
|
for (int i = 0; i < BLE_LINKS_MAX; ++i) if (b->links[i].id && b->links[i].peer == peer) { l = &b->links[i]; break; }
|
|
}
|
|
if (l && l->local_nonce && !l->held && !l->count) reset(b, l, 0);
|
|
if (!l || start(b, l, key)) { DEBUG_ERROR(CAT, "BLE JOIN cannot reserve peer=%016llx", (unsigned long long)peer); return -1; }
|
|
if (l->requested) { DEBUG_WARN(CAT, "BLE JOIN already requested on link=%llu", (unsigned long long)l->id); return -1; }
|
|
l->requested = l->held = 1; l->releasing = l->notified = 0; *handle = l->local_nonce; b->wake(b->arg); return 0;
|
|
}
|
|
static int carrier_send(void* arg, uint64_t handle, const uint8_t* data, size_t len) {
|
|
struct ble_join* b = arg; struct join_link* l = find_link(b, handle, 1);
|
|
if (!l || !l->authenticated || !data || len < 18 || len > CHAT_JOIN_WIRE_MAX ||
|
|
(data[9] != CS_MSG_JOIN_INFO_REQ && data[9] != CS_MSG_JOIN_INFO_RESP && data[9] != CS_MSG_JOIN_REQUEST &&
|
|
data[9] != CS_MSG_JOIN_READY && data[9] != CS_MSG_ERROR)) {
|
|
DEBUG_WARN(CAT, "BLE JOIN send on inactive carrier handle=%016llx bytes=%zu", (unsigned long long)handle, len); return -1;
|
|
}
|
|
return enqueue(b, l, JOIN_MESSAGE, data, len);
|
|
}
|
|
static void carrier_close(void* arg, uint64_t handle, int success) {
|
|
struct ble_join* b = arg; struct join_link* l = find_link(b, handle, 1);
|
|
if (!l) return;
|
|
DEBUG_INFO(CAT, "BLE JOIN release requested link=%llu success=%d queued=%u", (unsigned long long)l->id, success, l->count);
|
|
if (!success && l->authenticated && !l->releasing && enqueue(b, l, JOIN_CANCEL, NULL, 0))
|
|
DEBUG_WARN(CAT, "BLE JOIN cancellation could not be queued link=%llu", (unsigned long long)l->id);
|
|
l->requested = 0; l->releasing = 1; b->wake(b->arg);
|
|
}
|
|
struct ble_join* ble_join_create(struct UTUN_INSTANCE* inst, struct ble_transport* radio,
|
|
int (*connect)(void*, uint64_t), void (*wake)(void*), void* arg) {
|
|
if (!inst || !radio || !connect || !wake || inst->join_carrier) {
|
|
DEBUG_ERROR(CAT, "BLE JOIN invalid/duplicate provider"); return NULL;
|
|
}
|
|
struct ble_join* b = u_calloc(1, sizeof(*b));
|
|
if (!b) { DEBUG_ERROR(CAT, "BLE JOIN provider allocation failed"); return NULL; }
|
|
b->inst = inst; b->radio = radio; b->connect = connect; b->wake = wake; b->arg = arg;
|
|
b->provider = (struct chat_join_carrier){b, carrier_open, carrier_send, carrier_close};
|
|
if (chat_sync_set_join_carrier(inst, &b->provider)) { u_free(b); return NULL; }
|
|
return b;
|
|
}
|
|
void ble_join_destroy(struct ble_join* b) {
|
|
if (!b) return;
|
|
if (b->inst->join_carrier == &b->provider) chat_sync_set_join_carrier(b->inst, NULL);
|
|
for (int i = 0; i < BLE_LINKS_MAX; ++i) reset(b, &b->links[i], 0);
|
|
OPENSSL_cleanse(b, sizeof(*b)); u_free(b);
|
|
}
|
|
void ble_join_connection(struct ble_join* b, ble_link_id id, uint64_t peer, enum ble_state state) {
|
|
if (!b) return;
|
|
struct join_link* l = find_link(b, id, 0);
|
|
if (state == BLE_CLOSED) { if (l) { reset(b, l, 1); l->id = 0; } return; }
|
|
if (!l) for (int i = 0; i < BLE_LINKS_MAX; ++i) if (!b->links[i].id) { l = &b->links[i]; l->id = id; break; }
|
|
if (!l) { DEBUG_ERROR(CAT, "BLE JOIN connection limit link=%llu", (unsigned long long)id); return; }
|
|
l->peer = peer; l->ready = state == BLE_READY;
|
|
}
|
|
int ble_join_holds(struct ble_join* b, ble_link_id id) {
|
|
struct join_link* l = b ? find_link(b, id, 0) : NULL; return l && l->held;
|
|
}
|
|
int ble_join_receive(struct ble_join* b, ble_link_id id, const uint8_t* bytes, size_t len) {
|
|
if (len < 2 || bytes[0] != 1 || bytes[1] != 3) return 0;
|
|
struct join_link* l = find_link(b, id, 0);
|
|
if (!l || !l->ready || b->inst->utun_started || len < JOIN_PREFIX + JOIN_BODY + 33 ||
|
|
len > JOIN_PREFIX + JOIN_BODY + CHAT_JOIN_WIRE_MAX + 33 || sc_derive_node_id_from_pubkey(bytes + 2) != l->peer) {
|
|
DEBUG_WARN(CAT, "BLE JOIN invalid envelope link=%llu bytes=%zu", (unsigned long long)id, len); return 1;
|
|
}
|
|
sc_context_t candidate;
|
|
if ((l->local_nonce && memcmp(l->crypto.peer_public_key, bytes + 2, 32)) ||
|
|
sc_init_ctx(&candidate, &b->inst->my_keys) != SC_OK || sc_set_peer_public_key(&candidate, bytes + 2, SC_PEER_PUBKEY_BIN) != SC_OK) {
|
|
DEBUG_WARN(CAT, "BLE JOIN unexpected peer key link=%llu", (unsigned long long)id); return 1;
|
|
}
|
|
uint8_t plain[JOIN_BODY + CHAT_JOIN_WIRE_MAX]; size_t size = 0;
|
|
int decrypted = sc_decrypt(&candidate, bytes + JOIN_PREFIX, len - JOIN_PREFIX, plain, &size);
|
|
OPENSSL_cleanse(&candidate, sizeof(candidate));
|
|
if (decrypted != SC_OK || size < JOIN_BODY || memcmp(plain, domain, 8) ||
|
|
local_get64(plain + 8) != l->peer || local_get64(plain + 16) != b->inst->node_id) {
|
|
DEBUG_WARN(CAT, "BLE JOIN authentication/domain rejected link=%llu rc=%d", (unsigned long long)id, decrypted);
|
|
OPENSSL_cleanse(plain, sizeof(plain)); return 1;
|
|
}
|
|
uint64_t nonce = local_get64(plain + 24), echo = local_get64(plain + 32);
|
|
uint32_t wire; memcpy(&wire, plain + 40, 4); uint32_t sequence = ntohl(wire); int opcode = plain[44];
|
|
uint64_t opening_deadline = 0;
|
|
if (l->local_nonce && (get_time_us() >= l->deadline ||
|
|
(opcode == JOIN_HELLO && l->peer_nonce && (!l->held || !l->authenticated) && !l->count && l->peer_nonce != nonce))) {
|
|
if (l->held && !l->authenticated && get_time_us() < l->deadline) opening_deadline = l->deadline;
|
|
reset(b, l, 1);
|
|
}
|
|
if (!nonce || (l->peer_nonce && l->peer_nonce != nonce) ||
|
|
(opcode != JOIN_HELLO && (!l->local_nonce || echo != l->local_nonce)) ||
|
|
(opcode == JOIN_HELLO && (echo || sequence || size != JOIN_BODY)) ||
|
|
(opcode == JOIN_REPLY && (sequence || size != JOIN_BODY || !l->requested)) ||
|
|
(opcode >= JOIN_MESSAGE && (!l->peer_nonce || !sequence || sequence != l->rx_sequence + 1)) ||
|
|
(opcode == JOIN_CANCEL && size != JOIN_BODY) ||
|
|
(opcode != JOIN_HELLO && opcode != JOIN_REPLY && opcode != JOIN_MESSAGE && opcode != JOIN_CANCEL)) {
|
|
DEBUG_WARN(CAT, "BLE JOIN stale/invalid session link=%llu op=%d seq=%u expected=%u",
|
|
(unsigned long long)id, opcode, sequence, l->rx_sequence + 1);
|
|
OPENSSL_cleanse(plain, sizeof(plain)); return 1;
|
|
}
|
|
if (start(b, l, bytes + 2)) { OPENSSL_cleanse(plain, sizeof(plain)); return 1; }
|
|
if (opening_deadline) l->deadline = opening_deadline; /* Смена ещё не доказанного challenge не продлевает открытие. */
|
|
l->peer_nonce = nonce;
|
|
DEBUG_DEBUG(CAT, "BLE JOIN RX link=%llu op=%d seq=%u bytes=%zu", (unsigned long long)id, opcode, sequence, size - JOIN_BODY);
|
|
if (opcode == JOIN_HELLO) {
|
|
if (enqueue(b, l, JOIN_REPLY, NULL, 0)) reset(b, l, 1);
|
|
} else if (opcode == JOIN_REPLY) l->authenticated = 1;
|
|
else if (opcode == JOIN_CANCEL) reset(b, l, 1);
|
|
else {
|
|
l->authenticated = 1; l->rx_sequence = sequence; l->held = 1;
|
|
chat_sync_join_carrier_receive(b->inst, l->local_nonce, l->crypto.peer_public_key, plain + JOIN_BODY, size - JOIN_BODY);
|
|
}
|
|
OPENSSL_cleanse(plain, sizeof(plain)); b->wake(b->arg); return 1;
|
|
}
|
|
void ble_join_run(struct ble_join* b) {
|
|
if (!b) return;
|
|
for (int i = 0; i < BLE_LINKS_MAX; ++i) {
|
|
struct join_link* l = &b->links[i];
|
|
if (!l->id || !l->local_nonce) continue;
|
|
if (get_time_us() >= l->deadline && (l->held || l->count)) {
|
|
DEBUG_WARN(CAT, "BLE JOIN session deadline link=%llu", (unsigned long long)l->id); reset(b, l, 1); continue;
|
|
}
|
|
if (!l->ready) continue;
|
|
if (l->requested && !l->authenticated && get_time_us() >= l->hello_due && l->count < JOIN_QUEUE) {
|
|
if (enqueue(b, l, JOIN_HELLO, NULL, 0)) { reset(b, l, 1); continue; }
|
|
l->hello_due = get_time_us() + 500000; /* Только challenge повторяется; общий deadline не сдвигается. */
|
|
}
|
|
if (l->requested && l->authenticated && !l->notified) {
|
|
l->notified = 1; chat_sync_join_carrier_ready(b->inst, l->local_nonce, l->crypto.peer_public_key);
|
|
}
|
|
if (l->count && ble_channel_tx_ready(b->radio, l->id, BLE_CHANNEL_INVITE)) {
|
|
struct ll_entry* e = l->queue[l->head];
|
|
int result = ble_channel_tx_put(b->radio, l->id, BLE_CHANNEL_INVITE, e);
|
|
if (result == BLE_OK) { l->queue[l->head] = NULL; l->head = (l->head + 1) % JOIN_QUEUE; l->count--; }
|
|
else if (result != BLE_WOULD_BLOCK) {
|
|
DEBUG_WARN(CAT, "BLE JOIN TX refused link=%llu rc=%d", (unsigned long long)l->id, result); reset(b, l, 1); continue;
|
|
}
|
|
}
|
|
if (l->releasing && !l->count && ble_channel_tx_ready(b->radio, l->id, BLE_CHANNEL_INVITE)) {
|
|
l->held = l->releasing = 0;
|
|
DEBUG_INFO(CAT, "BLE JOIN carrier drained link=%llu; known-key admission may proceed", (unsigned long long)l->id);
|
|
}
|
|
}
|
|
}
|