From cecdbbe1d7e1366fabb119121eaea7ab5d734238 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 29 Oct 2026 22:10:24 +0100 Subject: [PATCH] Add the mandatory ACK characteristic to BlueZ --- lib/ble/ble_bluez.c | 81 ++++++++++++++++++------------ lib/ble/tests/test_ble_transport.c | 11 ++-- 2 files changed, 55 insertions(+), 37 deletions(-) diff --git a/lib/ble/ble_bluez.c b/lib/ble/ble_bluez.c index 8d4804bf..34d03670 100644 --- a/lib/ble/ble_bluez.c +++ b/lib/ble/ble_bluez.c @@ -1,6 +1,6 @@ /* BlueZ D-Bus через sd-bus (без Qt/GLib). Один uasync-поток, без блокирующих вызовов. - * Серверные DATA notifications и INVITE indications адресуются через AcquireNotify fd каждого клиента. - * DATA завершается после send(fd); только INVITE ждёт байт 1 от BlueZ (ATT completion). */ + * Серверные DATA notifications и INVITE/ACK indications адресуются через AcquireNotify fd каждого клиента. + * DATA завершается после send(fd); INVITE/ACK ждут байт 1 от BlueZ (ATT completion). */ #include "ble_backend.h" #include "../u_async.h" #include "../mem.h" @@ -20,6 +20,7 @@ #define RX SERVICE "/rx" #define TX SERVICE "/tx" #define INVITE SERVICE "/invite" +#define ACK SERVICE "/ack" #define ADV ROOT "/advertisement" #define BEACON ROOT "/beacon" #define DEVICE_IF "org.bluez.Device1" @@ -27,10 +28,10 @@ struct bluez_link { struct ble_backend* backend; ble_link_id id; - char device[BLE_ENDPOINT_MAX], rx[BLE_ENDPOINT_MAX], tx[BLE_ENDPOINT_MAX], invite[BLE_ENDPOINT_MAX]; + char device[BLE_ENDPOINT_MAX], rx[BLE_ENDPOINT_MAX], tx[BLE_ENDPOINT_MAX], invite[BLE_ENDPOINT_MAX], ack[BLE_ENDPOINT_MAX]; sd_bus_slot *connect, *query, *subscribe, *write; - int fd[BLE_CHANNEL_COUNT], outgoing, opened, sending, buffered, subscribed, channel, connected, resolved, mtu_known; - void* io[BLE_CHANNEL_COUNT]; + int fd[BLE_WIRE_COUNT], outgoing, opened, sending, buffered, subscribed, channel, connected, resolved, mtu_known; + void* io[BLE_WIRE_COUNT]; unsigned int mtu; uint8_t value[BLE_VALUE_MAX]; size_t value_size; @@ -41,8 +42,8 @@ struct ble_backend { struct ble_transport* owner; struct UASYNC* ua; sd_bus* bus; - sd_bus_slot *setup, *signals, *objects, *removed, *owner_watch, *service, *rx, *tx, *invite, *manager, *adv, *beacon, *beacon_op; - struct characteristic rx_context, tx_context, invite_context; + sd_bus_slot *setup, *signals, *objects, *removed, *owner_watch, *service, *rx, *tx, *invite, *ack, *manager, *adv, *beacon, *beacon_op; + struct characteristic rx_context, tx_context, invite_context, ack_context; struct advertisement adv_context, beacon_context; struct bluez_link links[BLE_LINKS_MAX]; char adapter[BLE_ENDPOINT_MAX], adapter_name[64], daemon_owner[BLE_ENDPOINT_MAX]; @@ -113,7 +114,9 @@ static struct bluez_link* alloc_link(struct ble_backend* b, ble_link_id id, cons if (strlen(device) >= BLE_ENDPOINT_MAX || find_device(b, device)) return NULL; for (int i = 0; i < BLE_LINKS_MAX; i++) if (!b->links[i].id) { struct bluez_link* l = &b->links[i]; memset(l, 0, sizeof(*l)); - l->backend = b; l->id = id; l->fd[0] = l->fd[1] = -1; l->mtu = 23; l->outgoing = outgoing; strcpy(l->device, device); return l; + l->backend = b; l->id = id; + for (int ch = 0; ch < BLE_WIRE_COUNT; ch++) l->fd[ch] = -1; + l->mtu = 23; l->outgoing = outgoing; strcpy(l->device, device); return l; } return NULL; } @@ -143,7 +146,7 @@ static void notify_read(int fd, void* arg) { link_error(l, "notification socket read failed"); return; } if (!n) { link_error(l, "notification subscription closed"); return; } - if (n != 1 || data[0] != 1 || fd != l->fd[BLE_CHANNEL_INVITE] || l->channel != BLE_CHANNEL_INVITE || + if (n != 1 || data[0] != 1 || l->channel == BLE_WIRE_DATA || fd != l->fd[l->channel] || !l->sending || l->buffered) { DEBUG_WARN(CAT, "BLE indication completion invalid fd=%d expected_fd=%d bytes=%zd sending=%d buffered=%d", fd, l->fd[l->channel], n, l->sending, l->buffered); @@ -163,7 +166,7 @@ static void notify_write(int fd, void* arg) { link_error(l, "notification socket write failed"); return; } l->buffered = 0; uasync_set_socket_write(l->backend->ua, l->io[l->channel], 0); - if (l->channel == BLE_CHANNEL_DATA) { + if (l->channel == BLE_WIRE_DATA) { l->sending = 0; ble_backend_sent(l->backend->owner, l->id, 1); } @@ -200,7 +203,8 @@ static int write_value(sd_bus_message* m, void* arg, sd_bus_error* error) { struct bluez_link* l = incoming(b, device); if (!l) return reject(m, "connection unavailable"); ble_link_id id = l->id; - ble_backend_received(b->owner, id, ctx->tx == 2 ? BLE_CHANNEL_INVITE : BLE_CHANNEL_DATA, data, len); + enum ble_wire_channel channel = ctx->tx >= 2 ? (enum ble_wire_channel)(ctx->tx - 1) : BLE_WIRE_DATA; + ble_backend_received(b->owner, id, channel, data, len); if (!find_link(b, id)) return reject(m, "session rejected"); int r = sd_bus_reply_method_return(m, ""); if (r < 0) DEBUG_ERROR(CAT, "BLE BlueZ WriteValue reply failed link=%llu: %s", (unsigned long long)id, strerror(-r)); @@ -212,7 +216,7 @@ static int acquire_notify(sd_bus_message* m, void* arg, sd_bus_error* error) { if (!sd_bus_message_get_sender(m) || strcmp(sd_bus_message_get_sender(m), b->daemon_owner)) return reject(m, "sender is not BlueZ"); if (!ctx->tx || options(m, &device, &mtu, &offset) < 0 || !device || offset || mtu < 23) return reject(m, "invalid AcquireNotify"); struct bluez_link* l = incoming(b, device); - int channel = ctx->tx == 2 ? BLE_CHANNEL_INVITE : BLE_CHANNEL_DATA; + int channel = ctx->tx >= 2 ? ctx->tx - 1 : BLE_WIRE_DATA; if (!l || l->fd[channel] >= 0) return reject(m, "subscription unavailable"); int pair[2]; if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, pair) < 0) { @@ -228,8 +232,8 @@ static int acquire_notify(sd_bus_message* m, void* arg, sd_bus_error* error) { link_error(l, "AcquireNotify reply failed"); return r; } DEBUG_DEBUG(CAT, "BLE BlueZ subscription link=%llu channel=%d mode=%s mtu=%u", - (unsigned long long)l->id, channel, channel == BLE_CHANNEL_DATA ? "notification" : "indication", mtu); - if (l->fd[0] >= 0 && l->fd[1] >= 0 && !l->opened) { + (unsigned long long)l->id, channel, channel == BLE_WIRE_DATA ? "notification" : "indication", mtu); + if (l->fd[0] >= 0 && l->fd[1] >= 0 && l->fd[2] >= 0 && !l->opened) { l->opened = 1; ble_backend_opened(b->owner, l->id, mtu); } bus_update(b); return r; @@ -242,9 +246,10 @@ static int get_service(sd_bus* bus, const char* path, const char* interface, con static int get_char(sd_bus* bus, const char* path, const char* interface, const char* property, sd_bus_message* reply, void* arg, sd_bus_error* error) { (void)bus; (void)path; (void)interface; (void)error; struct characteristic* ctx = arg; - if (!strcmp(property, "UUID")) return sd_bus_message_append(reply, "s", ctx->tx == 2 ? BLE_INVITE_UUID : ctx->tx ? BLE_TX_UUID : BLE_RX_UUID); + if (!strcmp(property, "UUID")) return sd_bus_message_append(reply, "s", + ctx->tx == 3 ? BLE_ACK_UUID : ctx->tx == 2 ? BLE_INVITE_UUID : ctx->tx ? BLE_TX_UUID : BLE_RX_UUID); if (!strcmp(property, "Service")) return sd_bus_message_append(reply, "o", SERVICE); - if (!strcmp(property, "Flags") && ctx->tx == 2) return sd_bus_message_append(reply, "as", 2, "write", "indicate"); + if (!strcmp(property, "Flags") && ctx->tx >= 2) return sd_bus_message_append(reply, "as", 2, "write", "indicate"); if (!strcmp(property, "Flags")) return sd_bus_message_append(reply, "as", 1, ctx->tx ? "notify" : "write-without-response"); return sd_bus_message_append(reply, "b", 0); /* NotifyAcquired объявляет поддержку адресного fd API. */ } @@ -313,10 +318,11 @@ static const sd_bus_vtable beacon_table[] = { static int subscribed(sd_bus_message* m, void* arg, sd_bus_error* error) { (void)error; struct bluez_link* l = arg; l->subscribe = sd_bus_slot_unref(l->subscribe); if (response_error(m, "StartNotify")) { link_error(l, "subscription failed"); return 0; } - if (++l->subscribed < BLE_CHANNEL_COUNT) { - int r = sd_bus_call_method_async(l->backend->bus, &l->subscribe, "org.bluez", l->invite, CHAR_IF, + if (++l->subscribed < BLE_WIRE_COUNT) { + const char* path = l->subscribed == BLE_WIRE_INVITE ? l->invite : l->ack; + int r = sd_bus_call_method_async(l->backend->bus, &l->subscribe, "org.bluez", path, CHAR_IF, "StartNotify", subscribed, l, ""); - if (r < 0) link_error(l, "invitation subscription failed"); + if (r < 0) link_error(l, "control subscription failed"); return 0; } DEBUG_INFO(CAT, "BLE BlueZ DATA configured link=%llu write=without_response receive=notification mtu=%u; PHY/interval managed by OS/peer", @@ -359,8 +365,10 @@ static int properties(struct ble_backend* b, const char* path, const char* inter const void* bytes; size_t size; r = sd_bus_message_read_array(m, 'y', &bytes, &size); if (r >= 0) for (int i = 0; i < BLE_LINKS_MAX; i++) { struct bluez_link* l = &b->links[i]; - if (size && l->id && l->outgoing && (!strcmp(l->tx, path) || !strcmp(l->invite, path))) - ble_backend_received(b->owner, l->id, !strcmp(l->invite, path) ? BLE_CHANNEL_INVITE : BLE_CHANNEL_DATA, bytes, size); + if (live && size && l->id && l->opened && l->outgoing && + (!strcmp(l->tx, path) || !strcmp(l->invite, path) || !strcmp(l->ack, path))) + ble_backend_received(b->owner, l->id, !strcmp(l->ack, path) ? BLE_WIRE_ACK : + !strcmp(l->invite, path) ? BLE_WIRE_INVITE : BLE_WIRE_DATA, bytes, size); } } else r = sd_bus_message_skip(m, type); if (r < 0) return r; @@ -396,6 +404,7 @@ static int properties(struct ble_backend* b, const char* path, const char* inter } if (!strcmp(uuid, BLE_TX_UUID)) strcpy(l->tx, path); if (!strcmp(uuid, BLE_INVITE_UUID)) strcpy(l->invite, path); + if (!strcmp(uuid, BLE_ACK_UUID)) strcpy(l->ack, path); } return 0; } @@ -426,9 +435,9 @@ static int queried(sd_bus_message* m, void* arg, sd_bus_error* error) { l->query = sd_bus_slot_unref(l->query); if (!l->id) return 0; if (r < 0) { link_error(l, "service discovery failed"); return 0; } - DEBUG_DEBUG(CAT, "BLE BlueZ GATT snapshot link=%llu connected=%d resolved=%d RX=%d TX=%d INVITE=%d mtu=%u known=%d", - (unsigned long long)l->id, l->connected, l->resolved, !!l->rx[0], !!l->tx[0], !!l->invite[0], l->mtu, l->mtu_known); - if (l->connected && l->resolved && l->rx[0] && l->tx[0] && l->invite[0] && !l->subscribe && !l->opened) { + DEBUG_DEBUG(CAT, "BLE BlueZ GATT snapshot link=%llu connected=%d resolved=%d RX=%d TX=%d INVITE=%d ACK=%d mtu=%u known=%d", + (unsigned long long)l->id, l->connected, l->resolved, !!l->rx[0], !!l->tx[0], !!l->invite[0], !!l->ack[0], l->mtu, l->mtu_known); + if (l->connected && l->resolved && l->rx[0] && l->tx[0] && l->invite[0] && l->ack[0] && !l->subscribe && !l->opened) { if (!l->mtu_known) DEBUG_WARN(CAT, "BLE BlueZ MTU unavailable link=%llu: using ATT minimum 23", (unsigned long long)l->id); r = sd_bus_call_method_async(l->backend->bus, &l->subscribe, "org.bluez", l->tx, CHAR_IF, "StartNotify", subscribed, l, ""); if (r < 0) link_error(l, "StartNotify request failed"); @@ -466,7 +475,8 @@ static int object_removed(sd_bus_message* m, void* arg, sd_bus_error* error) { if (!strcmp(path, b->adapter)) { fail(b, "Bluetooth adapter removed", ENODEV); return 0; } for (int i = 0; i < BLE_LINKS_MAX; i++) { struct bluez_link* l = &b->links[i]; - if (l->id && (!strcmp(path, l->device) || !strcmp(path, l->rx) || !strcmp(path, l->tx) || !strcmp(path, l->invite))) link_error(l, "BlueZ object removed"); + if (l->id && (!strcmp(path, l->device) || !strcmp(path, l->rx) || !strcmp(path, l->tx) || + !strcmp(path, l->invite) || !strcmp(path, l->ack))) link_error(l, "BlueZ object removed"); } return 0; } @@ -531,6 +541,7 @@ struct ble_backend* ble_backend_create(struct ble_transport* owner, struct UASYN if (cfg->adapter) snprintf(b->adapter_name, sizeof(b->adapter_name), "%s", cfg->adapter); b->rx_context.backend = b; b->tx_context.backend = b; b->tx_context.tx = 1; b->invite_context.backend = b; b->invite_context.tx = 2; + b->ack_context.backend = b; b->ack_context.tx = 3; b->adv_context.backend = b; b->beacon_context.backend = b; b->beacon_context.beacon = 1; int r = sd_bus_open_system(&b->bus); if (r >= 0) r = sd_bus_add_object_manager(b->bus, &b->manager, ROOT); @@ -538,6 +549,7 @@ struct ble_backend* ble_backend_create(struct ble_transport* owner, struct UASYN if (r >= 0) r = sd_bus_add_object_vtable(b->bus, &b->rx, RX, CHAR_IF, rx_table, &b->rx_context); if (r >= 0) r = sd_bus_add_object_vtable(b->bus, &b->tx, TX, CHAR_IF, tx_table, &b->tx_context); if (r >= 0) r = sd_bus_add_object_vtable(b->bus, &b->invite, INVITE, CHAR_IF, invite_table, &b->invite_context); + if (r >= 0) r = sd_bus_add_object_vtable(b->bus, &b->ack, ACK, CHAR_IF, invite_table, &b->ack_context); if (r >= 0) r = sd_bus_add_object_vtable(b->bus, &b->adv, ADV, "org.bluez.LEAdvertisement1", adv_table, &b->adv_context); if (r >= 0) r = sd_bus_add_object_vtable(b->bus, &b->beacon, BEACON, "org.bluez.LEAdvertisement1", beacon_table, &b->beacon_context); if (r >= 0) r = sd_bus_add_match_async(b->bus, &b->signals, @@ -559,13 +571,14 @@ void ble_backend_close(struct ble_backend* b, ble_link_id id) { struct bluez_link* l = find_link(b, id); if (!l) return; l->connect = sd_bus_slot_unref(l->connect); l->query = sd_bus_slot_unref(l->query); l->subscribe = sd_bus_slot_unref(l->subscribe); l->write = sd_bus_slot_unref(l->write); - for (int channel = 0; channel < BLE_CHANNEL_COUNT; channel++) { + for (int channel = 0; channel < BLE_WIRE_COUNT; channel++) { if (l->io[channel]) uasync_remove_socket(b->ua, l->io[channel]); if (l->fd[channel] >= 0) close(l->fd[channel]); } /* Не разрываем общий Device1 ACL: он может принадлежать другим профилям/приложениям. */ if (l->outgoing && !b->failed) for (int channel = 0; channel < l->subscribed; channel++) { - int r = sd_bus_call_method_async(b->bus, NULL, "org.bluez", channel ? l->invite : l->tx, CHAR_IF, "StopNotify", NULL, NULL, ""); + const char* path = channel == BLE_WIRE_ACK ? l->ack : channel == BLE_WIRE_INVITE ? l->invite : l->tx; + int r = sd_bus_call_method_async(b->bus, NULL, "org.bluez", path, CHAR_IF, "StopNotify", NULL, NULL, ""); if (r < 0) DEBUG_WARN(CAT, "BLE StopNotify failed: %s", strerror(-r)); } memset(l, 0, sizeof(*l)); bus_update(b); @@ -578,7 +591,8 @@ void ble_backend_destroy(struct ble_backend* b) { if (b->io) uasync_remove_socket(b->ua, b->io); sd_bus_slot_unref(b->setup); sd_bus_slot_unref(b->signals); sd_bus_slot_unref(b->objects); sd_bus_slot_unref(b->removed); sd_bus_slot_unref(b->owner_watch); - sd_bus_slot_unref(b->service); sd_bus_slot_unref(b->rx); sd_bus_slot_unref(b->tx); sd_bus_slot_unref(b->invite); sd_bus_slot_unref(b->manager); + sd_bus_slot_unref(b->service); sd_bus_slot_unref(b->rx); sd_bus_slot_unref(b->tx); sd_bus_slot_unref(b->invite); + sd_bus_slot_unref(b->ack); sd_bus_slot_unref(b->manager); sd_bus_slot_unref(b->adv); sd_bus_slot_unref(b->beacon); sd_bus_slot_unref(b->beacon_op); /* Закрытие приватного bus освобождает регистрации/scan сессии в BlueZ по owner-disconnect. */ sd_bus_close_unref(b->bus); u_free(b); @@ -607,9 +621,9 @@ static int written(sd_bus_message* m, void* arg, sd_bus_error* error) { (void)error; struct bluez_link* l = arg; l->write = sd_bus_slot_unref(l->write); l->sending = 0; ble_backend_sent(l->backend->owner, l->id, !response_error(m, "WriteValue")); return 0; } -int ble_backend_send(struct ble_backend* b, ble_link_id id, enum ble_channel channel, const uint8_t* data, size_t len) { +int ble_backend_send(struct ble_backend* b, ble_link_id id, enum ble_wire_channel channel, const uint8_t* data, size_t len) { struct bluez_link* l = find_link(b, id); - if ((unsigned)channel >= BLE_CHANNEL_COUNT || !l || !l->opened || l->sending || !len || len > BLE_VALUE_MAX) { + if ((unsigned)channel >= BLE_WIRE_COUNT || !l || !l->opened || l->sending || !len || len > BLE_VALUE_MAX) { DEBUG_ERROR(CAT, "BLE invalid backend send link=%llu", (unsigned long long)id); return BLE_ERROR; } l->channel = channel; @@ -618,13 +632,14 @@ int ble_backend_send(struct ble_backend* b, ble_link_id id, enum ble_channel cha uasync_set_socket_write(b->ua, l->io[channel], 1); return BLE_OK; } sd_bus_message* m = NULL; - int r = sd_bus_message_new_method_call(b->bus, &m, "org.bluez", channel == BLE_CHANNEL_INVITE ? l->invite : l->rx, CHAR_IF, "WriteValue"); + const char* path = channel == BLE_WIRE_ACK ? l->ack : channel == BLE_WIRE_INVITE ? l->invite : l->rx; + int r = sd_bus_message_new_method_call(b->bus, &m, "org.bluez", path, CHAR_IF, "WriteValue"); if (r >= 0) r = sd_bus_message_append_array(m, 'y', data, len); - if (r >= 0) r = sd_bus_message_append(m, "a{sv}", 1, "type", "s", channel == BLE_CHANNEL_DATA ? "command" : "request"); + if (r >= 0) r = sd_bus_message_append(m, "a{sv}", 1, "type", "s", channel == BLE_WIRE_DATA ? "command" : "request"); if (r >= 0) r = sd_bus_call_async(b->bus, &l->write, m, written, l, 10000000); sd_bus_message_unref(m); if (r < 0) { DEBUG_ERROR(CAT, "BLE WriteValue request: %s", strerror(-r)); return BLE_ERROR; } DEBUG_TRACE(CAT, "BLE WriteValue submitted link=%llu channel=%d type=%s bytes=%zu", (unsigned long long)id, channel, - channel == BLE_CHANNEL_DATA ? "command" : "request", len); + channel == BLE_WIRE_DATA ? "command" : "request", len); l->sending = 1; bus_update(b); return BLE_OK; } diff --git a/lib/ble/tests/test_ble_transport.c b/lib/ble/tests/test_ble_transport.c index 754158bb..5705c528 100644 --- a/lib/ble/tests/test_ble_transport.c +++ b/lib/ble/tests/test_ble_transport.c @@ -7,7 +7,7 @@ #include #include #define CHECK(v) do { if (!(v)) { fprintf(stderr, "FAIL line=%d: %s\n", __LINE__, #v); exit(1); } } while (0) -struct fake_link { ble_link_id id, other; struct ble_backend* peer; uint8_t data[512]; size_t len; int pending; enum ble_channel channel; }; +struct fake_link { ble_link_id id, other; struct ble_backend* peer; uint8_t data[512]; size_t len; int pending; enum ble_wire_channel channel; }; struct ble_backend { struct ble_transport* owner; struct UASYNC* ua; void* task; int index, started; struct fake_link links[16]; }; static struct ble_backend* backends[3]; static unsigned int mtu = 23; @@ -41,7 +41,7 @@ int ble_backend_connect(struct ble_backend* b, ble_link_id id, const struct ble_ void ble_backend_close(struct ble_backend* b, ble_link_id id) { struct fake_link* f = slot(b, id); if (f) memset(f, 0, sizeof(*f)); } -int ble_backend_send(struct ble_backend* b, ble_link_id id, enum ble_channel channel, const uint8_t* data, size_t len) { +int ble_backend_send(struct ble_backend* b, ble_link_id id, enum ble_wire_channel channel, const uint8_t* data, size_t len) { struct fake_link* f = slot(b, id); CHECK(f && !f->pending && len <= sizeof(f->data)); CHECK(len <= mtu - 3); f->channel = channel; memcpy(f->data, data, len); f->len = len; f->pending = 1; schedule(b); return 0; @@ -56,7 +56,7 @@ static void run(void* arg) { ble_link_id id = f->id, other = f->other; struct ble_backend* peer = f->peer; uint8_t data[512]; size_t len = f->len; memcpy(data, f->data, len); f->pending = 0; - int dropped = data[0] == 3 && drop && --drop == 0; + int dropped = f->channel != BLE_WIRE_ACK && data[f->channel == BLE_WIRE_DATA ? 2 : 0] == 3 && drop && --drop == 0; if (!dropped && peer && slot(peer, other)) ble_backend_received(peer->owner, other, f->channel, data, len); if (slot(b, id)) ble_backend_sent(b->owner, id, 1); } @@ -66,7 +66,10 @@ static void connection(void* arg, ble_link_id id, uint64_t node, enum ble_state (void)arg; (void)id; (void)node; (void)reason; if (s == BLE_READY) ready_count++; } static void writable(void* arg, ble_link_id id) { (void)arg; (void)id; writable_count++; } -static void drain(struct UASYNC* ua) { for (int i = 0; i < 1000; i++) uasync_poll(ua, 0); } +static void drain(struct UASYNC* ua) { + uint64_t end = get_time_us() + 2 * BLE_ACK_INTERVAL_US; + do { uasync_poll(ua, 10); } while (get_time_us() < end); +} static struct ll_entry* frame(unsigned int size, uint8_t value) { struct ll_entry* e = ll_alloc_lldgram(size); CHECK(e); e->len = size; memset(e->dgram, value, size); return e; }