421 lines
24 KiB
C
421 lines
24 KiB
C
#include "ble_backend.h"
|
|
#include "../u_async.h"
|
|
#include "../mem.h"
|
|
#include "../debug_config.h"
|
|
#include <string.h>
|
|
#define CAT DEBUG_CATEGORY_DISCOVERY
|
|
#define HELLO 1
|
|
#define ACCEPT 2
|
|
#define FRAME 3
|
|
#define HEADER 7
|
|
#define DEADLINE_US 15000000ULL
|
|
#define STATS_US 5000000ULL
|
|
struct ble_stats {
|
|
uint64_t since, tx_bytes, rx_bytes, wait_us, max_wait_us;
|
|
unsigned tx_frames, rx_frames, fragments, dropped, missing;
|
|
};
|
|
struct ble_channel_state {
|
|
struct ll_queue *tx, *rx;
|
|
struct ll_entry* current;
|
|
uint64_t rx_deadline;
|
|
uint16_t sequence, offset, rx_sequence, rx_size, rx_offset, dropped_sequence, rx_next;
|
|
int blocked, dropping, rx_seen;
|
|
uint8_t assembly[BLE_FRAME_MAX];
|
|
};
|
|
struct ble_connection {
|
|
ble_link_id id;
|
|
uint64_t peer, deadline, submitted;
|
|
struct ble_stats stats;
|
|
struct ble_endpoint endpoint;
|
|
struct ble_channel_state channels[BLE_CHANNEL_COUNT];
|
|
unsigned int capacity;
|
|
uint16_t pending_bytes;
|
|
enum ble_channel pending_channel, next_channel;
|
|
int outgoing, opened, ready, pending, control_pending;
|
|
uint8_t control[17];
|
|
};
|
|
struct ble_transport {
|
|
struct UASYNC* ua;
|
|
struct ble_config config;
|
|
char adapter[64];
|
|
struct ble_backend* backend;
|
|
struct ble_connection links[BLE_LINKS_MAX];
|
|
ble_link_id sequence;
|
|
void* timer;
|
|
int started, ready, reject_incoming;
|
|
};
|
|
static uint16_t read16(const uint8_t* p) { return (uint16_t)((p[0] << 8) | p[1]); }
|
|
static void write16(uint8_t* p, uint16_t v) { p[0] = v >> 8; p[1] = v; }
|
|
static uint64_t read64(const uint8_t* p) {
|
|
uint64_t v = 0;
|
|
for (int i = 0; i < 8; i++) v = (v << 8) | p[i];
|
|
return v;
|
|
}
|
|
static void write64(uint8_t* p, uint64_t v) { for (int i = 7; i >= 0; i--) { p[i] = v; v >>= 8; } }
|
|
/* Диагностический отпечаток зашифрованного кадра для сопоставления журналов двух узлов. */
|
|
static uint32_t frame_tag(const uint8_t* data, size_t len) {
|
|
uint32_t tag = 2166136261u;
|
|
for (size_t i = 0; i < len; i++) tag = (tag ^ data[i]) * 16777619u;
|
|
return tag;
|
|
}
|
|
static void release_entry(struct ll_entry* e) { if (e) { queue_dgram_free(e); queue_entry_free(e); } }
|
|
static struct ble_connection* lookup(struct ble_transport* t, ble_link_id id) {
|
|
if (t && id) for (int i = 0; i < BLE_LINKS_MAX; i++) if (t->links[i].id == id) return &t->links[i];
|
|
return NULL;
|
|
}
|
|
/* Сводка раз в пять секунд и при закрытии: payload кадров, очереди и локальное ожидание GATT. */
|
|
static void log_stats(struct ble_connection* c, uint64_t now) {
|
|
struct ble_stats* s = &c->stats;
|
|
uint64_t elapsed = now > s->since ? now - s->since : 1;
|
|
if (s->tx_frames || s->rx_frames || s->fragments || s->dropped || c->pending) {
|
|
DEBUG_DEBUG(CAT, "BLE traffic link=%llu outgoing=%d elapsed_ms=%llu TX_Bps=%llu RX_Bps=%llu TX_frames=%u RX_frames=%u "
|
|
"fragments=%u drops=%u missing=%u wait_avg_us=%llu wait_max_us=%llu DATA_queue=%d INVITE_queue=%d pending_ms=%llu",
|
|
(unsigned long long)c->id, c->outgoing, (unsigned long long)(elapsed / 1000),
|
|
(unsigned long long)(s->tx_bytes * 1000000 / elapsed), (unsigned long long)(s->rx_bytes * 1000000 / elapsed),
|
|
s->tx_frames, s->rx_frames, s->fragments, s->dropped, s->missing,
|
|
(unsigned long long)(s->fragments ? s->wait_us / s->fragments : 0), (unsigned long long)s->max_wait_us,
|
|
queue_entry_count(c->channels[BLE_CHANNEL_DATA].tx) + !!c->channels[BLE_CHANNEL_DATA].current,
|
|
queue_entry_count(c->channels[BLE_CHANNEL_INVITE].tx) + !!c->channels[BLE_CHANNEL_INVITE].current,
|
|
(unsigned long long)(c->pending ? (now - c->submitted) / 1000 : 0));
|
|
}
|
|
memset(s, 0, sizeof(*s)); s->since = now;
|
|
}
|
|
int ble_get_outgoing(struct ble_transport* t, ble_link_id id, int* outgoing) {
|
|
struct ble_connection* c = lookup(t, id);
|
|
if (!c || !outgoing) { DEBUG_ERROR(CAT, "BLE direction: invalid link=%llu/output", (unsigned long long)id); return -1; }
|
|
*outgoing = c->outgoing;
|
|
return 0;
|
|
}
|
|
static void close_link(struct ble_transport* t, struct ble_connection* c, const char* reason) {
|
|
ble_link_id id = c->id;
|
|
uint64_t peer = c->peer;
|
|
log_stats(c, get_time_us());
|
|
DEBUG_INFO(CAT, "BLE closed link=%llu peer=%016llx reason=%s", (unsigned long long)id, (unsigned long long)peer, reason);
|
|
if (t->backend) ble_backend_close(t->backend, id);
|
|
for (int i = 0; i < BLE_CHANNEL_COUNT; i++) {
|
|
struct ble_channel_state* ch = &c->channels[i];
|
|
release_entry(ch->current);
|
|
queue_clear(ch->tx); queue_free(ch->tx);
|
|
queue_clear(ch->rx); queue_free(ch->rx);
|
|
}
|
|
memset(c, 0, sizeof(*c));
|
|
if (t->config.callbacks.connection) t->config.callbacks.connection(t->config.arg, id, peer, BLE_CLOSED, reason);
|
|
}
|
|
static struct ble_connection* new_link(struct ble_transport* t, const struct ble_endpoint* ep, int outgoing, uint64_t peer) {
|
|
if (!t || !t->started || !ep || !memchr(ep->id, 0, sizeof(ep->id)) || !ep->id[0]) {
|
|
DEBUG_ERROR(CAT, "BLE invalid connection request"); return NULL;
|
|
}
|
|
for (int i = 0; i < BLE_LINKS_MAX; i++) if (t->links[i].id && !strcmp(t->links[i].endpoint.id, ep->id)) {
|
|
DEBUG_WARN(CAT, "BLE duplicate connection endpoint=%s", ep->id); return NULL;
|
|
}
|
|
for (int i = 0; i < BLE_LINKS_MAX; i++) if (!t->links[i].id) {
|
|
struct ble_connection* c = &t->links[i];
|
|
int allocated = 1;
|
|
for (int ch = 0; ch < BLE_CHANNEL_COUNT; ch++) {
|
|
c->channels[ch].tx = queue_new(t->ua, 0, 0, 0, "ble_tx");
|
|
c->channels[ch].rx = queue_new(t->ua, 0, 0, 0, "ble_rx");
|
|
if (!c->channels[ch].tx || !c->channels[ch].rx) allocated = 0;
|
|
}
|
|
if (!allocated) {
|
|
DEBUG_ERROR(CAT, "BLE queue allocation failed");
|
|
for (int ch = 0; ch < BLE_CHANNEL_COUNT; ch++) { queue_free(c->channels[ch].tx); queue_free(c->channels[ch].rx); }
|
|
memset(c, 0, sizeof(*c)); return NULL;
|
|
}
|
|
c->id = ++t->sequence; c->peer = peer; c->endpoint = *ep; c->outgoing = outgoing;
|
|
c->capacity = 20; c->stats.since = get_time_us(); c->deadline = c->stats.since + DEADLINE_US;
|
|
DEBUG_INFO(CAT, "BLE connecting link=%llu endpoint=%s outgoing=%d", (unsigned long long)c->id, ep->id, outgoing);
|
|
if (t->config.callbacks.connection) t->config.callbacks.connection(t->config.arg, c->id, peer, BLE_STARTING, "connecting");
|
|
return c;
|
|
}
|
|
DEBUG_WARN(CAT, "BLE connection limit=%d", BLE_LINKS_MAX); return NULL;
|
|
}
|
|
static void pump(struct ble_transport* t, struct ble_connection* c) {
|
|
if (!c->opened || c->pending) return;
|
|
uint8_t value[BLE_VALUE_MAX];
|
|
size_t len;
|
|
c->pending_bytes = 0;
|
|
c->pending_channel = BLE_CHANNEL_DATA;
|
|
if (c->control_pending) { memcpy(value, c->control, 17); len = 17; }
|
|
else {
|
|
if (!c->ready) return;
|
|
struct ble_channel_state* ch = NULL;
|
|
for (int i = 0; i < BLE_CHANNEL_COUNT; i++) {
|
|
enum ble_channel channel = (c->next_channel + i) % BLE_CHANNEL_COUNT;
|
|
struct ble_channel_state* candidate = &c->channels[channel];
|
|
if (!candidate->current) {
|
|
candidate->current = queue_data_get(candidate->tx); candidate->offset = 0;
|
|
if (candidate->current) ++candidate->sequence;
|
|
}
|
|
if (candidate->current) { ch = candidate; c->pending_channel = channel; break; }
|
|
}
|
|
if (!ch) return;
|
|
c->next_channel = (c->pending_channel + 1) % BLE_CHANNEL_COUNT;
|
|
size_t n = ch->current->len - ch->offset;
|
|
if (n > c->capacity - HEADER) n = c->capacity - HEADER;
|
|
value[0] = FRAME; write16(value + 1, ch->sequence); write16(value + 3, ch->current->len); write16(value + 5, ch->offset);
|
|
memcpy(value + HEADER, ch->current->dgram + ch->offset, n); len = HEADER + n; c->pending_bytes = n;
|
|
}
|
|
int result = ble_backend_send(t->backend, c->id, c->pending_channel, value, len);
|
|
if (result == BLE_OK) { c->pending = 1; c->submitted = get_time_us(); c->deadline = c->submitted + DEADLINE_US; }
|
|
else { DEBUG_ERROR(CAT, "BLE backend send failed link=%llu result=%d", (unsigned long long)c->id, result);
|
|
close_link(t, c, "backend send failed"); }
|
|
}
|
|
static void tick(void* arg) {
|
|
struct ble_transport* t = arg; t->timer = NULL;
|
|
uint64_t now = get_time_us();
|
|
for (int i = 0; i < BLE_LINKS_MAX; i++) {
|
|
struct ble_connection* c = &t->links[i];
|
|
if (!c->id) continue;
|
|
if (now - c->stats.since >= STATS_US) log_stats(c, now);
|
|
if ((!c->ready || c->pending) && now >= c->deadline) {
|
|
DEBUG_WARN(CAT, "BLE handshake/send timeout link=%llu pending=%d", (unsigned long long)c->id, c->pending);
|
|
close_link(t, c, "operation timeout"); continue;
|
|
}
|
|
for (int channel = 0; channel < BLE_CHANNEL_COUNT; channel++) {
|
|
struct ble_channel_state* ch = &c->channels[channel];
|
|
if (ch->rx_size && now >= ch->rx_deadline) {
|
|
DEBUG_WARN(CAT, "BLE frame timeout link=%llu received=%u expected=%u",
|
|
(unsigned long long)c->id, ch->rx_offset, ch->rx_size);
|
|
ch->dropping = 1; ch->dropped_sequence = ch->rx_sequence;
|
|
c->stats.dropped++;
|
|
ch->rx_size = ch->rx_offset = 0;
|
|
}
|
|
}
|
|
}
|
|
t->timer = uasync_set_timeout(t->ua, 1000, t, tick, "ble_tick");
|
|
if (!t->timer) { DEBUG_ERROR(CAT, "BLE timer allocation failed"); ble_backend_status(t, 0, "timer allocation failed"); }
|
|
}
|
|
struct ble_transport* ble_create(struct UASYNC* ua, const struct ble_config* config) {
|
|
if (!ua || !config || !config->node_id || config->tx_limit > BLE_QUEUE_MAX ||
|
|
(config->adapter && strlen(config->adapter) >= 64)) {
|
|
DEBUG_ERROR(CAT, "BLE create invalid configuration"); return NULL;
|
|
}
|
|
struct ble_transport* t = u_calloc(1, sizeof(*t));
|
|
if (!t) { DEBUG_ERROR(CAT, "BLE create allocation failed"); return NULL; }
|
|
t->ua = ua; t->config = *config;
|
|
if (!t->config.tx_limit) t->config.tx_limit = BLE_QUEUE_MAX;
|
|
if (config->adapter) { strcpy(t->adapter, config->adapter); t->config.adapter = t->adapter; }
|
|
return t;
|
|
}
|
|
int ble_start(struct ble_transport* t) {
|
|
if (!t || t->started) { DEBUG_ERROR(CAT, "BLE invalid/repeated start"); return BLE_ERROR; }
|
|
t->started = 1;
|
|
t->backend = ble_backend_create(t, t->ua, &t->config);
|
|
if (!t->backend) { t->started = 0; DEBUG_ERROR(CAT, "BLE backend creation failed"); return BLE_ERROR; }
|
|
t->timer = uasync_set_timeout(t->ua, 1000, t, tick, "ble_tick");
|
|
if (!t->timer) { DEBUG_ERROR(CAT, "BLE timer allocation failed"); ble_stop(t); return BLE_ERROR; }
|
|
if (t->config.callbacks.status) t->config.callbacks.status(t->config.arg, BLE_STARTING, "registering BLE service");
|
|
return BLE_OK;
|
|
}
|
|
void ble_stop(struct ble_transport* t) {
|
|
if (!t || !t->started) return;
|
|
t->started = t->ready = 0;
|
|
if (t->timer) { uasync_cancel_timeout(t->ua, t->timer); t->timer = NULL; }
|
|
for (int i = 0; i < BLE_LINKS_MAX; i++) if (t->links[i].id) close_link(t, &t->links[i], "transport stopped");
|
|
ble_backend_destroy(t->backend); t->backend = NULL;
|
|
if (t->config.callbacks.status) t->config.callbacks.status(t->config.arg, BLE_CLOSED, "stopped");
|
|
}
|
|
void ble_set_incoming(struct ble_transport* t, int allowed) {
|
|
if (!t) { DEBUG_ERROR(CAT, "BLE incoming policy: null transport"); return; }
|
|
t->reject_incoming = !allowed;
|
|
DEBUG_INFO(CAT, "BLE incoming allowed=%d", !!allowed);
|
|
if (!allowed) for (int i = 0; i < BLE_LINKS_MAX; i++)
|
|
if (t->links[i].id && !t->links[i].outgoing) close_link(t, &t->links[i], "incoming disabled");
|
|
}
|
|
void ble_destroy(struct ble_transport* t) { if (t) { ble_stop(t); u_free(t); } }
|
|
int ble_advertise(struct ble_transport* t, const void* data, size_t len) {
|
|
if (!t || !t->ready) { DEBUG_WARN(CAT, "BLE advertise before ready"); return BLE_NOT_READY; }
|
|
if (len > BLE_BROADCAST_MAX || (len && !data)) { DEBUG_ERROR(CAT, "BLE invalid advertisement length=%zu", len); return BLE_ERROR; }
|
|
return ble_backend_advertise(t->backend, data, len);
|
|
}
|
|
ble_link_id ble_connect(struct ble_transport* t, const struct ble_endpoint* ep, uint64_t peer) {
|
|
if (!t || !t->ready) { DEBUG_WARN(CAT, "BLE connect before ready"); return 0; }
|
|
struct ble_connection* c = new_link(t, ep, 1, peer);
|
|
if (!c) return 0;
|
|
ble_link_id id = c->id;
|
|
if (ble_backend_connect(t->backend, id, ep) != BLE_OK) { close_link(t, c, "connect rejected"); return 0; }
|
|
return id;
|
|
}
|
|
void ble_disconnect(struct ble_transport* t, ble_link_id id) {
|
|
struct ble_connection* c = lookup(t, id);
|
|
if (c) close_link(t, c, "API disconnect");
|
|
else DEBUG_WARN(CAT, "BLE disconnect unknown link=%llu", (unsigned long long)id);
|
|
}
|
|
int ble_channel_tx_ready(struct ble_transport* t, ble_link_id id, enum ble_channel channel) {
|
|
if ((unsigned)channel >= BLE_CHANNEL_COUNT) { DEBUG_ERROR(CAT, "BLE invalid channel=%d", channel); return 0; }
|
|
struct ble_connection* c = lookup(t, id);
|
|
struct ble_channel_state* ch = c ? &c->channels[channel] : NULL;
|
|
return c && c->ready && (unsigned)queue_entry_count(ch->tx) + !!ch->current < (channel == BLE_CHANNEL_DATA ? t->config.tx_limit : 1);
|
|
}
|
|
int ble_channel_tx_put(struct ble_transport* t, ble_link_id id, enum ble_channel channel, struct ll_entry* e) {
|
|
if ((unsigned)channel >= BLE_CHANNEL_COUNT) { DEBUG_ERROR(CAT, "BLE invalid channel=%d", channel); return BLE_ERROR; }
|
|
struct ble_connection* c = lookup(t, id);
|
|
struct ble_channel_state* ch = c ? &c->channels[channel] : NULL;
|
|
if (!c || !c->ready) { DEBUG_WARN(CAT, "BLE TX link not ready=%llu", (unsigned long long)id); return BLE_NOT_READY; }
|
|
if (!e || e->owner || e->next || e->prev || !e->dgram || !e->len || e->len > BLE_FRAME_MAX || e->len > e->memlen) {
|
|
DEBUG_ERROR(CAT, "BLE invalid TX frame link=%llu", (unsigned long long)id); return BLE_ERROR;
|
|
}
|
|
if (!ble_channel_tx_ready(t, id, channel)) {
|
|
if (!ch->blocked) DEBUG_TRACE(CAT, "BLE TX paused link=%llu packets=%u", (unsigned long long)id,
|
|
(channel == BLE_CHANNEL_DATA ? t->config.tx_limit : 1));
|
|
ch->blocked = 1; return BLE_WOULD_BLOCK;
|
|
}
|
|
/* Без лимита/индекса/consumer put не имеет отказа после проверок и не вызывает чужой callback. */
|
|
if (queue_data_put(ch->tx, e) != 0) { DEBUG_ERROR(CAT, "BLE TX queue invariant failed"); return BLE_ERROR; }
|
|
if (!ble_channel_tx_ready(t, id, channel)) ch->blocked = 1;
|
|
pump(t, c); return BLE_OK;
|
|
}
|
|
struct ll_entry* ble_channel_rx_get(struct ble_transport* t, ble_link_id id, enum ble_channel channel) {
|
|
if ((unsigned)channel >= BLE_CHANNEL_COUNT) { DEBUG_ERROR(CAT, "BLE invalid channel=%d", channel); return 0; }
|
|
struct ble_connection* c = lookup(t, id);
|
|
struct ble_channel_state* ch = c ? &c->channels[channel] : NULL;
|
|
return c ? queue_data_get(ch->rx) : NULL;
|
|
}
|
|
int ble_tx_ready(struct ble_transport* t, ble_link_id id) { return ble_channel_tx_ready(t, id, BLE_CHANNEL_DATA); }
|
|
int ble_tx_put(struct ble_transport* t, ble_link_id id, struct ll_entry* e) { return ble_channel_tx_put(t, id, BLE_CHANNEL_DATA, e); }
|
|
struct ll_entry* ble_rx_get(struct ble_transport* t, ble_link_id id) { return ble_channel_rx_get(t, id, BLE_CHANNEL_DATA); }
|
|
ble_link_id ble_backend_incoming(struct ble_transport* t, const struct ble_endpoint* ep) {
|
|
if (t->reject_incoming) { DEBUG_INFO(CAT, "BLE incoming rejected by policy"); return 0; }
|
|
struct ble_connection* c = new_link(t, ep, 0, 0); return c ? c->id : 0;
|
|
}
|
|
void ble_backend_status(struct ble_transport* t, int ready, const char* reason) {
|
|
t->ready = ready;
|
|
if (!ready) {
|
|
DEBUG_ERROR(CAT, "BLE radio failed: %s", reason);
|
|
for (int i = 0; i < BLE_LINKS_MAX; i++) if (t->links[i].id) close_link(t, &t->links[i], reason);
|
|
} else DEBUG_INFO(CAT, "BLE radio ready node=%016llx", (unsigned long long)t->config.node_id);
|
|
if (t->config.callbacks.status) t->config.callbacks.status(t->config.arg, ready ? BLE_READY : BLE_CLOSED, reason);
|
|
}
|
|
void ble_backend_broadcast(struct ble_transport* t, const struct ble_endpoint* ep, const uint8_t* data, size_t len, int rssi) {
|
|
if (len > BLE_BROADCAST_MAX) { DEBUG_WARN(CAT, "BLE oversized broadcast bytes=%zu", len); return; }
|
|
if (t->config.callbacks.broadcast) t->config.callbacks.broadcast(t->config.arg, ep, data, len, rssi);
|
|
}
|
|
void ble_backend_advertising(struct ble_transport* t, enum ble_state state, const char* reason) {
|
|
DEBUG_INFO(CAT, "BLE advertising state=%d reason=%s", state, reason);
|
|
if (t->config.callbacks.advertising) t->config.callbacks.advertising(t->config.arg, state, reason);
|
|
}
|
|
void ble_backend_opened(struct ble_transport* t, ble_link_id id, unsigned int mtu) {
|
|
struct ble_connection* c = lookup(t, id);
|
|
if (!c) return;
|
|
if (mtu < 23) { DEBUG_ERROR(CAT, "BLE invalid MTU=%u", mtu); close_link(t, c, "invalid MTU"); return; }
|
|
c->capacity = mtu - 3; if (c->capacity > BLE_VALUE_MAX) c->capacity = BLE_VALUE_MAX;
|
|
c->opened = 1;
|
|
DEBUG_INFO(CAT, "BLE GATT open link=%llu mtu=%u fragment_payload=%u outgoing=%d DATA=without_response/notify INVITE=request/indicate tx_limit=%u",
|
|
(unsigned long long)id, mtu, c->capacity - HEADER, c->outgoing, t->config.tx_limit);
|
|
if (c->outgoing) {
|
|
c->control[0] = HELLO; write64(c->control + 1, t->config.node_id); write64(c->control + 9, c->peer);
|
|
c->control_pending = 1;
|
|
}
|
|
pump(t, c);
|
|
}
|
|
void ble_backend_received(struct ble_transport* t, ble_link_id id, enum ble_channel channel, const uint8_t* data, size_t len) {
|
|
struct ble_connection* c = lookup(t, id);
|
|
if (!c) return;
|
|
if ((unsigned)channel >= BLE_CHANNEL_COUNT) { DEBUG_ERROR(CAT, "BLE invalid RX channel=%d", channel); return; }
|
|
struct ble_channel_state* ch = &c->channels[channel];
|
|
if (channel == BLE_CHANNEL_DATA && len == 17 && !c->ready && ((data[0] == HELLO && !c->outgoing) || (data[0] == ACCEPT && c->outgoing))) {
|
|
uint64_t peer = read64(data + 1), target = read64(data + 9);
|
|
if (!peer || peer == t->config.node_id || (target && target != t->config.node_id) || (c->peer && peer != c->peer)) {
|
|
DEBUG_WARN(CAT, "BLE invalid HELLO identity link=%llu", (unsigned long long)id); close_link(t, c, "invalid identity"); return;
|
|
}
|
|
if (t->config.callbacks.validate && !t->config.callbacks.validate(t->config.arg, id, peer)) {
|
|
DEBUG_WARN(CAT, "BLE validation rejected link=%llu", (unsigned long long)id); close_link(t, c, "validation rejected"); return;
|
|
}
|
|
c->peer = peer; c->ready = 1;
|
|
if (!c->outgoing) {
|
|
c->control[0] = ACCEPT; write64(c->control + 1, t->config.node_id); write64(c->control + 9, peer); c->control_pending = 1;
|
|
}
|
|
DEBUG_INFO(CAT, "BLE session ready link=%llu peer=%016llx", (unsigned long long)id, (unsigned long long)peer);
|
|
if (t->config.callbacks.connection) t->config.callbacks.connection(t->config.arg, id, peer, BLE_READY, "HELLO accepted");
|
|
pump(t, c); return;
|
|
}
|
|
if (!c->ready || len <= HEADER || len > c->capacity || data[0] != FRAME) {
|
|
DEBUG_WARN(CAT, "BLE invalid value link=%llu channel=%d bytes=%zu capacity=%u ready=%d type=%u",
|
|
(unsigned long long)id, channel, len, c->capacity, c->ready, len ? data[0] : 0);
|
|
c->stats.dropped++;
|
|
ch->rx_size = ch->rx_offset = 0; return;
|
|
}
|
|
uint16_t seq = read16(data + 1), total = read16(data + 3), offset = read16(data + 5);
|
|
size_t n = len - HEADER;
|
|
if (offset && ch->dropping && ch->dropped_sequence == seq) {
|
|
DEBUG_TRACE(CAT, "BLE discarded frame tail link=%llu seq=%u offset=%u", (unsigned long long)id, seq, offset); return;
|
|
}
|
|
if (!offset) ch->dropping = 0;
|
|
if (!total || total > BLE_FRAME_MAX || offset > total || n > (size_t)(total - offset)) goto invalid;
|
|
if (!offset) {
|
|
uint16_t expected = ch->rx_seen ? ch->rx_next : 1;
|
|
if (seq != expected) {
|
|
uint16_t gap = (uint16_t)(seq - expected);
|
|
DEBUG_WARN(CAT, "BLE RX sequence gap link=%llu channel=%d expected=%u received=%u missing=%u",
|
|
(unsigned long long)id, channel, expected, seq, gap < 0x8000 ? gap : 0);
|
|
if (gap < 0x8000) c->stats.missing += gap;
|
|
}
|
|
ch->rx_seen = 1; ch->rx_next = (uint16_t)(seq + 1);
|
|
if (ch->rx_size) {
|
|
DEBUG_WARN(CAT, "BLE incomplete frame replaced link=%llu channel=%d seq=%u received=%u total=%u next=%u",
|
|
(unsigned long long)id, channel, ch->rx_sequence, ch->rx_offset, ch->rx_size, seq);
|
|
c->stats.dropped++;
|
|
}
|
|
ch->rx_sequence = seq; ch->rx_size = total; ch->rx_offset = 0; ch->rx_deadline = get_time_us() + DEADLINE_US;
|
|
}
|
|
if (ch->rx_size != total || ch->rx_sequence != seq || ch->rx_offset != offset) goto invalid;
|
|
memcpy(ch->assembly + offset, data + HEADER, n); ch->rx_offset += n;
|
|
if (ch->rx_offset != total) return;
|
|
ch->rx_size = ch->rx_offset = 0;
|
|
if (queue_entry_count(ch->rx) >= BLE_QUEUE_MAX) {
|
|
DEBUG_WARN(CAT, "BLE RX full: frame dropped link=%llu channel=%d", (unsigned long long)id, channel);
|
|
c->stats.dropped++; return;
|
|
}
|
|
struct ll_entry* entry = ll_alloc_lldgram(total);
|
|
if (!entry) { DEBUG_ERROR(CAT, "BLE RX allocation failed bytes=%u", total); c->stats.dropped++; return; }
|
|
memcpy(entry->dgram, ch->assembly, total); entry->len = total;
|
|
if (queue_data_put(ch->rx, entry) != 0) { DEBUG_ERROR(CAT, "BLE RX queue invariant failed"); return; }
|
|
c->stats.rx_bytes += total; c->stats.rx_frames++;
|
|
DEBUG_TRACE(DEBUG_CATEGORY_DEBUG, "BLE wire RX link=%llu channel=%d seq=%u bytes=%u tag=%08x",
|
|
(unsigned long long)id, channel, seq, total, frame_tag(entry->dgram, entry->len));
|
|
DEBUG_TRACE(CAT, "BLE RX frame link=%llu channel=%d seq=%u bytes=%u queued=%d", (unsigned long long)id, channel, seq, total, queue_entry_count(ch->rx));
|
|
void (*received)(void*, ble_link_id) = channel == BLE_CHANNEL_DATA ? t->config.callbacks.received : t->config.callbacks.invite_received;
|
|
if (received) received(t->config.arg, id);
|
|
return;
|
|
invalid:
|
|
c->stats.dropped++;
|
|
DEBUG_WARN(CAT, "BLE fragment dropped link=%llu channel=%d seq=%u offset=%u expected=%u total=%u bytes=%zu",
|
|
(unsigned long long)id, channel, seq, offset, ch->rx_offset, total, n);
|
|
ch->rx_size = ch->rx_offset = 0;
|
|
ch->dropping = 1; ch->dropped_sequence = seq;
|
|
}
|
|
void ble_backend_sent(struct ble_transport* t, ble_link_id id, int success) {
|
|
struct ble_connection* c = lookup(t, id);
|
|
if (!c) return;
|
|
if (!success || !c->pending) { DEBUG_WARN(CAT, "BLE send completion failed link=%llu success=%d pending=%d channel=%d bytes=%u",
|
|
(unsigned long long)id, success, c->pending, c->pending_channel, c->pending_bytes);
|
|
close_link(t, c, "send failed"); return; }
|
|
enum ble_channel channel = c->pending_channel;
|
|
uint64_t waited = get_time_us() - c->submitted;
|
|
c->stats.fragments++; c->stats.wait_us += waited;
|
|
if (waited > c->stats.max_wait_us) c->stats.max_wait_us = waited;
|
|
struct ble_channel_state* ch = &c->channels[channel];
|
|
c->pending = 0;
|
|
if (!c->pending_bytes) c->control_pending = 0;
|
|
else if (ch->current) {
|
|
ch->offset += c->pending_bytes;
|
|
if (ch->offset == ch->current->len) {
|
|
c->stats.tx_bytes += ch->current->len; c->stats.tx_frames++;
|
|
DEBUG_TRACE(DEBUG_CATEGORY_DEBUG, "BLE wire TX link=%llu channel=%d seq=%u bytes=%u tag=%08x",
|
|
(unsigned long long)id, channel, ch->sequence, ch->current->len,
|
|
frame_tag(ch->current->dgram, ch->current->len));
|
|
DEBUG_TRACE(CAT, "BLE TX frame link=%llu channel=%d seq=%u bytes=%u", (unsigned long long)id, channel, ch->sequence, ch->current->len);
|
|
release_entry(ch->current); ch->current = NULL;
|
|
}
|
|
}
|
|
if (ch->blocked && ble_channel_tx_ready(t, id, channel)) {
|
|
ch->blocked = 0; DEBUG_TRACE(CAT, "BLE TX resumed link=%llu", (unsigned long long)id);
|
|
void (*writable)(void*, ble_link_id) = channel == BLE_CHANNEL_DATA ? t->config.callbacks.writable : t->config.callbacks.invite_writable;
|
|
if (writable) writable(t->config.arg, id);
|
|
}
|
|
pump(t, c);
|
|
}
|
|
void ble_backend_closed(struct ble_transport* t, ble_link_id id, const char* reason) {
|
|
struct ble_connection* c = lookup(t, id); if (c) close_link(t, c, reason);
|
|
}
|